diff --git a/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt b/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt new file mode 100644 index 0000000000000..a08e9010d4e52 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt @@ -0,0 +1,25 @@ +Altera FPGA DMA FIFO driver + +Required properties: +- compatible : "altr,fpga-dma"; + +- reg : CSR and DATA register resource definitions (address and length). + +- reg-names : Names of the register resources. Should be "csr", "data". + +- dmas : DMA request lines. Should be <&pdma 0 &pdma 1> + +- dma-names : Names of DMA request lines. Should be "tx", "rx". + +Example: + + fpgadma: fifo { + #address-cells = <1>; + #size-cells = <1>; + compatible = "altr,fpga-dma"; + reg = <0xff230000 0x20>, <0xc0011000 0x400>; + reg-names = "csr", "data"; + dmas = <&pdma 0 &pdma 1>; + dma-names = "tx", "rx"; + }; + diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-l2-ecc.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-l2-ecc.txt new file mode 100644 index 0000000000000..080525bab3f96 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-l2-ecc.txt @@ -0,0 +1,15 @@ +Altera SoCFPGA L2 cache Error Detection and Correction [EDAC] + +Required Properties: +- compatible : Should be "altr,l2-edac" +- reg : Address and size for ECC error interrupt clear registers. +- interrupts : Should be single bit error interrupt, then double bit error + interrupt. Note the rising edge type. + +Example: + + l2edac@xffd08140 { + compatible = "altr,l2-edac"; + reg = <0xffd08140 0x4>; + interrupts = <0 36 1>, <0 37 1>; + }; diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-l3.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-l3.txt new file mode 100644 index 0000000000000..c99094a3e6467 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-l3.txt @@ -0,0 +1,14 @@ +Altera SOCFPGA L3 Interconnect (NIC-301) + +Required properties: +- compatible : "altr,l3regs", "syscon"; + Note that syscon is invoked for this device to support the FPGA + bridge driver and possibly other devices in the future. See + also Documentation/devicetree/bindings/mfd/syscon.txt +- reg : Should contain 1 register ranges(address and length) + +Example: + l3regs@0xff800000 { + compatible = "altr,l3regs", "syscon"; + reg = <0xff800000 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-ocram-ecc.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-ocram-ecc.txt new file mode 100644 index 0000000000000..31ab205a7a517 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-ocram-ecc.txt @@ -0,0 +1,16 @@ +Altera SoCFPGA On-Chip RAM Error Detection and Correction [EDAC] + +OCRAM ECC Required Properties: +- compatible : Should be "altr,ocram-edac" +- reg : Address and size for ECC error interrupt clear registers. +- iram : phandle to On-Chip RAM definition. +- interrupts : Should be single bit error interrupt, then double bit error + interrupt. Note the rising edge type. + +Example: + ocramedac@ffd08144 { + compatible = "altr,ocram-edac"; + reg = <0xffd08144 0x4>; + iram = <&ocram>; + interrupts = <0 178 1>, <0 179 1>; + }; diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-sdram-edac.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-sdram-edac.txt new file mode 100644 index 0000000000000..9348c53f063d8 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-sdram-edac.txt @@ -0,0 +1,12 @@ +Altera SOCFPGA SDRAM Error Detection & Correction [EDAC] + +Required properties: +- compatible : should contain "altr,sdr-edac"; +- interrupts : Should contain the SDRAM ECC IRQ in the + appropriate format for the IRQ controller. + +Example: + sdramedac@0 { + compatible = "altr,sdram-edac"; + interrupts = <0 39 4>; + }; diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-sdram.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-sdram.txt new file mode 100644 index 0000000000000..351ab7bf423c7 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-sdram.txt @@ -0,0 +1,14 @@ +Altera SOCFPGA SDRAM Controller + +Required properties: +- compatible : "altr,sdr-ctl", "syscon"; + Note that syscon is invoked for this device to support the FPGA + bridge driver and possibly other devices in the future. See + also Documentation/devicetree/bindings/mfd/syscon.txt +- reg : Should contain 1 register ranges(address and length) + +Example: + sdrctl@0xffc25000 { + compatible = "altr,sdr-ctl", "syscon"; + reg = <0xffc25000 0x1000>; + }; diff --git a/Documentation/devicetree/bindings/arm/altera/socfpga-system.txt b/Documentation/devicetree/bindings/arm/altera/socfpga-system.txt index f4d04a0672824..a17c582888255 100644 --- a/Documentation/devicetree/bindings/arm/altera/socfpga-system.txt +++ b/Documentation/devicetree/bindings/arm/altera/socfpga-system.txt @@ -1,13 +1,16 @@ Altera SOCFPGA System Manager Required properties: -- compatible : "altr,sys-mgr" +- compatible : "altr,sys-mgr", "syscon"; + - reg : Should contain 1 register ranges(address and length) + Note that syscon is invoked for this device. See + also Documentation/devicetree/bindings/mfd/syscon.txt - cpu1-start-addr : CPU1 start address in hex. Example: sysmgr@ffd08000 { - compatible = "altr,sys-mgr"; + compatible = "altr,sys-mgr", "syscon"; reg = <0xffd08000 0x1000>; cpu1-start-addr = <0xffd080c4>; }; diff --git a/Documentation/devicetree/bindings/fpga/altera-fpga-mgr.txt b/Documentation/devicetree/bindings/fpga/altera-fpga-mgr.txt new file mode 100644 index 0000000000000..fbce3b0e18ab8 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/altera-fpga-mgr.txt @@ -0,0 +1,25 @@ +Altera FPGA Manager + +Required properties: + + - compatible : should be "," + "altr,fpga-mgr-1.0", "altr,fpga-mgr"; + + - transport : the interface for register and configuration data. + Currently only memory mapped io is supported, so must be "mmio" + + - reg : base address for memory mapped io. + - The first index is for FPGA manager register access. + - The second index is for writing FPGA configuration data. + + - interrupts : interrupts for the FPGA Manager device. + +Example: + + hps_0_fpgamgr: fpgamgr@0xff706000 { + compatible = "altr,fpga-mgr-1.0", "altr,fpga-mgr"; + transport = "mmio"; + reg = <0xFF706000 0x1000 + 0xFFB90000 0x1000>; + interrupts = <0 175 4>; + }; diff --git a/Documentation/devicetree/bindings/fpga/altera-fpga2sdram-bridge.txt b/Documentation/devicetree/bindings/fpga/altera-fpga2sdram-bridge.txt new file mode 100644 index 0000000000000..2b6c37b6d09b4 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/altera-fpga2sdram-bridge.txt @@ -0,0 +1,53 @@ +Altera FPGA To SDRAM Bridge Driver + + This driver manages a bridge between an FPGA and the SDRAM used by an + host processor system (HPS). The bridge contains a number read ports, + write ports, and command ports. Reconfiguring these ports requires + that no SDRAM transactions occur during reconfiguration. In other words, + the code reconfiguring the ports cannot be run out of SDRAM nor can the + FPGA access the SDRAM during the reconfiguration. This driver does not + support reconfiguring the ports. Typcially, the ports are configured by + code running out of onchip ram before Linux is started. + + This driver supports enabling and disabling of the configured ports all + at once, which allows for safe reprogramming of the FPGA from user space, + provided the new FPGA image uses the same port configuration. + User space can enable/disable the bridge by writing a "1" or a "0", + respectively, to its enable file under bridge's entry in + /sys/class/fpga-bridge. Typically, one disables the bridges before + reprogramming the FPGA. Once the FPGA is reprogrammed, the bridges + are reenabled. + +Required properties: + + - compatible : "altr,socfpga-fpga2sdram-bridge" + + - read-ports-mask : Bits 0 to 3 corresponds read ports 0 to 3. A + bit set to 1 indicates the corresponding read port should be enabled. + + - write-ports-mask : Bits 0 to 3 corresponds write ports 0 to 3. A + bit set to 1 indicates the corresponding write port should be enabled. + + - cmd-ports-mask : Bits 0 to 5 correspond to command ports 0 to 5. A + bit set to 1 indicates the corresponding command port should be enabled. + +Optional properties: + + - label : name that you want this bridge to show up as under + /sys/class/fpga-bridge. + Default is br if this is not specified + + - init-val :0 if driver should disable bridge at startup + 1 if driver should enable bridge at startup + driver leaves bridge in current state if property not specified + + +Example: + fpga2sdram_br: fpgabridge@3 { + compatible = "altr,socfpga-fpga2sdram-bridge"; + label = "fpga2sdram"; + read-ports-mask = <3>; + write-ports-mask = <3>; + cmd-ports-mask = <0xd>; + init-val = <0>; + }; diff --git a/Documentation/devicetree/bindings/fpga/altera-hps2fpga-bridge.txt b/Documentation/devicetree/bindings/fpga/altera-hps2fpga-bridge.txt new file mode 100644 index 0000000000000..d9eb0a7434dc0 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/altera-hps2fpga-bridge.txt @@ -0,0 +1,45 @@ +Altera FPGA/HPS Bridge Driver + + This driver manages a bridge between a FPGA and a host processor system + (HPS). User space can enable/disable the bridge by writing a "1" or a "0", + respectively, to its enable file under bridge's entry in + /sys/class/fpga-bridge. Typically, one disables the bridges before + reprogramming the FPGA. Once the FPGA is reprogrammed, the bridges + are reenabled. + +Required properties: + + - compatible : "altr,socfpga-hps2fpga-bridge" + "altr,socfpga-lwhps2fpga-bridge" + "altr,socfpga-fpga2hps-bridge" + + - clocks : clocks used by this module + +Optional properties: + - label : name that you want this bridge to show up as under + /sys/class/fpga-bridge. Default is br if this is not specified + + - init-val : 0 if driver should disable bridge at startup + 1 if driver should enable bridge at startup + driver leaves bridge in current state if property not specified + +Example: + hps_fpgabridge0: fpgabridge@0 { + compatible = "altr,socfpga-hps2fpga-bridge"; + label = "hps2fpga"; + clocks = <&l4_main_clk>; + init-val = <1>; + }; + + hps_fpgabridge1: fpgabridge@1 { + compatible = "altr,socfpga-lwhps2fpga-bridge"; + label = "lwhps2fpga"; + clocks = <&l4_main_clk>; + init-val = <0>; + }; + + hps_fpgabridge2: fpgabridge@2 { + compatible = "altr,socfpga-fpga2hps-bridge"; + label = "fpga2hps"; + clocks = <&l4_main_clk>; + }; diff --git a/Documentation/devicetree/bindings/gpio/gpio-altera.txt b/Documentation/devicetree/bindings/gpio/gpio-altera.txt new file mode 100644 index 0000000000000..1de1f9bc24dee --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-altera.txt @@ -0,0 +1,44 @@ +Altera GPIO controller bindings + +Required properties: +- compatible: + - "altr,pio-1.0" +- reg: Physical base address and length of the controller's registers. +- #gpio-cells : Should be 1 + - The first cell is the gpio offset number +- gpio-controller : Marks the device node as a GPIO controller. +- #interrupt-cells : Should be 1. + - The first cell is the GPIO offset number within the GPIO controller. +- interrupts: Specify the interrupt. +- interrupt-controller: Mark the device node as an interrupt controller + +Altera GPIO specific required properties: +- altr,interrupt_trigger: Specifies the interrupt trigger type the GPIO + hardware is synthesized. This field is required if the Altera GPIO controller + used has IRQ enabled as the interrupt type is not software controlled, + but hardware synthesized. Required if GPIO is used as an interrupt + controller. The value is defined in + Only the following flags are supported: + IRQ_TYPE_EDGE_RISING + IRQ_TYPE_EDGE_FALLING + IRQ_TYPE_EDGE_BOTH + IRQ_TYPE_LEVEL_HIGH + +Altera GPIO specific optional properties: +- altr,gpio-bank-width: Width of the GPIO bank. This defines how many pins the + GPIO device has. Ranges between 1-32. Optional and defaults to 32 is not + specified. + +Example: + +gpio_altr: gpio@40000 { + compatible = "altr,pio-1.0"; + reg = <0xff200000 0x10>; + interrupts = <0 45 4>; + altr,gpio-bank-width = <32>; + altr,interrupt_trigger = ; + #gpio-cells = <1>; + gpio-controller; + #interrupt-cells = <1>; + interrupt-controller; +}; diff --git a/Documentation/devicetree/bindings/i2c/i2c-designware.txt b/Documentation/devicetree/bindings/i2c/i2c-designware.txt index 5199b0c8cf7a4..dd8431f9988f2 100644 --- a/Documentation/devicetree/bindings/i2c/i2c-designware.txt +++ b/Documentation/devicetree/bindings/i2c/i2c-designware.txt @@ -9,6 +9,8 @@ Required properties : Recommended properties : - clock-frequency : desired I2C bus clock frequency in Hz. + - speed-mode : 0 = standard (0 - 100Kb/s) + : 1 = fast (<= 400Kb/s) <== default Optional properties : - i2c-sda-hold-time-ns : should contain the SDA hold time in nanoseconds. @@ -29,6 +31,7 @@ Example : reg = <0xf0000 0x1000>; interrupts = <11>; clock-frequency = <400000>; + speed-mode = <1>; }; i2c@1120000 { diff --git a/Documentation/devicetree/bindings/mailbox/mailbox-altera.txt b/Documentation/devicetree/bindings/mailbox/mailbox-altera.txt new file mode 100644 index 0000000000000..4227e70cf73da --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/mailbox-altera.txt @@ -0,0 +1,32 @@ +Altera mailbox (simple) soft IP + +Required properties: +- compatible : "altr,mailbox-1.0". +- reg : physical base address of the mailbox and length of memory mapped + region. +- linux,mailbox-name : Mailbox instance name + +Optional properties: +- interrupt-parent : interrupt source phandle. +- interrupts : interrupt number. The interrupt specifier format depends on the + interrupt controller parent. + +The property of "linux,mailbox-name" must be unique. This name will be used as +controller name in driver to identify the controller and the mailbox client will +use this name when requesting a mailbox channel. + +Example: + mailbox0: mailbox0@0x100 { + compatible = "altr,mailbox-1.0"; + reg = <0x100 0x8>; + interrupt-parent = < &gic_0 >; + interrupts = <5>; + linux,mailbox-name = "mailbox0"; + }; + +Example of mailbox's client node that includes mailbox phandle. + mclient0: mclient0@0x200 { + compatible = "client-1.0"; + reg = <0x200 0x10>; + mailbox = <&mailbox0>; + }; diff --git a/Documentation/devicetree/bindings/misc/altera-hwmutex.txt b/Documentation/devicetree/bindings/misc/altera-hwmutex.txt new file mode 100644 index 0000000000000..6a583d08ece43 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera-hwmutex.txt @@ -0,0 +1,22 @@ +Altera hardware mutex +Altera hardware mutex can provide hardware assistance for synchronization and +mutual exclusion between processors in asymmetric/symmetric multiprocessing +(AMP/SMP) system or multi processes/threads in uniprocessor system. + +Required properties: +- compatible : "altr,mutex-1.0". +- reg : physical base address of the mutex and length of memory mapped + region. + +Example: + mutex0: mutex0@0x100 { + compatible = "altr,hwmutex-1.0"; + reg = <0x100 0x8>; + }; + +Example of mutex's client node that includes mutex phandle. + mclient0: mclient0@0x200 { + compatible = "client-1.0"; + reg = <0x200 0x10>; + mutex = <&mutex0>; + }; diff --git a/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt b/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt new file mode 100644 index 0000000000000..09f6820576163 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt @@ -0,0 +1,49 @@ +Altera Interrupt Latency Counter soft IP +Altera Interrupt Latency Counter IP core driver provides a sysfs interface +for user to obtain interrupt latency values from Altera Interrupt Latency +Counter soft IP. + +The sysfs interface is located at path, +/sys/bus/platform/devices/{addr}.ilc/ilc_data/{int_#} +with +- {addr} = the base address of the soft ip +- {int_#} = the interrupt number + +Example use case: +# cat /sys/bus/platform/devices/c0010000.ilc/ilc_data/40 + +Required properties: +- compatible : + - "altr,ilc-1.0" +- reg : + - physical base address of the soft ip and length of memory mapped region +- interrupt-parent : + - interrupt source phandle similiar to the interrupt source node +- interrupts : + -interrupt number. The interrupt specifier format depends on the interrupt + controller parent + +Altera specific properties: +- altr,sw-fifo-depth : + - define software fifo depth needed to record latency values + +Note: +- For edge triggered interrupt, the order of loading the ILC driver relative + to driver of the actual interrupt source affects the meaning of the ILC + values. If the ILC driver is loaded first, then the count values represent + the time to the start of the interrupt handler of the of the interrupt source. + If the order is switched, then the counts represent the time to finish the + interrupt handler for the interrupt source. + +- The driver for the interrupt source must be changed to request a shared irq. + +Example: + interrupt_latency_counter_0: intc@0x10000000 { + compatible = "altr,ilc-1.0"; + reg = <0x10000000 0x00000100>; + interrupt-parent = < &interrupt_parent >; + interrupts = < 0 1 4 >; + altr,sw-fifo-depth = < 32 >; + }; + + diff --git a/Documentation/devicetree/bindings/misc/altera_sysid.txt b/Documentation/devicetree/bindings/misc/altera_sysid.txt new file mode 100644 index 0000000000000..c3bbd576b74b6 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera_sysid.txt @@ -0,0 +1,11 @@ +Altera Sysid IP core driver + +Required properties: +- compatible: altr,sysid-1.0 + +Example: + +sysid_qsys: sysid@0x10000 { + compatible = "altr,sysid-1.0"; + reg = < 0x10000 0x00000008 >; +}; diff --git a/Documentation/devicetree/bindings/mtd/denali-nand.txt b/Documentation/devicetree/bindings/mtd/denali-nand.txt index b04d03a1d4995..13522208f0a31 100644 --- a/Documentation/devicetree/bindings/mtd/denali-nand.txt +++ b/Documentation/devicetree/bindings/mtd/denali-nand.txt @@ -6,6 +6,8 @@ Required properties: - reg-names: Should contain the reg names "nand_data" and "denali_reg" - interrupts : The interrupt number. - dm-mask : DMA bit mask + - have-hw-ecc-fixup : boolean indicating controller has ECC correction + in hardware, and also has slightly diffrent interrupt status bits. The device tree may optionally contain sub-nodes describing partitions of the address space. See partition.txt for more detail. @@ -20,4 +22,5 @@ nand: nand@ff900000 { reg-names = "nand_data", "denali_reg"; interrupts = <0 144 4>; dma-mask = <0xffffffff>; + have-hw-ecc-fixup; }; diff --git a/Documentation/devicetree/bindings/reset/socfpga-reset.txt b/Documentation/devicetree/bindings/reset/socfpga-reset.txt index 32c1c8bfd5dc5..c338638ee9130 100644 --- a/Documentation/devicetree/bindings/reset/socfpga-reset.txt +++ b/Documentation/devicetree/bindings/reset/socfpga-reset.txt @@ -1,13 +1,16 @@ Altera SOCFPGA Reset Manager Required properties: -- compatible : "altr,rst-mgr" +- compatible : "altr,rst-mgr", "syscon"; + Note that syscon is invoked for this device to support the FPGA + bridge driver and possibly other devices in the future. See + also Documentation/devicetree/bindings/mfd/syscon.txt - reg : Should contain 1 register ranges(address and length) - #reset-cells: 1 Example: rstmgr@ffd05000 { #reset-cells = <1>; - compatible = "altr,rst-mgr"; + compatible = "altr,rst-mgr", "syscon"; reg = <0xffd05000 0x1000>; }; diff --git a/Documentation/devicetree/bindings/spi/spi-cadence-qspi.txt b/Documentation/devicetree/bindings/spi/spi-cadence-qspi.txt new file mode 100644 index 0000000000000..7214b2c83f31f --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi-cadence-qspi.txt @@ -0,0 +1,37 @@ +* SPI driver for Cadence QSPI Controller + +Required properties: +- compatible : Should be "cadence,qspi". +- reg : Contains two entries, each of which is a tuple consisting of a + physical address and length. The first entry is the address and + length of the controller register set. The second entry is the + address and length of the QSPI Controller data area. +- interrupts : Unit interrupt specifier for the controller interrupt. +- ext-decoder : Value of 0 means no external chipselect decoder is + connected, 1 means there is an external chipselect decoder connected. +- num-chipselect : Number of chip select lines. +- fifo-depth : Size of the data FIFO in bytes. +- bus-num : Number of the SPI bus to which the controller is connected. + +Optional properties: +- dmas : DMA request lines. Should be <&pdma 0 &pdma 1>. If present, + then DMA is used for reads and writes +- dma-names : Names of DMA request lines. Should be "tx", "rx". + +Example: + + qspi: spi@ff705000 { + compatible = "cadence,qspi"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xff705000 0x1000>, + <0xffa00000 0x1000>; + interrupts = <0 151 4>; + clocks = <&qspi_clk>; + ext-decoder = <0>; + num-chipselect = <4>; + fifo-depth = <128>; + bus-num = <2>; + dmas = <&pdma 24 &pdma 25>; + dma-names = "tx", "rx"; + } diff --git a/Documentation/devicetree/bindings/tty/newhaven_lcd.txt b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt new file mode 100644 index 0000000000000..5ff0438640d69 --- /dev/null +++ b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt @@ -0,0 +1,21 @@ +* TTY on a Newhaven NHD‐0216K3Z‐NSW‐BBW LCD connected to I2C + +Required properties: +- compatible: Should be "newhaven,nhd‐0216k3z‐nsw‐bbw"; +- reg: i2c address +- height: should be 2 lines +- width: should be 16 characters +- brightness: backlight brightness. Range is 1 to 8, where + 1=OFF and 8=maximum brightness. + +Example: + +&i2c0 { + lcd: lcd@28 { + compatible = "newhaven,nhd‐0216k3z‐nsw‐bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + diff --git a/Documentation/devicetree/bindings/video/altvipfb.txt b/Documentation/devicetree/bindings/video/altvipfb.txt new file mode 100644 index 0000000000000..5e376184ba33e --- /dev/null +++ b/Documentation/devicetree/bindings/video/altvipfb.txt @@ -0,0 +1,22 @@ +Altera Video and Image Processing(VIP) Frame Reader bindings + +Required properties: +- compatible: "altr,vip-frame-reader-9.1" or "altr,vip-frame-reader-1.0" +- reg: Physical base address and length of the framebuffer controller's + registers. +- max-width: The width of the framebuffer in pixels. +- max-height: The height of the framebuffer in pixels. +- bits-per-color: only "8" is currently supported +- mem-word-width = the bus width of the avalon master port on the frame reader + +Example: + +alt_vip_vfr_0: vip@0xff260000 { + compatible = "altr,vip-frame-reader-1.0"; + reg = <0xff260000 0x00000080>; + max-width = <1024>; + max-height = <768>; + bits-per-color = <8>; + mem-word-width = <128>; +}; + diff --git a/arch/arm/boot/dts/socfpga.dtsi b/arch/arm/boot/dts/socfpga.dtsi index 4676f25e87a7e..502ecfd9f5664 100644 --- a/arch/arm/boot/dts/socfpga.dtsi +++ b/arch/arm/boot/dts/socfpga.dtsi @@ -1,18 +1,17 @@ /* - * Copyright (C) 2012 Altera + * Copyright Altera Corporation (C) 2012-2014. All rights reserved. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include "skeleton.dtsi" @@ -83,12 +82,14 @@ <0 108 4>, <0 109 4>, <0 110 4>, - <0 111 4>; + <0 111 4>, + <0 112 4>; #dma-cells = <1>; #dma-channels = <8>; #dma-requests = <32>; clocks = <&l4_main_clk>; clock-names = "apb_pclk"; + copy-align = <3>; }; }; @@ -163,7 +164,7 @@ dbg_base_clk: dbg_base_clk { #clock-cells = <0>; compatible = "altr,socfpga-perip-clk"; - clocks = <&main_pll>; + clocks = <&main_pll>, <&osc1>; div-reg = <0xe8 0 9>; reg = <0x50>; }; @@ -317,7 +318,7 @@ l3_sp_clk: l3_sp_clk { #clock-cells = <0>; compatible = "altr,socfpga-gate-clk"; - clocks = <&mainclk>; + clocks = <&l3_mp_clk>; div-reg = <0x64 2 2>; }; @@ -348,7 +349,7 @@ dbg_clk: dbg_clk { #clock-cells = <0>; compatible = "altr,socfpga-gate-clk"; - clocks = <&dbg_base_clk>; + clocks = <&dbg_at_clk>; div-reg = <0x68 2 2>; clk-gate = <0x60 5>; }; @@ -472,8 +473,37 @@ clocks = <&f2s_periph_ref_clk>, <&main_qspi_clk>, <&per_qspi_clk>; clk-gate = <0xa0 11>; }; + + ddr_dqs_clk_gate: ddr_dqs_clk_gate { + #clock-cells = <0>; + compatible = "altr,socfpga-gate-clk"; + clocks = <&ddr_dqs_clk>; + clk-gate = <0xd8 0>; + }; + + ddr_2x_dqs_clk_gate: ddr_2x_dqs_clk_gate { + #clock-cells = <0>; + compatible = "altr,socfpga-gate-clk"; + clocks = <&ddr_2x_dqs_clk>; + clk-gate = <0xd8 1>; + }; + + ddr_dq_clk_gate: ddr_dq_clk_gate { + #clock-cells = <0>; + compatible = "altr,socfpga-gate-clk"; + clocks = <&ddr_dq_clk>; + clk-gate = <0xd8 2>; + }; + + h2f_user2_clk: h2f_user2_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-gate-clk"; + clocks = <&h2f_usr2_clk>; + clk-gate = <0xd8 3>; + }; + }; - }; + }; gmac0: ethernet@ff700000 { compatible = "altr,socfpga-stmmac", "snps,dwmac-3.70a", "snps,dwmac"; @@ -503,6 +533,32 @@ status = "disabled"; }; + hps_0_fpgamgr: fpgamgr@0xff706000 { + compatible = "altr,fpga-mgr-1.0", "altr,fpga-mgr"; + transport = "mmio"; + reg = <0xFF706000 0x1000 + 0xFFB90000 0x1000>; + interrupts = <0 175 4>; + }; + + hps_fpgabridge0: fpgabridge@0 { + compatible = "altr,socfpga-hps2fpga-bridge"; + label = "hps2fpga"; + clocks = <&l4_main_clk>; + }; + + hps_fpgabridge1: fpgabridge@1 { + compatible = "altr,socfpga-lwhps2fpga-bridge"; + label = "lwhps2fpga"; + clocks = <&l4_main_clk>; + }; + + hps_fpgabridge2: fpgabridge@2 { + compatible = "altr,socfpga-fpga2hps-bridge"; + label = "fpga2hps"; + clocks = <&l4_main_clk>; + }; + i2c0: i2c@ffc04000 { #address-cells = <1>; #size-cells = <0>; @@ -548,7 +604,7 @@ #size-cells = <0>; compatible = "snps,dw-apb-gpio"; reg = <0xff708000 0x1000>; - clocks = <&per_base_clk>; + clocks = <&l4_mp_clk>; status = "disabled"; gpio0: gpio-controller@0 { @@ -568,7 +624,7 @@ #size-cells = <0>; compatible = "snps,dw-apb-gpio"; reg = <0xff709000 0x1000>; - clocks = <&per_base_clk>; + clocks = <&l4_mp_clk>; status = "disabled"; gpio1: gpio-controller@0 { @@ -588,7 +644,7 @@ #size-cells = <0>; compatible = "snps,dw-apb-gpio"; reg = <0xff70a000 0x1000>; - clocks = <&per_base_clk>; + clocks = <&l4_mp_clk>; status = "disabled"; gpio2: gpio-controller@0 { @@ -604,7 +660,7 @@ }; L2: l2-cache@fffef000 { - compatible = "arm,pl310-cache"; + compatible = "arm,pl310-cache", "syscon"; reg = <0xfffef000 0x1000>; interrupts = <0 38 0x04>; cache-unified; @@ -624,6 +680,154 @@ clock-names = "biu", "ciu"; }; + nand: nand@ff900000 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "denali,denali-nand-dt"; + reg = <0xff900000 0x100000>, <0xffb80000 0x10000>; + reg-names = "nand_data", "denali_reg"; + interrupts = <0 144 4>; + dma-mask = <0xffffffff>; + clocks = <&nand_clk>; + have-hw-ecc-fixup; + status = "disabled"; + + partition@nand-boot { + /* 8MB for raw data. */ + label = "NAND Flash Boot Area 8MB"; + reg = <0x0 0x800000>; + }; + partition@nand-rootfs { + /* 128MB jffs2 root filesystem. */ + label = "NAND Flash jffs2 Root Filesystem 128MB"; + reg = <0x800000 0x8000000>; + }; + partition@nand-128 { + label = "NAND Flash 128 MB"; + reg = <0x8800000 0x8000000>; + }; + partition@nand-64 { + label = "NAND Flash 64 MB"; + reg = <0x10800000 0x4000000>; + }; + partition@nand-32 { + label = "NAND Flash 32 MB"; + reg = <0x14800000 0x2000000>; + }; + partition@nand-16 { + label = "NAND Flash 16 MB"; + reg = <0x16800000 0x1000000>; + }; + }; + + ocram: sram@ffff0000 { + compatible = "mmio-sram"; + reg = <0xffff0000 0x10000>; + }; + + pmu { + #address-cells = <1>; + #size-cells = <1>; + compatible = "arm,cortex-a9-pmu"; + interrupts = <0 176 4>, <0 177 4>; + ranges; + + cti0: cti0@ff118000 { + compatible = "arm,coresight-cti"; + reg = <0xff118000 0x100>; + }; + + cti1: cti1@ff119000 { + compatible = "arm,coresight-cti"; + reg = <0xff119000 0x100>; + }; + }; + + sdrctl@0xffc25000 { + compatible = "altr,sdr-ctl", "syscon"; + reg = <0xffc25000 0x1000>; + }; + + sdramedac@0 { + compatible = "altr,sdram-edac"; + interrupts = <0 39 4>; + }; + + l2edac@xffd08140 { + compatible = "altr,l2-edac"; + reg = <0xffd08140 0x4>; + interrupts = <0 36 1>, <0 37 1>; + }; + + ocramedac@ffd08144 { + compatible = "altr,ocram-edac"; + reg = <0xffd08144 0x4>; + iram = <&ocram>; + interrupts = <0 178 1>, <0 179 1>; + }; + + l3regs@0xff800000 { + compatible = "altr,l3regs", "syscon"; + reg = <0xff800000 0x1000>; + }; + + qspi: spi@ff705000 { + compatible = "cadence,qspi"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xff705000 0x1000>, + <0xffa00000 0x1000>; + interrupts = <0 151 4>; + clocks = <&qspi_clk>; + ext-decoder = <0>; /* external decoder */ + num-chipselect = <4>; + fifo-depth = <128>; + bus-num = <2>; + status = "disabled"; + }; + + spi0: spi@fff00000 { + compatible = "snps,dw-spi-mmio"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xfff00000 0x1000>; + interrupts = <0 154 4>; + num-chipselect = <4>; + bus-num = <0>; + tx-dma-channel = <&pdma 16>; + rx-dma-channel = <&pdma 17>; + clocks = <&per_base_clk>; + status = "disabled"; + + spidev@0 { + compatible = "spidev"; + reg = <0>; /* chip select */ + spi-max-frequency = <100000000>; + enable-dma = <1>; + }; + }; + + spi1: spi@fff01000 { + compatible = "snps,dw-spi-mmio"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xfff01000 0x1000>; + interrupts = <0 156 4>; + num-chipselect = <4>; + bus-num = <1>; + tx-dma-channel = <&pdma 20>; + rx-dma-channel = <&pdma 21>; + clocks = <&per_base_clk>; + status = "disabled"; + + spidev@0 { + compatible = "spidev"; + reg = <0>; + spi-max-frequency = <100000000>; + enable-dma = <1>; + }; + }; + /* Local timer */ timer@fffec600 { compatible = "arm,cortex-a9-twd-timer"; @@ -683,7 +887,7 @@ }; rst: rstmgr@ffd05000 { - compatible = "altr,rst-mgr"; + compatible = "altr,rst-mgr", "syscon"; reg = <0xffd05000 0x1000>; }; diff --git a/arch/arm/boot/dts/socfpga_arria5.dtsi b/arch/arm/boot/dts/socfpga_arria5.dtsi index 12d1c2ccaf5ba..1451c165d13de 100644 --- a/arch/arm/boot/dts/socfpga_arria5.dtsi +++ b/arch/arm/boot/dts/socfpga_arria5.dtsi @@ -38,6 +38,10 @@ }; }; + qspi: spi@ff705000 { + status = "okay"; + }; + sysmgr@ffd08000 { cpu1-start-addr = <0xffd080c4>; }; diff --git a/arch/arm/boot/dts/socfpga_arria5_socdk.dts b/arch/arm/boot/dts/socfpga_arria5_socdk.dts index d532d171e3917..3e95061cd11c5 100644 --- a/arch/arm/boot/dts/socfpga_arria5_socdk.dts +++ b/arch/arm/boot/dts/socfpga_arria5_socdk.dts @@ -44,6 +44,43 @@ */ ethernet0 = &gmac1; }; + + leds { + compatible = "gpio-leds"; + hps0 { + label = "hps_led0"; + gpios = <&gpio0 0 1>; + }; + + hps1 { + label = "hps_led1"; + gpios = <&gpio1 11 1>; + }; + + hps2 { + label = "hps_led2"; + gpios = <&gpio0 17 1>; + }; + + hps3 { + label = "hps_led3"; + gpios = <&gpio0 18 1>; + }; + }; + + soc { + gpio@ff708000 { + status = "okay"; + }; + + gpio@ff709000 { + status = "okay"; + }; + + gpio@ff70a000 { + status = "okay"; + }; + }; }; &gmac1 { @@ -62,6 +99,21 @@ &i2c0 { status = "okay"; + speed-mode = <0>; + /* + * adjust the falling times to decrease the i2c frequency to 50Khz + * because the LCD module does not work at the standard 100Khz + */ + i2c-sda-falling-time-ns = <5000>; + i2c-scl-falling-time-ns = <5000>; + + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; eeprom@51 { compatible = "atmel,24c32"; @@ -75,6 +127,62 @@ }; }; +&mmc { + num-slots = <1>; + supports-highspeed; + broken-cd; + altr,dw-mshc-ciu-div = <3>; + altr,dw-mshc-sdr-timing = <0 3>; + + slot@0 { + reg = <0>; + bus-width = <4>; + }; +}; + +&osc1 { + clock-frequency = <25000000>; +}; + +&qspi { + status = "okay"; + flash0: n25q512a@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "n25q512a"; + reg = <0>; /* chip select */ + spi-max-frequency = <100000000>; + m25p,fast-read; + page-size = <256>; + block-size = <16>; /* 2^16, 64KB */ + read-delay = <4>; /* delay value in read data capture register */ + tshsl-ns = <50>; + tsd2d-ns = <50>; + tchsh-ns = <4>; + tslch-ns = <4>; + + partition@qspi-boot { + /* 8MB for raw data. */ + label = "Flash 0 Raw Data"; + reg = <0x0 0x800000>; + }; + + partition@qspi-rootfs { + /* 56MB for jffs2 data. */ + label = "Flash 0 jffs2 Filesystem"; + reg = <0x800000 0x3800000>; + }; + }; +}; + +&uart0 { + status = "okay"; +}; + &usb1 { status = "okay"; }; + +&watchdog0 { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/socfpga_cyclone5.dtsi b/arch/arm/boot/dts/socfpga_cyclone5.dtsi index bf511828729f9..b9c35460c58a1 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5.dtsi +++ b/arch/arm/boot/dts/socfpga_cyclone5.dtsi @@ -1,18 +1,17 @@ /* - * Copyright (C) 2012 Altera Corporation + * Copyright Altera Corporation (C) 2012,2014. All rights reserved. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ /dts-v1/; @@ -20,33 +19,12 @@ / { soc { - clkmgr@ffd04000 { - clocks { - osc1 { - clock-frequency = <25000000>; - }; - }; - }; - - dwmmc0@ff704000 { - num-slots = <1>; - supports-highspeed; - broken-cd; - - slot@0 { - reg = <0>; - bus-width = <4>; - }; + sysmgr@ffd08000 { + cpu1-start-addr = <0xffd080c4>; }; - ethernet@ff702000 { - phy-mode = "rgmii"; - phy-addr = <0xffffffff>; /* probe for phy addr */ + qspi: spi@ff705000 { status = "okay"; }; - - sysmgr@ffd08000 { - cpu1-start-addr = <0xffd080c4>; - }; }; }; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts b/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts index 45de1514af0ac..42f71b93a8487 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts +++ b/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts @@ -1,18 +1,17 @@ /* - * Copyright (C) 2012 Altera Corporation + * Copyright Altera Corporation (C) 2012,2014. All rights reserved. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ #include "socfpga_cyclone5.dtsi" @@ -22,7 +21,7 @@ compatible = "altr,socfpga-cyclone5", "altr,socfpga"; chosen { - bootargs = "console=ttyS0,115200"; + bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait"; }; memory { @@ -37,6 +36,65 @@ */ ethernet0 = &gmac1; }; + + leds { + compatible = "gpio-leds"; + hps0 { + label = "hps_led0"; + gpios = <&gpio1 15 1>; + }; + + hps1 { + label = "hps_led1"; + gpios = <&gpio1 14 1>; + }; + + hps2 { + label = "hps_led2"; + gpios = <&gpio1 13 1>; + }; + + hps3 { + label = "hps_led3"; + gpios = <&gpio1 12 1>; + }; + }; + + soc { + gpio@ff708000 { + status = "okay"; + }; + + gpio@ff709000 { + status = "okay"; + }; + + gpio@ff70a000 { + status = "okay"; + }; + }; +}; + +&can0 { + status = "okay"; +}; + +&mmc { + num-slots = <1>; + supports-highspeed; + broken-cd; + altr,dw-mshc-ciu-div = <3>; + altr,dw-mshc-sdr-timing = <0 3>; + + slot@0 { + cd-gpios = <&gpio1 18 0>; + reg = <0>; + bus-width = <4>; + }; +}; + +&osc1 { + clock-frequency = <25000000>; }; &gmac1 { @@ -51,10 +109,27 @@ txc-skew-ps = <2600>; rxdv-skew-ps = <0>; rxc-skew-ps = <2000>; + max-frame-size = <3800>; }; &i2c0 { status = "okay"; + speed-mode = <0>; + + /* + * adjust the falling times to decrease the i2c frequency to 50Khz + * because the LCD module does not work at the standard 100Khz + */ + i2c-sda-falling-time-ns = <5000>; + i2c-scl-falling-time-ns = <5000>; + + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; eeprom@51 { compatible = "atmel,24c32"; @@ -71,3 +146,45 @@ &usb1 { status = "okay"; }; + +&qspi { + status = "okay"; + flash0: n25q00@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "n25q00"; + reg = <0>; /* chip select */ + spi-max-frequency = <100000000>; + m25p,fast-read; + page-size = <256>; + block-size = <16>; /* 2^16, 64KB */ + read-delay = <4>; /* delay value in read data capture register */ + tshsl-ns = <50>; + tsd2d-ns = <50>; + tchsh-ns = <4>; + tslch-ns = <4>; + + partition@qspi-boot { + /* 8MB for raw data. */ + label = "Flash 0 Raw Data"; + reg = <0x0 0x800000>; + }; + partition@qspi-rootfs { + /* 120MB for jffs2 data. */ + label = "Flash 0 jffs2 Filesystem"; + reg = <0x800000 0x7800000>; + }; + }; +}; + +&spi0 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&watchdog0 { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_sockit.dts b/arch/arm/boot/dts/socfpga_cyclone5_sockit.dts index d26f155f5fd9f..afd59669e05bf 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5_sockit.dts +++ b/arch/arm/boot/dts/socfpga_cyclone5_sockit.dts @@ -39,6 +39,10 @@ }; }; +&osc1 { + clock-frequency = <25000000>; +}; + &gmac1 { status = "okay"; phy-mode = "rgmii"; @@ -56,3 +60,33 @@ &usb1 { status = "okay"; }; + +&qspi { + flash0: n25q00@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "n25q00"; + reg = <0>; /* chip select */ + spi-max-frequency = <100000000>; + m25p,fast-read; + page-size = <256>; + block-size = <16>; /* 2^16, 64KB */ + read-delay = <4>; /* delay value in read data capture register */ + tshsl-ns = <50>; + tsd2d-ns = <50>; + tchsh-ns = <4>; + tslch-ns = <4>; + + partition@qspi-boot { + /* 8MB for raw data. */ + label = "Flash 0 Raw Data"; + reg = <0x0 0x800000>; + }; + + partition@qspi-rootfs { + /* 120MB for jffs2 data. */ + label = "Flash 0 jffs2 Filesystem"; + reg = <0x800000 0x7800000>; + }; + }; +}; diff --git a/arch/arm/boot/dts/socfpga_vt.dts b/arch/arm/boot/dts/socfpga_vt.dts index 09792b4111105..8c40909dee1d3 100644 --- a/arch/arm/boot/dts/socfpga_vt.dts +++ b/arch/arm/boot/dts/socfpga_vt.dts @@ -1,18 +1,17 @@ /* - * Copyright (C) 2013 Altera Corporation + * Copyright Altera Corporation (C) 2012-2014. All rights reserved. * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . */ /dts-v1/; @@ -61,6 +60,35 @@ clock-frequency = <7000000>; }; + qspi: spi@ff705000 { + status = "okay"; + flash0: n25q128@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "n25q128"; + reg = <0>; /* chip select */ + spi-max-frequency = <100000000>; + page-size = <256>; + block-size = <16>; /* 2^16, 64KB */ + quad = <1>; /* 1-support quad */ + tshsl-ns = <200>; + tsd2d-ns = <255>; + tchsh-ns = <20>; + tslch-ns = <20>; + + partition@0 { + /* 8MB for raw data. */ + label = "Flash 0 Raw Data"; + reg = <0x0 0x800000>; + }; + partition@800000 { + /* 8MB for jffs2 data. */ + label = "Flash 0 jffs2 Filesystem"; + reg = <0x800000 0x800000>; + }; + }; + }; + timer1@ffc09000 { clock-frequency = <7000000>; }; diff --git a/arch/arm/configs/socfpga_defconfig b/arch/arm/configs/socfpga_defconfig index e3a05e8801d8f..a55bb539efadc 100644 --- a/arch/arm/configs/socfpga_defconfig +++ b/arch/arm/configs/socfpga_defconfig @@ -41,7 +41,27 @@ CONFIG_IP_PNP_DHCP=y CONFIG_IP_PNP_BOOTP=y CONFIG_IP_PNP_RARP=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_CAN=y +CONFIG_CAN_RAW=y +CONFIG_CAN_BCM=y +CONFIG_CAN_GW=y +CONFIG_CAN_DEV=y +CONFIG_CAN_CALC_BITTIMING=y +CONFIG_CAN_C_CAN=y +CONFIG_CAN_C_CAN_PLATFORM=y +CONFIG_CAN_DEBUG_DEVICES=y CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_MTD=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_M25P80=y +CONFIG_MTD_NAND_ECC=y +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_DENALI=y +CONFIG_MTD_NAND_DENALI_DT=y +CONFIG_MTD_NAND_IDS=y +CONFIG_MTD_SPI_NOR=y CONFIG_PROC_DEVICETREE=y CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_COUNT=2 @@ -63,15 +83,43 @@ CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=2 CONFIG_SERIAL_8250_RUNTIME_UARTS=2 CONFIG_SERIAL_8250_DW=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_STORAGE=y +CONFIG_USB_GADGET=y +CONFIG_USB_LIBCOMPOSITE=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_PHY=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_GENERIC_PHY=y +CONFIG_USB_DWC2=y +CONFIG_MMC=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_IDMAC=y +CONFIG_SPI=y +CONFIG_SPI_CADENCE_QSPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_PL330_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_ALTERA=m # CONFIG_RTC_HCTOSYS is not set +CONFIG_WATCHDOG=y +CONFIG_DW_WATCHDOG=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT3_FS=y -CONFIG_NFS_FS=y -CONFIG_ROOT_NFS=y +CONFIG_EXT3_DEFAULTS_TO_ORDERED=y +CONFIG_EXT3_FS_XATTR=y +CONFIG_EXT4_FS=y +# CONFIG_EXT3_FS_POSIX_ACL is not set +# CONFIG_EXT3_FS_SECURITY is not set # CONFIG_DNOTIFY is not set -# CONFIG_INOTIFY_USER is not set +CONFIG_INOTIFY_USER=y CONFIG_VFAT_FS=y CONFIG_NTFS_FS=y CONFIG_NTFS_RW=y @@ -86,5 +134,51 @@ CONFIG_DEBUG_INFO=y CONFIG_ENABLE_DEFAULT_TRACERS=y CONFIG_DEBUG_USER=y CONFIG_XZ_DEC=y -CONFIG_MMC=y -CONFIG_MMC_DW=y +CONFIG_I2C=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_I2C_CHARDEV=y +CONFIG_NEWHAVEN_LCD=y +CONFIG_EEPROM_AT24=y +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V3=y +CONFIG_NFS_V4=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_KERNEL_DNS=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_CPU=y +CONFIG_FPGA=y +CONFIG_FPGA_MGR_ALTERA=y +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_COMMON_CLK_DEBUG=y +CONFIG_FRAME_POINTER=y +CONFIG_GENERIC_TRACER=y +CONFIG_FUNCTION_TRACER=y +CONFIG_FUNCTION_GRAPH_TRACER=y +CONFIG_DYNAMIC_FTRACE=y +CONFIG_FTRACE_MCOUNT_RECORD=y +CONFIG_OLD_MCOUNT=y +CONFIG_FPGA_BRIDGE=y +CONFIG_ALTERA_SOCFPGA_BRIDGE=y +CONFIG_SIGNALFD=y +CONFIG_VLAN_8021Q=y +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_GARP=y +CONFIG_IPV6=y +CONFIG_CONFIGFS_FS=y +CONFIG_SRAM=y +CONFIG_PPS=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_PTP_1588_CLOCK=y +CONFIG_MARVELL_PHY=y diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h index ae1919be8f988..37a7cd3943fe1 100644 --- a/arch/arm/include/asm/pmu.h +++ b/arch/arm/include/asm/pmu.h @@ -38,6 +38,9 @@ struct arm_pmu_platdata { irq_handler_t pmu_handler); int (*runtime_resume)(struct device *dev); int (*runtime_suspend)(struct device *dev); + int (*init)(struct platform_device *pdev); + int (*start)(struct platform_device *pdev); + int (*stop)(struct platform_device *pdev); }; #ifdef CONFIG_HW_PERF_EVENTS diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c index 4238bcba9d60f..c8be84b71f13b 100644 --- a/arch/arm/kernel/perf_event.c +++ b/arch/arm/kernel/perf_event.c @@ -325,6 +325,11 @@ static irqreturn_t armpmu_dispatch_irq(int irq, void *dev) static void armpmu_release_hardware(struct arm_pmu *armpmu) { + struct platform_device *plat_device = armpmu->plat_device; + struct arm_pmu_platdata *plat = dev_get_platdata(&plat_device->dev); + + if (plat->stop) + plat->stop(plat_device); armpmu->free_irq(armpmu); pm_runtime_put_sync(&armpmu->plat_device->dev); } @@ -334,6 +339,7 @@ armpmu_reserve_hardware(struct arm_pmu *armpmu) { int err; struct platform_device *pmu_device = armpmu->plat_device; + struct arm_pmu_platdata *plat = dev_get_platdata(&pmu_device->dev); if (!pmu_device) return -ENODEV; @@ -344,6 +350,8 @@ armpmu_reserve_hardware(struct arm_pmu *armpmu) armpmu_release_hardware(armpmu); return err; } + if (plat->start) + plat->start(pmu_device); return 0; } @@ -526,10 +534,18 @@ static void armpmu_init(struct arm_pmu *armpmu) int armpmu_register(struct arm_pmu *armpmu, int type) { + struct platform_device *plat_device = armpmu->plat_device; + struct arm_pmu_platdata *plat = dev_get_platdata(&plat_device->dev); + armpmu_init(armpmu); pm_runtime_enable(&armpmu->plat_device->dev); pr_info("enabled with %s PMU driver, %d counters available\n", armpmu->name, armpmu->num_events); + + /* Platform specific initialization. ie. CTI enable */ + if (plat->init) + plat->init(plat_device); + return perf_pmu_register(&armpmu->pmu, armpmu->name, type); } diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig index b5f8d75d51a05..01f6d53c40e62 100644 --- a/arch/arm/mach-socfpga/Kconfig +++ b/arch/arm/mach-socfpga/Kconfig @@ -8,3 +8,20 @@ config ARCH_SOCFPGA select HAVE_ARM_SCU select HAVE_ARM_TWD if SMP select MFD_SYSCON + select MIGHT_HAVE_CACHE_L2X0 + select SPARSE_IRQ + select USE_OF + select SOC_BUS + select ARM_ERRATA_754322 + select ARM_ERRATA_764369 if SMP + select ARM_ERRATA_775420 + select PL310_ERRATA_588369 + select PL310_ERRATA_727915 + select PL310_ERRATA_753970 if PL310 + select PL310_ERRATA_769419 + +config FPGADMA + tristate "FPGA DMA FIFO driver" + depends on DMA_ENGINE + help + Sample FPGA DMA driver, for testing with special FPGA FIFO image diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile index 6dd7a93a90fea..36d508ce59b01 100644 --- a/arch/arm/mach-socfpga/Makefile +++ b/arch/arm/mach-socfpga/Makefile @@ -4,3 +4,8 @@ obj-y := socfpga.o obj-$(CONFIG_SMP) += headsmp.o platsmp.o +obj-$(CONFIG_HW_PERF_EVENTS) += socfpga_cti.o +obj-$(CONFIG_FPGADMA) += fpga-dma.o +obj-$(CONFIG_EDAC_ALTERA_OCRAM_ECC) += ocram.o +obj-$(CONFIG_EDAC_ALTERA_L2_ECC) += l2_cache.o + diff --git a/arch/arm/mach-socfpga/core.h b/arch/arm/mach-socfpga/core.h index 572b8f719ffbf..a09be4472e291 100644 --- a/arch/arm/mach-socfpga/core.h +++ b/arch/arm/mach-socfpga/core.h @@ -31,8 +31,20 @@ extern void socfpga_secondary_startup(void); extern void __iomem *socfpga_scu_base_addr; -extern void socfpga_init_clocks(void); -extern void socfpga_sysmgr_init(void); +/*MPU Module Reset Register */ +#define RSTMGR_MPUMODRST_CPU0 0x1 /*CPU0 Reset*/ +#define RSTMGR_MPUMODRST_CPU1 0x2 /*CPU1 Reset*/ +#define RSTMGR_MPUMODRST_WDS 0x4 /*Watchdog Reset*/ +#define RSTMGR_MPUMODRST_SCUPER 0x8 /*SCU and periphs reset*/ +#define RSTMGR_MPUMODRST_L2 0x10 /*L2 Cache reset*/ +#define SOCFPGA_ID_DEFAULT 0x1 +#define SOCFPGA_REVISION_DEFAULT 0x1 + +#define SYSMGR_SILICON_ID1_OFFSET 0x0 +#define SYSMGR_SILICON_ID1_REV_SHIFT 0 +#define SYSMGR_SILICON_ID1_REV_MASK 0x0000FFFF +#define SYSMGR_SILICON_ID1_ID_SHIFT 16 +#define SYSMGR_SILICON_ID1_ID_MASK 0xFFFF0000 extern void __iomem *sys_manager_base_addr; extern void __iomem *rst_manager_base_addr; @@ -42,6 +54,9 @@ extern char secondary_trampoline, secondary_trampoline_end; extern unsigned long cpu1start_addr; -#define SOCFPGA_SCU_VIRT_BASE 0xfffec000 +#define SOCFPGA_SCU_VIRT_BASE 0xfee00000 + +/* Clock manager defines */ +#define SOCFPGA_ENABLE_PLL_REG 0xA0 #endif diff --git a/arch/arm/mach-socfpga/fpga-dma.c b/arch/arm/mach-socfpga/fpga-dma.c new file mode 100644 index 0000000000000..23ed0a0d8e28f --- /dev/null +++ b/arch/arm/mach-socfpga/fpga-dma.c @@ -0,0 +1,689 @@ +/* + * FPGA DMA transfer module + * + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************/ + +static unsigned int max_burst_words = 16; +module_param(max_burst_words, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_burst_words, "Size of a burst in words " + "(in this case a word is 64 bits)"); + +static int timeout = 1000; +module_param(timeout, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(timeout, "Transfer Timeout in msec (default: 1000), " + "Pass -1 for infinite timeout"); + +#define ALT_FPGADMA_DATA_WRITE 0x00 +#define ALT_FPGADMA_DATA_READ 0x08 + +#define ALT_FPGADMA_CSR_WR_WTRMK 0x00 +#define ALT_FPGADMA_CSR_RD_WTRMK 0x04 +#define ALT_FPGADMA_CSR_BURST 0x08 +#define ALT_FPGADMA_CSR_FIFO_STATUS 0x0C +#define ALT_FPGADMA_CSR_DATA_WIDTH 0x10 +#define ALT_FPGADMA_CSR_FIFO_DEPTH 0x14 +#define ALT_FPGADMA_CSR_FIFO_CLEAR 0x18 +#define ALT_FPGADMA_CSR_ZERO 0x1C + +#define ALT_FPGADMA_CSR_BURST_TX_SINGLE (1 << 0) +#define ALT_FPGADMA_CSR_BURST_TX_BURST (1 << 1) +#define ALT_FPGADMA_CSR_BURST_RX_SINGLE (1 << 2) +#define ALT_FPGADMA_CSR_BURST_RX_BURST (1 << 3) + +#define ALT_FPGADMA_FIFO_FULL (1 << 25) +#define ALT_FPGADMA_FIFO_EMPTY (1 << 24) +#define ALT_FPGADMA_FIFO_USED_MASK ((1 << 24)-1) + +struct fpga_dma_pdata { + + struct platform_device *pdev; + + struct dentry *root; + + unsigned int data_reg_phy; + void __iomem *data_reg; + void __iomem *csr_reg; + + unsigned int fifo_size_bytes; + unsigned int fifo_depth; + unsigned int data_width; + unsigned int data_width_bytes; + unsigned char *read_buf; + unsigned char *write_buf; + + struct dma_chan *txchan; + struct dma_chan *rxchan; + dma_addr_t tx_dma_addr; + dma_addr_t rx_dma_addr; + dma_cookie_t rx_cookie; + dma_cookie_t tx_cookie; +}; + +static DECLARE_COMPLETION(dma_read_complete); +static DECLARE_COMPLETION(dma_write_complete); + +#define IS_DMA_READ (true) +#define IS_DMA_WRITE (false) + +static int fpga_dma_dma_start_rx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size); +static int fpga_dma_dma_start_tx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size); + +/* --------------------------------------------------------------------- */ + +static void dump_csr(struct fpga_dma_pdata *pdata) +{ + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_WR_WTRMK %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_RD_WTRMK %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_BURST %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_BURST)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_FIFO_STATUS %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_STATUS)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_DATA_WIDTH %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_DATA_WIDTH)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_FIFO_DEPTH %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_DEPTH)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_ZERO %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_ZERO)); +} + +/* --------------------------------------------------------------------- */ + +static void recalc_burst_and_words(struct fpga_dma_pdata *pdata, + int *burst_size, int *num_words) +{ + /* adjust size and maxburst so that total bytes transferred + is a multiple of burst length and width */ + if (*num_words < max_burst_words) { + /* we have only a few words left, make it our burst size */ + *burst_size = *num_words; + } else { + /* here we may not transfer all words to FIFO, but next + call will pick them up... */ + *num_words = max_burst_words * (*num_words / max_burst_words); + *burst_size = max_burst_words; + } +} + +static int word_to_bytes(struct fpga_dma_pdata *pdata, int num_bytes) +{ + return (num_bytes + pdata->data_width_bytes - 1) + / pdata->data_width_bytes; +} + +static ssize_t dbgfs_write_dma(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + int ret = 0; + int bytes_to_transfer; + int num_words; + u32 burst_size; + int pad_index; + + *ppos = 0; + + /* get user data into kernel buffer */ + bytes_to_transfer = simple_write_to_buffer(pdata->write_buf, + pdata->fifo_size_bytes, ppos, + user_buf, count); + pad_index = bytes_to_transfer; + + num_words = word_to_bytes(pdata, bytes_to_transfer); + recalc_burst_and_words(pdata, &burst_size, &num_words); + /* we sometimes send more than asked for, padded with zeros */ + bytes_to_transfer = num_words * pdata->data_width_bytes; + for (; pad_index < bytes_to_transfer; pad_index++) + pdata->write_buf[pad_index] = 0; + + ret = fpga_dma_dma_start_tx(pdata->pdev, + bytes_to_transfer, pdata->write_buf, + burst_size); + if (ret) { + dev_err(&pdata->pdev->dev, "Error starting TX DMA %d\n", ret); + return ret; + } + + if (!wait_for_completion_timeout(&dma_write_complete, + msecs_to_jiffies(timeout))) { + dev_err(&pdata->pdev->dev, "Timeout waiting for TX DMA!\n"); + dev_err(&pdata->pdev->dev, + "count %d burst_size %d num_words %d bytes_to_transfer %d\n", + count, burst_size, num_words, bytes_to_transfer); + dmaengine_terminate_all(pdata->txchan); + return -ETIMEDOUT; + } + + return bytes_to_transfer; +} + +static ssize_t dbgfs_read_dma(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + int ret; + int num_words; + int num_bytes; + u32 burst_size; + + num_words = readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_STATUS); + num_words &= ALT_FPGADMA_FIFO_USED_MASK; + + num_bytes = num_words * pdata->data_width_bytes; + if (num_bytes > count) { + dev_dbg(&pdata->pdev->dev, + "dbgfs_read_dma num_bytes %d > count %d\n", + num_bytes, count); + num_bytes = count; + num_words = num_bytes / (pdata->data_width_bytes); + } + if (num_bytes > pdata->fifo_size_bytes) { + dev_dbg(&pdata->pdev->dev, + "dbgfs_read_dma num_bytes %d > pdata->fifo_size_bytes %d\n", + num_bytes, pdata->fifo_size_bytes); + num_bytes = pdata->fifo_size_bytes; + num_words = num_bytes / (pdata->data_width_bytes); + } + + recalc_burst_and_words(pdata, &burst_size, &num_words); + num_bytes = num_words * pdata->data_width_bytes; + + if (num_bytes > 0) { + ret = fpga_dma_dma_start_rx(pdata->pdev, num_bytes, + pdata->read_buf, burst_size); + if (ret) { + dev_err(&pdata->pdev->dev, + "Error starting RX DMA %d\n", ret); + return ret; + } + + if (!wait_for_completion_timeout(&dma_read_complete, + msecs_to_jiffies(timeout))) { + dev_err(&pdata->pdev->dev, + "Timeout waiting for RX DMA!\n"); + dmaengine_terminate_all(pdata->rxchan); + return -ETIMEDOUT; + } + *ppos = 0; + } + return simple_read_from_buffer(user_buf, count, ppos, + pdata->read_buf, num_bytes); +} + +static const struct file_operations dbgfs_dma_fops = { + .write = dbgfs_write_dma, + .read = dbgfs_read_dma, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_read_csr(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + dump_csr(pdata); + return 0; +} + +static const struct file_operations dbgfs_csr_fops = { + .read = dbgfs_read_csr, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_clear(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + writel(1, pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_CLEAR); + return count; +} + +static const struct file_operations dbgfs_clear_fops = { + .write = dbgfs_write_clear, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_wrwtrmk(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + char buf[32]; + unsigned long val; + int ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, user_buf, min(count, (sizeof(buf) - 1)))) + return -EFAULT; + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + writel(val, pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK); + return count; +} + +static const struct file_operations dbgfs_wrwtrmk_fops = { + .write = dbgfs_write_wrwtrmk, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_rdwtrmk(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + char buf[32]; + int ret; + unsigned long val; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, user_buf, min(count, (sizeof(buf) - 1)))) + return -EFAULT; + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + writel(val, pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK); + return count; +} + +static const struct file_operations dbgfs_rdwtrmk_fops = { + .write = dbgfs_write_rdwtrmk, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static int fpga_dma_register_dbgfs(struct fpga_dma_pdata *pdata) +{ + struct dentry *d; + + d = debugfs_create_dir("fpga_dma", NULL); + if (IS_ERR(d)) + return PTR_ERR(d); + if (!d) { + dev_err(&pdata->pdev->dev, "Failed to initialize debugfs\n"); + return -ENOMEM; + } + + pdata->root = d; + + debugfs_create_file("dma", S_IWUSR | S_IRUGO, pdata->root, pdata, + &dbgfs_dma_fops); + + debugfs_create_file("csr", S_IRUGO, pdata->root, pdata, + &dbgfs_csr_fops); + + debugfs_create_file("clear", S_IWUSR, pdata->root, pdata, + &dbgfs_clear_fops); + + debugfs_create_file("wrwtrmk", S_IWUSR, pdata->root, pdata, + &dbgfs_wrwtrmk_fops); + + debugfs_create_file("rdwtrmk", S_IWUSR, pdata->root, pdata, + &dbgfs_rdwtrmk_fops); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void fpga_dma_dma_rx_done(void *arg) +{ + complete(&dma_read_complete); +} + +static void fpga_dma_dma_tx_done(void *arg) +{ + complete(&dma_write_complete); +} + +static void fpga_dma_dma_cleanup(struct platform_device *pdev, + unsigned datalen, bool do_read) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + if (do_read) + dma_unmap_single(&pdev->dev, pdata->rx_dma_addr, + datalen, DMA_FROM_DEVICE); + else + dma_unmap_single(&pdev->dev, pdata->tx_dma_addr, + datalen, DMA_TO_DEVICE); +} + +static int fpga_dma_dma_start_rx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + struct dma_chan *dmachan; + struct dma_slave_config dmaconf; + struct dma_async_tx_descriptor *dmadesc = NULL; + + int num_words; + + num_words = word_to_bytes(pdata, datalen); + + dmachan = pdata->rxchan; + memset(&dmaconf, 0, sizeof(dmaconf)); + dmaconf.direction = DMA_DEV_TO_MEM; + dmaconf.src_addr = pdata->data_reg_phy + ALT_FPGADMA_DATA_READ; + dmaconf.src_addr_width = 8; + dmaconf.src_maxburst = burst_size; + + pdata->rx_dma_addr = dma_map_single(&pdev->dev, + databuf, datalen, DMA_FROM_DEVICE); + if (dma_mapping_error(&pdev->dev, pdata->rx_dma_addr)) { + dev_err(&pdev->dev, "dma_map_single for RX failed\n"); + return -EINVAL; + } + + /* set up slave config */ + dmaengine_slave_config(dmachan, &dmaconf); + + /* get dmadesc */ + dmadesc = dmaengine_prep_slave_single(dmachan, + pdata->rx_dma_addr, + datalen, + dmaconf.direction, + DMA_PREP_INTERRUPT); + if (!dmadesc) { + fpga_dma_dma_cleanup(pdev, datalen, IS_DMA_READ); + return -ENOMEM; + } + dmadesc->callback = fpga_dma_dma_rx_done; + dmadesc->callback_param = pdata; + + /* start DMA */ + pdata->rx_cookie = dmaengine_submit(dmadesc); + if (dma_submit_error(pdata->rx_cookie)) + dev_err(&pdev->dev, "rx_cookie error on dmaengine_submit\n"); + dma_async_issue_pending(dmachan); + + return 0; +} + +static int fpga_dma_dma_start_tx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + struct dma_chan *dmachan; + struct dma_slave_config dmaconf; + struct dma_async_tx_descriptor *dmadesc = NULL; + + int num_words; + + num_words = word_to_bytes(pdata, datalen); + + dmachan = pdata->txchan; + memset(&dmaconf, 0, sizeof(dmaconf)); + dmaconf.direction = DMA_MEM_TO_DEV; + dmaconf.dst_addr = pdata->data_reg_phy + ALT_FPGADMA_DATA_WRITE; + dmaconf.dst_addr_width = 8; + dmaconf.dst_maxburst = burst_size; + pdata->tx_dma_addr = dma_map_single(&pdev->dev, + databuf, datalen, DMA_TO_DEVICE); + if (dma_mapping_error(&pdev->dev, pdata->tx_dma_addr)) { + dev_err(&pdev->dev, "dma_map_single for TX failed\n"); + return -EINVAL; + } + + /* set up slave config */ + dmaengine_slave_config(dmachan, &dmaconf); + + /* get dmadesc */ + dmadesc = dmaengine_prep_slave_single(dmachan, + pdata->tx_dma_addr, + datalen, + dmaconf.direction, + DMA_PREP_INTERRUPT); + if (!dmadesc) { + fpga_dma_dma_cleanup(pdev, datalen, IS_DMA_WRITE); + return -ENOMEM; + } + dmadesc->callback = fpga_dma_dma_tx_done; + dmadesc->callback_param = pdata; + + /* start DMA */ + pdata->tx_cookie = dmaengine_submit(dmadesc); + if (dma_submit_error(pdata->tx_cookie)) + dev_err(&pdev->dev, "tx_cookie error on dmaengine_submit\n"); + dma_async_issue_pending(dmachan); + + return 0; +} + +static void fpga_dma_dma_shutdown(struct fpga_dma_pdata *pdata) +{ + if (pdata->txchan) { + dmaengine_terminate_all(pdata->txchan); + dma_release_channel(pdata->txchan); + } + if (pdata->rxchan) { + dmaengine_terminate_all(pdata->rxchan); + dma_release_channel(pdata->rxchan); + } + pdata->rxchan = pdata->txchan = NULL; +} + +static int fpga_dma_dma_init(struct fpga_dma_pdata *pdata) +{ + struct platform_device *pdev = pdata->pdev; + + pdata->txchan = dma_request_slave_channel(&pdev->dev, "tx"); + if (pdata->txchan) + dev_dbg(&pdev->dev, "TX channel %s %d selected\n", + dma_chan_name(pdata->txchan), pdata->txchan->chan_id); + else + dev_err(&pdev->dev, "could not get TX dma channel\n"); + + pdata->rxchan = dma_request_slave_channel(&pdev->dev, "rx"); + if (pdata->rxchan) + dev_dbg(&pdev->dev, "RX channel %s %d selected\n", + dma_chan_name(pdata->rxchan), pdata->rxchan->chan_id); + else + dev_err(&pdev->dev, "could not get RX dma channel\n"); + + if (!pdata->rxchan && !pdata->txchan) + /* both channels not there, maybe it's + bcs dma isn't loaded... */ + return -EPROBE_DEFER; + + if (!pdata->rxchan || !pdata->txchan) + return -ENOMEM; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void __iomem *request_and_map(struct platform_device *pdev, + const struct resource *res) +{ + void __iomem *ptr; + + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) { + dev_err(&pdev->dev, "unable to request %s\n", res->name); + return NULL; + } + + ptr = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res)); + if (!ptr) + dev_err(&pdev->dev, "ioremap_nocache of %s failed!", res->name); + + return ptr; +} + +static int fpga_dma_remove(struct platform_device *pdev) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + dev_dbg(&pdev->dev, "fpga_dma_remove\n"); + debugfs_remove_recursive(pdata->root); + fpga_dma_dma_shutdown(pdata); + return 0; +} + +static int fpga_dma_probe(struct platform_device *pdev) +{ + struct resource *csr_reg, *data_reg; + struct fpga_dma_pdata *pdata; + int ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(struct fpga_dma_pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + csr_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr"); + data_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "data"); + if (!csr_reg || !data_reg) { + dev_err(&pdev->dev, "registers not completely defined\n"); + return -EINVAL; + } + + pdata->csr_reg = request_and_map(pdev, csr_reg); + if (!pdata->csr_reg) + return -ENOMEM; + + pdata->data_reg = request_and_map(pdev, data_reg); + if (!pdata->data_reg) + return -ENOMEM; + pdata->data_reg_phy = data_reg->start; + + /* read HW and calculate fifo size in bytes */ + pdata->fifo_depth = readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_DEPTH); + pdata->data_width = readl(pdata->csr_reg + ALT_FPGADMA_CSR_DATA_WIDTH); + /* 64-bit bus to FIFO */ + pdata->data_width_bytes = pdata->data_width / sizeof(u64); + pdata->fifo_size_bytes = pdata->fifo_depth * pdata->data_width_bytes; + + pdata->read_buf = devm_kzalloc(&pdev->dev, pdata->fifo_size_bytes, + GFP_KERNEL); + if (!pdata->read_buf) + return -ENOMEM; + + pdata->write_buf = devm_kzalloc(&pdev->dev, pdata->fifo_size_bytes, + GFP_KERNEL); + if (!pdata->write_buf) + return -ENOMEM; + + ret = fpga_dma_register_dbgfs(pdata); + if (ret) + return ret; + + pdata->pdev = pdev; + platform_set_drvdata(pdev, pdata); + + ret = fpga_dma_dma_init(pdata); + if (ret) { + fpga_dma_remove(pdev); + return ret; + } + + /* OK almost ready, set up the watermarks */ + /* we may need to tweak this for single/burst, etc */ + writel(pdata->fifo_depth - max_burst_words, + pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK); + /* we use read watermark of 0 so that rx_burst line + is always asserted, i.e. no single-only requests */ + writel(0, pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id fpga_dma_of_match[] = { + {.compatible = "altr,fpga-dma",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, fpga_dma_of_match); +#endif + +static struct platform_driver fpga_dma_driver = { + .probe = fpga_dma_probe, + .remove = fpga_dma_remove, + .driver = { + .name = "fpga_dma", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(fpga_dma_of_match), + }, +}; + +static int __init fpga_dma_init(void) +{ + return platform_driver_probe(&fpga_dma_driver, fpga_dma_probe); +} + +static void __exit fpga_dma_exit(void) +{ + platform_driver_unregister(&fpga_dma_driver); +} + +late_initcall(fpga_dma_init); +module_exit(fpga_dma_exit); + +MODULE_AUTHOR("Graham Moore (Altera)"); +MODULE_DESCRIPTION("Altera FPGA DMA Example Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-socfpga/headsmp.S b/arch/arm/mach-socfpga/headsmp.S index 95c115d8b5eee..42b77e3a10ebb 100644 --- a/arch/arm/mach-socfpga/headsmp.S +++ b/arch/arm/mach-socfpga/headsmp.S @@ -9,21 +9,21 @@ */ #include #include +#include .arch armv7-a ENTRY(secondary_trampoline) - movw r2, #:lower16:cpu1start_addr - movt r2, #:upper16:cpu1start_addr - - /* The socfpga VT cannot handle a 0xC0000000 page offset when loading - the cpu1start_addr, we bit clear it. Tested on HW and VT. */ - bic r2, r2, #0x40000000 - - ldr r0, [r2] - ldr r1, [r0] - bx r1 + adr r0, 1f + ldmia r0, {r1, r2} + sub r2, r2, #PAGE_OFFSET + ldr r3, [r2] + ldr r4, [r3] + bx r4 + .align +1: .long . + .long cpu1start_addr ENTRY(secondary_trampoline_end) ENTRY(socfpga_secondary_startup) diff --git a/arch/arm/mach-socfpga/l2_cache.c b/arch/arm/mach-socfpga/l2_cache.c new file mode 100644 index 0000000000000..a2e3bb6ff433f --- /dev/null +++ b/arch/arm/mach-socfpga/l2_cache.c @@ -0,0 +1,44 @@ +/* + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include + +void socfpga_init_l2_ecc(void) +{ + struct device_node *np; + void __iomem *mapped_l2_edac_addr; + + np = of_find_compatible_node(NULL, NULL, "altr,l2-edac"); + if (!np) { + pr_err("SOCFPGA: Unable to find altr,l2-edac in dtb\n"); + return; + } + + mapped_l2_edac_addr = of_iomap(np, 0); + if (!mapped_l2_edac_addr) { + pr_err("SOCFPGA: Unable to find L2 ECC mapping in dtb\n"); + return; + } + + /* Enable ECC */ + writel(0x01, mapped_l2_edac_addr); + + pr_alert("SOCFPGA: Success Initializing L2 cache ECC"); + + return; +} + diff --git a/arch/arm/mach-socfpga/l2_cache.h b/arch/arm/mach-socfpga/l2_cache.h new file mode 100644 index 0000000000000..de84d0d6b8b8c --- /dev/null +++ b/arch/arm/mach-socfpga/l2_cache.h @@ -0,0 +1,28 @@ +/* + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef MACH_SOCFPGA_L2_CACHE_H +#define MACH_SOCFPGA_L2_CACHE_H + +#ifdef CONFIG_EDAC_ALTERA_L2_ECC +void socfpga_init_l2_ecc(void); +#else +inline void socfpga_init_l2_ecc(void) +{ +} +#endif + +#endif /* #ifndef MACH_SOCFPGA_L2_CACHE_H */ diff --git a/arch/arm/mach-socfpga/ocram.c b/arch/arm/mach-socfpga/ocram.c new file mode 100644 index 0000000000000..861332c4660a9 --- /dev/null +++ b/arch/arm/mach-socfpga/ocram.c @@ -0,0 +1,84 @@ +/* + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include + +void socfpga_init_ocram_ecc(void) +{ + struct device_node *np; + const __be32 *prop; + u32 ocr_edac_addr, iram_addr, len; + void __iomem *mapped_ocr_edac_addr; + size_t size; + struct gen_pool *gp; + + np = of_find_compatible_node(NULL, NULL, "altr,ocram-edac"); + if (!np) { + pr_err("SOCFPGA: Unable to find altr,ocram-edac in dtb\n"); + return; + } + + prop = of_get_property(np, "reg", &size); + ocr_edac_addr = be32_to_cpup(prop++); + len = be32_to_cpup(prop); + if (!prop || size < sizeof(*prop)) { + pr_err("SOCFPGA: Unable to find OCRAM ECC mapping in dtb\n"); + return; + } + + gp = of_get_named_gen_pool(np, "iram", 0); + if (!gp) { + pr_err("SOCFPGA: OCRAM cannot find gen pool\n"); + return; + } + + np = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!np) { + pr_err("SOCFPGA: Unable to find mmio-sram in dtb\n"); + return; + } + /* Determine the OCRAM address and size */ + prop = of_get_property(np, "reg", &size); + iram_addr = be32_to_cpup(prop++); + len = be32_to_cpup(prop); + + if (!prop || size < sizeof(*prop)) { + pr_err("SOCFPGA: Unable to find OCRAM mapping in dtb\n"); + return; + } + + iram_addr = gen_pool_alloc(gp, len); + if (iram_addr == 0) { + pr_err("SOCFPGA: cannot alloc from gen pool\n"); + return; + } + + memset((void *)iram_addr, 0, len); + + mapped_ocr_edac_addr = ioremap(ocr_edac_addr, 4); + + gen_pool_free(gp, iram_addr, len); + + /* Clear any pending OCRAM ECC interrupts, then enable ECC */ + writel(0x18, mapped_ocr_edac_addr); + writel(0x19, mapped_ocr_edac_addr); + + pr_alert("SOCFPGA: Success Initializing OCRAM"); + + return; +} + diff --git a/arch/arm/mach-socfpga/ocram.h b/arch/arm/mach-socfpga/ocram.h new file mode 100644 index 0000000000000..c5c107084b222 --- /dev/null +++ b/arch/arm/mach-socfpga/ocram.h @@ -0,0 +1,28 @@ +/* + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef MACH_SOCFPGA_OCRAM_H +#define MACH_SOCFPGA_OCRAM_H + +#ifdef CONFIG_EDAC_ALTERA_OCRAM_ECC +void socfpga_init_ocram_ecc(void); +#else +inline void socfpga_init_ocram_ecc(void) +{ +} +#endif + +#endif /* #ifndef MACH_SOCFPGA_OCRAM_H */ diff --git a/arch/arm/mach-socfpga/platsmp.c b/arch/arm/mach-socfpga/platsmp.c index 5356a72bc8ce9..1d5f8adef4cbd 100644 --- a/arch/arm/mach-socfpga/platsmp.c +++ b/arch/arm/mach-socfpga/platsmp.c @@ -34,6 +34,10 @@ static int socfpga_boot_secondary(unsigned int cpu, struct task_struct *idle) int trampoline_size = &secondary_trampoline_end - &secondary_trampoline; if (cpu1start_addr) { + /* This will put CPU #1 into reset.*/ + __raw_writel(RSTMGR_MPUMODRST_CPU1, + rst_manager_base_addr + 0x10); + memcpy(phys_to_virt(0), &secondary_trampoline, trampoline_size); __raw_writel(virt_to_phys(socfpga_secondary_startup), @@ -86,10 +90,12 @@ static void __init socfpga_smp_prepare_cpus(unsigned int max_cpus) */ static void socfpga_cpu_die(unsigned int cpu) { - cpu_do_idle(); + /* Flush the L1 data cache. */ + flush_cache_all(); - /* We should have never returned from idle */ - panic("cpu %d unexpectedly exit from shutdown\n", cpu); + /* Do WFI. If we wake up early, go back into WFI */ + while (1) + cpu_do_idle(); } struct smp_operations socfpga_smp_ops __initdata = { diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c index adbf38314ca8c..88e1887246100 100644 --- a/arch/arm/mach-socfpga/socfpga.c +++ b/arch/arm/mach-socfpga/socfpga.c @@ -19,17 +19,42 @@ #include #include #include +#include +#include #include #include #include +#include #include "core.h" +#include "socfpga_cti.h" +#include "l2_cache.h" +#include "ocram.h" void __iomem *socfpga_scu_base_addr = ((void __iomem *)(SOCFPGA_SCU_VIRT_BASE)); void __iomem *sys_manager_base_addr; void __iomem *rst_manager_base_addr; unsigned long cpu1start_addr; +void __iomem *sdr_ctl_base_addr; +void __iomem *l3regs_base_addr; +void __iomem *clkmgr_base_addr; + +#ifdef CONFIG_HW_PERF_EVENTS +static struct arm_pmu_platdata socfpga_pmu_platdata = { + .handle_irq = socfpga_pmu_handler, + .init = socfpga_init_cti, + .start = socfpga_start_cti, + .stop = socfpga_stop_cti, +}; +#endif + +static const struct of_dev_auxdata socfpga_auxdata_lookup[] __initconst = { +#ifdef CONFIG_HW_PERF_EVENTS + OF_DEV_AUXDATA("arm,cortex-a9-pmu", 0, "arm-pmu", &socfpga_pmu_platdata), +#endif + { /* sentinel */ } +}; static struct map_desc scu_io_desc __initdata = { .virtual = SOCFPGA_SCU_VIRT_BASE, @@ -45,6 +70,57 @@ static struct map_desc uart_io_desc __initdata = { .type = MT_DEVICE, }; +static void __init socfpga_soc_device_init(void) +{ + struct device_node *root; + struct soc_device *soc_dev; + struct soc_device_attribute *soc_dev_attr; + const char *machine; + u32 id = SOCFPGA_ID_DEFAULT; + u32 rev = SOCFPGA_REVISION_DEFAULT; + int err; + + root = of_find_node_by_path("/"); + if (!root) + return; + + err = of_property_read_string(root, "model", &machine); + if (err) + return; + + of_node_put(root); + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + return; + + /* Read Silicon ID from System manager */ + if (sys_manager_base_addr) { + id = __raw_readl(sys_manager_base_addr + + SYSMGR_SILICON_ID1_OFFSET); + rev = (id & SYSMGR_SILICON_ID1_REV_MASK) + >> SYSMGR_SILICON_ID1_REV_SHIFT; + id = (id & SYSMGR_SILICON_ID1_ID_MASK) + >> SYSMGR_SILICON_ID1_ID_SHIFT; + } + + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%u", id); + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d", rev); + soc_dev_attr->machine = kasprintf(GFP_KERNEL, "%s", machine); + soc_dev_attr->family = "SOCFPGA"; + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR_OR_NULL(soc_dev)) { + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->machine); + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr); + return; + } + + return; +} + static void __init socfpga_scu_map_io(void) { unsigned long base; @@ -56,6 +132,12 @@ static void __init socfpga_scu_map_io(void) iotable_init(&scu_io_desc, 1); } +static void __init enable_periphs(void) +{ + /* Release all peripherals from reset.*/ + __raw_writel(0, rst_manager_base_addr + SOCFPGA_RSTMGR_MODPERRST); +} + static void __init socfpga_map_io(void) { socfpga_scu_map_io(); @@ -77,18 +159,45 @@ void __init socfpga_sysmgr_init(void) np = of_find_compatible_node(NULL, NULL, "altr,rst-mgr"); rst_manager_base_addr = of_iomap(np, 0); + WARN_ON(!rst_manager_base_addr); + + np = of_find_compatible_node(NULL, NULL, "altr,clk-mgr"); + clkmgr_base_addr = of_iomap(np, 0); + WARN_ON(!clkmgr_base_addr); + + np = of_find_compatible_node(NULL, NULL, "altr,sdr-ctl"); + if (!np) { + pr_err("SOCFPGA: Unable to find sdr-ctl\n"); + return; + } + + sdr_ctl_base_addr = of_iomap(np, 0); + WARN_ON(!sdr_ctl_base_addr); + + np = of_find_compatible_node(NULL, NULL, "altr,l3regs"); + if (!np) { + pr_err("SOCFPGA: Unable to find l3regs\n"); + return; + } + + l3regs_base_addr = of_iomap(np, 0); + WARN_ON(!l3regs_base_addr); } static void __init socfpga_init_irq(void) { irqchip_init(); socfpga_sysmgr_init(); + socfpga_init_l2_ecc(); } static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd) { u32 temp; + /* Turn on all periph PLL clocks */ + writel(0xffff, clkmgr_base_addr + SOCFPGA_ENABLE_PLL_REG); + temp = readl(rst_manager_base_addr + SOCFPGA_RSTMGR_CTRL); if (mode == REBOOT_HARD) @@ -98,17 +207,29 @@ static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd) writel(temp, rst_manager_base_addr + SOCFPGA_RSTMGR_CTRL); } +static void __init socfpga_cyclone5_init(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, + socfpga_auxdata_lookup, NULL); + enable_periphs(); + socfpga_soc_device_init(); + socfpga_init_ocram_ecc(); +} + static const char *altera_dt_match[] = { "altr,socfpga", NULL }; DT_MACHINE_START(SOCFPGA, "Altera SOCFPGA") - .l2c_aux_val = 0, + .l2c_aux_val = L310_AUX_CTRL_DATA_PREFETCH | + L310_AUX_CTRL_INSTR_PREFETCH | + L2C_AUX_CTRL_SHARED_OVERRIDE, .l2c_aux_mask = ~0, .smp = smp_ops(socfpga_smp_ops), .map_io = socfpga_map_io, .init_irq = socfpga_init_irq, + .init_machine = socfpga_cyclone5_init, .restart = socfpga_cyclone5_restart, .dt_compat = altera_dt_match, MACHINE_END diff --git a/arch/arm/mach-socfpga/socfpga_cti.c b/arch/arm/mach-socfpga/socfpga_cti.c new file mode 100644 index 0000000000000..72ef9a494fdc9 --- /dev/null +++ b/arch/arm/mach-socfpga/socfpga_cti.c @@ -0,0 +1,143 @@ +/* + * Copyright Altera Corporation (C) 2013-2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "socfpga_cti.h" + +#define SOCFPGA_MAX_NUM_CTI 8 + +struct socfpga_cti { + struct cti socfpga_cti_data; + void __iomem *cti_addr; + int irq; +}; + +struct socfpga_cti_device { + struct device *dev; + struct socfpga_cti *socfpga_cti[SOCFPGA_MAX_NUM_CTI]; + int socfpga_ncores; +}; + +irqreturn_t socfpga_pmu_handler(int irq, void *dev, irq_handler_t handler) +{ + struct arm_pmu *armpmu = (struct arm_pmu *)dev; + struct platform_device *pdev = armpmu->plat_device; + struct socfpga_cti_device *socfpga_cti_device = + platform_get_drvdata(pdev); + int ncores = socfpga_cti_device->socfpga_ncores; + struct cti *cti; + int irq_local; + unsigned int handled = 0; + int i; + + for (i = 0; i < ncores; i++) { + irq_local = socfpga_cti_device->socfpga_cti[i]->irq; + cti = &socfpga_cti_device->socfpga_cti[i]->socfpga_cti_data; + if (irq == irq_local) + cti_irq_ack(cti); + } + + handled = handler(irq, dev); + + return IRQ_RETVAL(handled); +} + +int socfpga_init_cti(struct platform_device *pdev) +{ + struct device_node *np, *np2; + struct socfpga_cti_device *socfpga_cti_device; + struct socfpga_cti *socfpga_cti, *socfpga_cti_error; + void __iomem *cti_addr; + int ncores; + int i; + + socfpga_cti_device = devm_kzalloc(&pdev->dev, + sizeof(*socfpga_cti_device), + GFP_KERNEL); + if (!socfpga_cti_device) + return -ENOMEM; + + ncores = num_online_cpus(); + socfpga_cti_device->socfpga_ncores = ncores; + + socfpga_cti_device->dev = &pdev->dev; + + np = pdev->dev.of_node; + + socfpga_cti = kzalloc(sizeof(socfpga_cti) * ncores, GFP_KERNEL); + if (!socfpga_cti) + return -ENOMEM; + socfpga_cti_error = socfpga_cti; + + for (i = 0; i < socfpga_cti_device->socfpga_ncores; i++) { + np2 = of_find_compatible_node(np, NULL, "arm,coresight-cti"); + + cti_addr = of_iomap(np2, 0); + if (!cti_addr) { + dev_err(&pdev->dev, "PMU: ioremap for CTI failed\n"); + kfree(socfpga_cti_error); + return -ENOMEM; + } + socfpga_cti->cti_addr = cti_addr; + + socfpga_cti->irq = platform_get_irq(pdev, i); + + /*configure CTI0 for pmu irq routing*/ + cti_init(&socfpga_cti->socfpga_cti_data, socfpga_cti->cti_addr, + socfpga_cti->irq, CTI_MPU_IRQ_TRIG_OUT); + cti_unlock(&socfpga_cti->socfpga_cti_data); + cti_map_trigger(&socfpga_cti->socfpga_cti_data, + CTI_MPU_IRQ_TRIG_IN, CTI_MPU_IRQ_TRIG_OUT, + PMU_CHANNEL_0); + + socfpga_cti_device->socfpga_cti[i] = socfpga_cti; + socfpga_cti += sizeof(socfpga_cti); + } + platform_set_drvdata(pdev, socfpga_cti_device); + + dev_info(&pdev->dev, "PMU:CTI successfully enabled for %d cores\n", + socfpga_cti_device->socfpga_ncores); + return 0; +} + +int socfpga_start_cti(struct platform_device *pdev) +{ + struct socfpga_cti_device *cti_dev = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < cti_dev->socfpga_ncores; i++) + cti_enable(&cti_dev->socfpga_cti[i]->socfpga_cti_data); + + return 0; +} + +int socfpga_stop_cti(struct platform_device *pdev) +{ + struct socfpga_cti_device *cti_dev = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < cti_dev->socfpga_ncores; i++) + cti_disable(&cti_dev->socfpga_cti[i]->socfpga_cti_data); + + return 0; +} diff --git a/arch/arm/mach-socfpga/socfpga_cti.h b/arch/arm/mach-socfpga/socfpga_cti.h new file mode 100644 index 0000000000000..efabc3c172468 --- /dev/null +++ b/arch/arm/mach-socfpga/socfpga_cti.h @@ -0,0 +1,32 @@ +/* + * Copyright Altera Corporation (C) 2013-2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __SOCFPGA_CTI_H +#define __SOCFPGA_CTI_H + +#define CTI_MPU_IRQ_TRIG_IN 1 +#define CTI_MPU_IRQ_TRIG_OUT 6 + +#define PMU_CHANNEL_0 0 +#define PMU_CHANNEL_1 1 + +#ifdef CONFIG_HW_PERF_EVENTS +extern irqreturn_t socfpga_pmu_handler(int irq, void *dev, irq_handler_t handler); +extern int socfpga_init_cti(struct platform_device *pdev); +extern int socfpga_start_cti(struct platform_device *pdev); +extern int socfpga_stop_cti(struct platform_device *pdev); +#endif +#endif /* __SOCFPGA_CTI_H */ diff --git a/arch/nios2/Kconfig b/arch/nios2/Kconfig new file mode 100644 index 0000000000000..6c906a1435159 --- /dev/null +++ b/arch/nios2/Kconfig @@ -0,0 +1,217 @@ +config NIOS2 + def_bool y + select OF + select OF_EARLY_FLATTREE + select HAVE_OPROFILE + select HAVE_ARCH_KGDB + select USB_ARCH_HAS_HCD if USB_SUPPORT + select ARCH_WANT_OPTIONAL_GPIOLIB + select GENERIC_IRQ_PROBE + select GENERIC_IRQ_SHOW + select GENERIC_CPU_DEVICES + select GENERIC_ATOMIC64 + select MODULES_USE_ELF_RELA + select IRQ_DOMAIN + select SOC_BUS + select OLD_SIGACTION + select OLD_SIGSUSPEND + select CLKSRC_OF + +config GENERIC_CSUM + def_bool y + +config GENERIC_FIND_NEXT_BIT + def_bool y + +config GENERIC_HWEIGHT + def_bool y + +config GENERIC_CALIBRATE_DELAY + def_bool y + + +config NO_IOPORT_MAP + def_bool y + +config HAS_DMA + def_bool y + +config ZONE_DMA + def_bool y + +config FPU + def_bool n + +config SWAP + def_bool n + +config RWSEM_GENERIC_SPINLOCK + def_bool y + +config TRACE_IRQFLAGS_SUPPORT + def_bool n + +source "init/Kconfig" + +menu "Kernel features" + +source "kernel/Kconfig.preempt" + +source "kernel/Kconfig.freezer" + +source "kernel/Kconfig.hz" + +source "mm/Kconfig" + +config FORCE_MAX_ZONEORDER + int "Maximum zone order" + range 9 20 + default "11" + help + The kernel memory allocator divides physically contiguous memory + blocks into "zones", where each zone is a power of two number of + pages. This option selects the largest power of two that the kernel + keeps in the memory allocator. If you need to allocate very large + blocks of physically contiguous memory, then you may need to + increase this value. + + This config option is actually maximum order plus one. For example, + a value of 11 means that the largest free memory block is 2^10 pages. + +endmenu + +source "arch/nios2/platform/Kconfig.platform" + +menu "Processor type and features" + +config MMU + def_bool y + +config ALIGNMENT_TRAP + bool "Catch alignment trap" + default y + help + Nios II CPUs cannot fetch/store data which is not bus aligned, + i.e., a 2 or 4 byte fetch must start at an address divisible by + 2 or 4. Any non-aligned load/store instructions will be trapped and + emulated in software if you say Y here, which has a performance + impact. + +comment "Boot options" + +config CMDLINE_BOOL + bool "Default bootloader kernel arguments" + default y + +config CMDLINE + string "Default kernel command string" + default "" + depends on CMDLINE_BOOL + help + On some platforms, there is currently no way for the boot loader to + pass arguments to the kernel. For these platforms, you can supply + some command-line options at build time by entering them here. In + other cases you can specify kernel args so that you don't have + to set them up in board prom initialization routines. + +config CMDLINE_FORCE + bool "Force default kernel command string" + depends on CMDLINE_BOOL + help + Set this to have arguments from the default kernel command string + override those passed by the boot loader. + +config CMDLINE_IGNORE_DTB + bool "Ignore kernel command string from DTB" + depends on !CMDLINE_FORCE + default y + help + Set this to ignore the bootargs property from the devicetree's + chosen node and fall back to CMDLINE if nothing is passed. + +config PASS_CMDLINE + bool "Passed kernel command line from u-boot" + default n + help + Use bootargs env variable from u-boot for kernel command line. + will override "Default kernel command string". + Say N if you are unsure. + +config BOOT_LINK_OFFSET + hex "Link address offset for booting" + default "0x00500000" + help + This option allows you to set the link address offset of the zImage. + This can be useful if you are on a board which has a small amount of + memory. + +endmenu + +menu "Advanced setup" + +config ADVANCED_OPTIONS + bool "Prompt for advanced kernel configuration options" + help + +comment "Default settings for advanced configuration options are used" + depends on !ADVANCED_OPTIONS + +config KERNEL_MMU_REGION_BASE_BOOL + bool "Set custom kernel MMU region base address" + depends on ADVANCED_OPTIONS + help + This option allows you to set the virtual address of the kernel MMU region. + + Say N here unless you know what you are doing. + +config KERNEL_MMU_REGION_BASE + hex "Virtual base address of the kernel MMU region " if KERNEL_MMU_REGION_BASE_BOOL + default "0x80000000" + help + This option allows you to set the virtual base address of the kernel MMU region. + +config KERNEL_REGION_BASE_BOOL + bool "Set custom kernel region base address" + depends on ADVANCED_OPTIONS + help + This option allows you to set the virtual address of the kernel region. + + Say N here unless you know what you are doing. + +config KERNEL_REGION_BASE + hex "Virtual base address of the kernel region " if KERNEL_REGION_BASE_BOOL + default "0xc0000000" + +config IO_REGION_BASE_BOOL + bool "Set custom I/O region base address" + depends on ADVANCED_OPTIONS + help + This option allows you to set the virtual address of the I/O region. + + Say N here unless you know what you are doing. + +config IO_REGION_BASE + hex "Virtual base address of the I/O region " if IO_REGION_BASE_BOOL + default "0xe0000000" + +endmenu + +menu "Executable file formats" + +source "fs/Kconfig.binfmt" + +endmenu + +source "net/Kconfig" + +source "drivers/Kconfig" + +source "fs/Kconfig" + +source "arch/nios2/Kconfig.debug" + +source "security/Kconfig" + +source "crypto/Kconfig" + +source "lib/Kconfig" diff --git a/arch/nios2/Kconfig.debug b/arch/nios2/Kconfig.debug new file mode 100644 index 0000000000000..de063bd1e7326 --- /dev/null +++ b/arch/nios2/Kconfig.debug @@ -0,0 +1,28 @@ +menu "Kernel hacking" + +config TRACE_IRQFLAGS_SUPPORT + def_bool y + +source "lib/Kconfig.debug" + +config EARLY_PRINTK + bool "Activate early kernel debugging" + default y + select SERIAL_CORE_CONSOLE + depends on SERIAL_ALTERA_JTAGUART_CONSOLE || SERIAL_ALTERA_UART_CONSOLE + help + Enable early printk on console + This is useful for kernel debugging when your machine crashes very + early before the console code is initialized. + You should normally say N here, unless you want to debug such a crash. + +config DEBUG_STACK_USAGE + bool "Enable stack utilization instrumentation" + depends on DEBUG_KERNEL + help + Enables the display of the minimum amount of free stack which each + task has ever had available in the sysrq-T and sysrq-P debug output. + + This option will slow down process creation somewhat. + +endmenu diff --git a/arch/nios2/Makefile b/arch/nios2/Makefile new file mode 100644 index 0000000000000..7a74aedeeb23e --- /dev/null +++ b/arch/nios2/Makefile @@ -0,0 +1,82 @@ +# +# This file is subject to the terms and conditions of the GNU General Public +# License. See the file "COPYING" in the main directory of this archive +# for more details. +# +# Copyright (C) 2013 Altera Corporation +# Copyright (C) 1994, 95, 96, 2003 by Wind River Systems +# Written by Fredrik Markstrom +# +# This file is included by the global makefile so that you can add your own +# architecture-specific flags and dependencies. Remember to do have actions +# for "archclean" cleaning up for this architecture. +# +# Nios2 port by Wind River Systems Inc trough: +# fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + +UTS_SYSNAME = Linux + +export MMU + +cflags-y := +LDFLAGS := +LDFLAGS_vmlinux := + +LIBGCC := $(shell $(CC) $(KBUILD_CFLAGS) -print-libgcc-file-name) + +KBUILD_AFLAGS += $(cflags-y) +KBUILD_CFLAGS += -pipe -D__linux__ -D__ELF__ $(cflags-y) +KBUILD_CFLAGS += $(if $(CONFIG_NIOS2_HW_MUL_SUPPORT),-mhw-mul,-mno-hw-mul) +KBUILD_CFLAGS += $(if $(CONFIG_NIOS2_HW_MULX_SUPPORT),-mhw-mulx,-mno-hw-mulx) +KBUILD_CFLAGS += $(if $(CONFIG_NIOS2_HW_DIV_SUPPORT),-mhw-div,-mno-hw-div) +KBUILD_CFLAGS += $(if $(CONFIG_NIOS2_FPU_SUPPORT),-mcustom-fpu-cfg=60-1,) + +KBUILD_CFLAGS += -fno-optimize-sibling-calls +KBUILD_CFLAGS += -DUTS_SYSNAME=\"$(UTS_SYSNAME)\" +KBUILD_CFLAGS += -fno-builtin +KBUILD_CFLAGS += -G 0 + +LDFLAGS += -mnios2elf + +head-y := arch/nios2/kernel/head.o +libs-y += arch/nios2/lib/ $(LIBGCC) +core-y += arch/nios2/kernel/ arch/nios2/mm/ +core-y += arch/nios2/platform/ + +drivers-$(CONFIG_OPROFILE) += arch/$(ARCH)/oprofile/ + +INSTALL_PATH ?= /tftpboot +boot := arch/$(ARCH)/boot +BOOT_TARGETS = vmImage zImage +PHONY += $(BOOT_TARGETS) install +KBUILD_IMAGE := $(boot)/vmImage + +ifneq ($(CONFIG_DTB_SOURCE),"") + core-y += $(boot)/ +endif + +all: vmImage + +archclean: + $(Q)$(MAKE) $(clean)=$(boot) + +%.dtb: + $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ + +dtbs: + $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ + +$(BOOT_TARGETS): vmlinux + $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@ + +install: + $(Q)$(MAKE) $(build)=$(boot) BOOTIMAGE=$(KBUILD_IMAGE) install + +define archhelp + echo '* vmImage - Kernel-only image for U-Boot (arch/$(ARCH)/boot/vmImage)' + echo ' install - Install kernel using' + echo ' (your) ~/bin/$(CROSS_COMPILE)installkernel or' + echo ' (distribution) PATH: $(CROSS_COMPILE)installkernel or' + echo ' install to $$(INSTALL_PATH)' + echo ' dtbs - Build device tree blobs for enabled boards' +endef diff --git a/arch/nios2/boot/Makefile b/arch/nios2/boot/Makefile new file mode 100644 index 0000000000000..cf1ee5008305d --- /dev/null +++ b/arch/nios2/boot/Makefile @@ -0,0 +1,58 @@ +# +# arch/nios2/boot/Makefile +# +# This file is subject to the terms and conditions of the GNU General Public +# License. See the file "COPYING" in the main directory of this archive +# for more details. +# + +UIMAGE_LOADADDR = $(shell $(NM) vmlinux | awk '$$NF == "_stext" {print $$1}') +UIMAGE_ENTRYADDR = $(shell $(NM) vmlinux | awk '$$NF == "_start" {print $$1}') +UIMAGE_COMPRESSION = gzip + +OBJCOPYFLAGS_vmlinux.bin := -O binary +$(obj)/vmlinux.bin: vmlinux FORCE + $(call if_changed,objcopy) + +$(obj)/vmlinux.gz: $(obj)/vmlinux.bin FORCE + $(call if_changed,gzip) + +$(obj)/vmImage: $(obj)/vmlinux.gz + $(call if_changed,uimage) + @$(kecho) 'Kernel: $@ is ready' + +$(obj)/zImage: $(obj)/compressed/vmlinux FORCE + $(call if_changed,objcopy) + @$(kecho) 'Kernel: $@ is ready' + +$(obj)/compressed/vmlinux: $(obj)/vmlinux.gz FORCE + $(Q)$(MAKE) $(build)=$(obj)/compressed $@ + +# Rule to build device tree blobs +DTB_SRC := $(subst ",,$(CONFIG_DTB_SOURCE)) + +# Make sure the generated dtb gets removed during clean +extra-$(CONFIG_DTB_SOURCE_BOOL) += system.dtb + +$(obj)/system.dtb: $(DTB_SRC) FORCE + $(call cmd,dtc) + +# Ensure system.dtb exists +$(obj)/linked_dtb.o: $(obj)/system.dtb + +#ifneq ($(CONFIG_DTB_SOURCE),"") +obj-$(CONFIG_DTB_SOURCE_BOOL) += linked_dtb.o +#endif + +targets += $(dtb-y) + +# Rule to build device tree blobs with make command +$(obj)/%.dtb: $(src)/dts/%.dts FORCE + $(call if_changed_dep,dtc) + +$(obj)/dtbs: $(addprefix $(obj)/, $(dtb-y)) + +clean-files := *.dtb + +install: + sh $(srctree)/$(src)/install.sh $(KERNELRELEASE) $(BOOTIMAGE) System.map "$(INSTALL_PATH)" diff --git a/arch/nios2/boot/compressed/Makefile b/arch/nios2/boot/compressed/Makefile new file mode 100644 index 0000000000000..665d53599fd6b --- /dev/null +++ b/arch/nios2/boot/compressed/Makefile @@ -0,0 +1,21 @@ +# +# linux/arch/nios2/boot/compressed/Makefile +# +# create a compressed vmlinux image from the original vmlinux +# + +targets := vmlinux head.o misc.o piggy.o vmlinux.lds +asflags-y := + +OBJECTS = $(obj)/head.o $(obj)/misc.o + +LDFLAGS_vmlinux := -T + +$(obj)/vmlinux: $(obj)/vmlinux.lds $(OBJECTS) $(obj)/piggy.o FORCE + $(call if_changed,ld) + @: + +LDFLAGS_piggy.o := -r --format binary --oformat elf32-littlenios2 -T + +$(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/../vmlinux.gz FORCE + $(call if_changed,ld) diff --git a/arch/nios2/boot/compressed/console.c b/arch/nios2/boot/compressed/console.c new file mode 100644 index 0000000000000..aa05724a0b238 --- /dev/null +++ b/arch/nios2/boot/compressed/console.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008-2010 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +#if (defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE) && defined(JTAG_UART_BASE))\ + || (defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE) && defined(UART0_BASE)) +static void *my_ioremap(unsigned long physaddr) +{ + return (void *)(physaddr | CONFIG_IO_REGION_BASE); +} +#endif + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE) && defined(JTAG_UART_BASE) + +#define ALTERA_JTAGUART_SIZE 8 +#define ALTERA_JTAGUART_DATA_REG 0 +#define ALTERA_JTAGUART_CONTROL_REG 4 +#define ALTERA_JTAGUART_CONTROL_AC_MSK (0x00000400) +#define ALTERA_JTAGUART_CONTROL_WSPACE_MSK (0xFFFF0000) +static unsigned uartbase; + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS) +static void jtag_putc(int ch) +{ + if (readl(uartbase + ALTERA_JTAGUART_CONTROL_REG) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) + writeb(ch, uartbase + ALTERA_JTAGUART_DATA_REG); +} +#else +static void jtag_putc(int ch) +{ + while ((readl(uartbase + ALTERA_JTAGUART_CONTROL_REG) & + ALTERA_JTAGUART_CONTROL_WSPACE_MSK) == 0) + ; + writeb(ch, uartbase + ALTERA_JTAGUART_DATA_REG); +} +#endif + +static int putchar(int ch) +{ + jtag_putc(ch); + return ch; +} + +static void console_init(void) +{ + uartbase = (unsigned long) my_ioremap((unsigned long) JTAG_UART_BASE); + writel(ALTERA_JTAGUART_CONTROL_AC_MSK, + uartbase + ALTERA_JTAGUART_CONTROL_REG); +} + +#elif defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE) && defined(UART0_BASE) + +#define ALTERA_UART_SIZE 32 +#define ALTERA_UART_TXDATA_REG 4 +#define ALTERA_UART_STATUS_REG 8 +#define ALTERA_UART_DIVISOR_REG 16 +#define ALTERA_UART_STATUS_TRDY_MSK (0x40) +static unsigned uartbase; + +static void uart_putc(int ch) +{ + int i; + + for (i = 0; (i < 0x10000); i++) { + if (readw(uartbase + ALTERA_UART_STATUS_REG) & + ALTERA_UART_STATUS_TRDY_MSK) + break; + } + writeb(ch, uartbase + ALTERA_UART_TXDATA_REG); +} + +static int putchar(int ch) +{ + uart_putc(ch); + if (ch == '\n') + uart_putc('\r'); + return ch; +} + +static void console_init(void) +{ + unsigned int baud, baudclk; + + uartbase = (unsigned long) my_ioremap((unsigned long) UART0_BASE); + baud = CONFIG_SERIAL_ALTERA_UART_BAUDRATE; + baudclk = UART0_FREQ / baud; + writew(baudclk, uartbase + ALTERA_UART_DIVISOR_REG); +} + +#else + +static int putchar(int ch) +{ + return ch; +} + +static void console_init(void) +{ +} + +#endif + +static int puts(const char *s) +{ + while (*s) + putchar(*s++); + return 0; +} diff --git a/arch/nios2/boot/compressed/head.S b/arch/nios2/boot/compressed/head.S new file mode 100644 index 0000000000000..0658081d15fc3 --- /dev/null +++ b/arch/nios2/boot/compressed/head.S @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * Based on arch/nios2/kernel/head.S + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +/* + * This code can be loaded anywhere, eg FLASH ROM as reset vector, + * as long as output does not overlap it. + */ + +#include +#include + + .text + .set noat +ENTRY(_start) + wrctl status, r0 /* disable interrupt */ + /* invalidate all instruction cache */ + movia r1, NIOS2_ICACHE_SIZE + movui r2, NIOS2_ICACHE_LINE_SIZE +1: initi r1 + sub r1, r1, r2 + bgt r1, r0, 1b + /* invalidate all data cache */ + movia r1, NIOS2_DCACHE_SIZE + movui r2, NIOS2_DCACHE_LINE_SIZE +1: initd 0(r1) + sub r1, r1, r2 + bgt r1, r0, 1b + + nextpc r1 /* Find out where we are */ +chkadr: + movia r2, chkadr + beq r1, r2, finish_move /* We are running in correct address, + done */ + /* move code, r1: src, r2: dest, r3: last dest */ + addi r1, r1, (_start - chkadr) /* Source */ + movia r2, _start /* Destination */ + movia r3, __bss_start /* End of copy */ +1: ldw r8, 0(r1) /* load a word from [r1] */ + stw r8, 0(r2) /* stort a word to dest [r2] */ + addi r1, r1, 4 /* inc the src addr */ + addi r2, r2, 4 /* inc the dest addr */ + blt r2, r3, 1b + /* flush the data cache after moving */ + movia r1, NIOS2_DCACHE_SIZE + movui r2, NIOS2_DCACHE_LINE_SIZE +1: flushd 0(r1) + sub r1, r1, r2 + bgt r1, r0, 1b + movia r1, finish_move + jmp r1 /* jmp to linked address */ + +finish_move: + /* zero out the .bss segment (uninitialized common data) */ + movia r2, __bss_start /* presume nothing is between */ + movia r1, _end /* the .bss and _end. */ +1: stb r0, 0(r2) + addi r2, r2, 1 + bne r1, r2, 1b + /* + * set up the stack pointer, some where higher than _end. + * The stack space must be greater than 32K for decompress. + */ + movia sp, 0x10000 + add sp, sp, r1 + /* save args passed from u-boot, maybe */ + addi sp, sp, -16 + stw r4, 0(sp) + stw r5, 4(sp) + stw r6, 8(sp) + stw r7, 12(sp) + /* decompress the kernel */ + call decompress_kernel + /* pass saved args to kernel */ + ldw r4, 0(sp) + ldw r5, 4(sp) + ldw r6, 8(sp) + ldw r7, 12(sp) + + /* flush all data cache after decompressing */ + movia r1, NIOS2_DCACHE_SIZE + movui r2, NIOS2_DCACHE_LINE_SIZE +1: flushd 0(r1) + sub r1, r1, r2 + bgt r1, r0, 1b + /* flush all instruction cache */ + movia r1, NIOS2_ICACHE_SIZE + movui r2, NIOS2_ICACHE_LINE_SIZE +1: flushi r1 + sub r1, r1, r2 + bgt r1, r0, 1b + flushp + /* jump to start real kernel */ + movia r1, (CONFIG_MEM_BASE | CONFIG_KERNEL_REGION_BASE) + jmp r1 + + .balign 512 +fake_headers_as_bzImage: + .short 0 + .ascii "HdrS" + .short 0x0202 + .short 0 + .short 0 + .byte 0x00, 0x10 + .short 0 + .byte 0 + .byte 1 + .byte 0x00, 0x80 + .long 0 + .long 0 diff --git a/arch/nios2/boot/compressed/misc.c b/arch/nios2/boot/compressed/misc.c new file mode 100644 index 0000000000000..8de1fb80663f6 --- /dev/null +++ b/arch/nios2/boot/compressed/misc.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * This is a collection of several routines from gzip-1.0.3 + * adapted for Linux. + * + * malloc by Hannu Savolainen 1993 and Matthias Urlichs 1994 + * + * Adapted for SH by Stuart Menefy, Aug 1999 + * + * Modified to use standard LinuxSH BIOS by Greg Banks 7Jul2000 + * + * Based on arch/sh/boot/compressed/misc.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +/* + * gzip declarations + */ +#define OF(args) args +#define STATIC static + +#undef memset +#undef memcpy +#define memzero(s, n) memset((s), 0, (n)) + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; +#define WSIZE 0x8000 /* Window size must be at least 32k, */ + /* and a power of two */ + +static uch *inbuf; /* input buffer */ +static uch window[WSIZE]; /* Sliding window buffer */ + +static unsigned insize; /* valid bytes in inbuf */ +static unsigned inptr; /* index of next byte to be processed in inbuf */ +static unsigned outcnt; /* bytes in output buffer */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ASCII text */ +#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip + file */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define ENCRYPTED 0x20 /* bit 5 set: file is encrypted */ +#define RESERVED 0xC0 /* bit 6,7: reserved */ + +#define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf()) + +#ifdef DEBUG +# define Assert(cond, msg) {if (!(cond)) error(msg); } +# define Trace(x) fprintf x +# define Tracev(x) {if (verbose) fprintf x ; } +# define Tracevv(x) {if (verbose > 1) fprintf x ; } +# define Tracec(c, x) {if (verbose && (c)) fprintf x ; } +# define Tracecv(c, x) {if (verbose > 1 && (c)) fprintf x ; } +#else +# define Assert(cond, msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c, x) +# define Tracecv(c, x) +#endif +static int fill_inbuf(void); +static void flush_window(void); +static void error(char *m); + +extern char input_data[]; +extern int input_len; + +static long bytes_out; +static uch *output_data; +static unsigned long output_ptr; + +#include "console.c" + +static void error(char *m); + +int puts(const char *); + +extern int _end; +static unsigned long free_mem_ptr; +static unsigned long free_mem_end_ptr; + +#define HEAP_SIZE 0x10000 + +#include "../../../../lib/inflate.c" + +void *memset(void *s, int c, size_t n) +{ + int i; + char *ss = (char *)s; + + for (i = 0; i < n; i++) + ss[i] = c; + return s; +} + +void *memcpy(void *__dest, __const void *__src, size_t __n) +{ + int i; + char *d = (char *)__dest, *s = (char *)__src; + + for (i = 0; i < __n; i++) + d[i] = s[i]; + return __dest; +} + +/* + * Fill the input buffer. This is called only when the buffer is empty + * and at least one byte is really needed. + */ +static int fill_inbuf(void) +{ + if (insize != 0) + error("ran out of input data"); + + inbuf = input_data; + insize = input_len; + inptr = 1; + return inbuf[0]; +} + +/* + * Write the output window window[0..outcnt-1] and update crc and bytes_out. + * (Used for the decompressed data only.) + */ +static void flush_window(void) +{ + ulg c = crc; /* temporary variable */ + unsigned n; + uch *in, *out, ch; + in = window; + out = &output_data[output_ptr]; + for (n = 0; n < outcnt; n++) { + ch = *out++ = *in++; + c = crc_32_tab[((int)c ^ ch) & 0xff] ^ (c >> 8); + } + crc = c; + bytes_out += (ulg)outcnt; + output_ptr += (ulg)outcnt; + outcnt = 0; +} + +static void error(char *x) +{ + puts("\nERROR\n"); + puts(x); + puts("\n\n -- System halted"); + + while (1) /* Halt */ + ; +} + +void decompress_kernel(void) +{ + output_data = (void *) (CONFIG_MEM_BASE | CONFIG_KERNEL_REGION_BASE); + output_ptr = 0; + free_mem_ptr = (unsigned long)&_end; + free_mem_end_ptr = free_mem_ptr + HEAP_SIZE; + + console_init(); + makecrc(); + puts("Uncompressing Linux... "); + gunzip(); + puts("Ok, booting the kernel.\n"); +} diff --git a/arch/nios2/boot/compressed/vmlinux.lds.S b/arch/nios2/boot/compressed/vmlinux.lds.S new file mode 100644 index 0000000000000..7a76cda990896 --- /dev/null +++ b/arch/nios2/boot/compressed/vmlinux.lds.S @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +OUTPUT_FORMAT("elf32-littlenios2", "elf32-littlenios2", "elf32-littlenios2") + +OUTPUT_ARCH(nios) +ENTRY(_start) /* Defined in head.S */ + +SECTIONS +{ + . = (CONFIG_MEM_BASE + CONFIG_BOOT_LINK_OFFSET) | \ + CONFIG_KERNEL_REGION_BASE; + + _text = .; + .text : { *(.text) } = 0 + .rodata : { *(.rodata) *(.rodata.*) } + _etext = .; + + . = ALIGN(32 / 8); + .data : { *(.data) } + . = ALIGN(32 / 8); + _got = .; + .got : { *(.got) _egot = .; *(.got.*) } + _edata = .; + + . = ALIGN(32 / 8); + __bss_start = .; + .bss : { *(.bss) *(.sbss) } + . = ALIGN(32 / 8); + _ebss = .; + end = . ; + _end = . ; + + got_len = (_egot - _got); +} diff --git a/arch/nios2/boot/compressed/vmlinux.scr b/arch/nios2/boot/compressed/vmlinux.scr new file mode 100644 index 0000000000000..28c42f1d127ee --- /dev/null +++ b/arch/nios2/boot/compressed/vmlinux.scr @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +SECTIONS +{ + .data : { + input_len = .; + LONG(input_data_end - input_data) input_data = .; + *(.data) + . = ALIGN(4); + input_data_end = .; + } +} diff --git a/arch/nios2/boot/dts/3c120_devboard.dts b/arch/nios2/boot/dts/3c120_devboard.dts new file mode 100644 index 0000000000000..cad29a9d409c4 --- /dev/null +++ b/arch/nios2/boot/dts/3c120_devboard.dts @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is generated by sopc2dts. + */ + +/dts-v1/; + +/ { + model = "ALTR,qsys_ghrd_3c120"; + compatible = "ALTR,qsys_ghrd_3c120"; + #address-cells = < 1 >; + #size-cells = < 1 >; + + cpus { + #address-cells = < 1 >; + #size-cells = < 0 >; + + cpu: cpu@0x0 { + device_type = "cpu"; + compatible = "ALTR,nios2-1.0"; + reg = < 0x00000000 >; + interrupt-controller; + #interrupt-cells = < 1 >; + clock-frequency = < 125000000 >; + dcache-line-size = < 32 >; + icache-line-size = < 32 >; + dcache-size = < 32768 >; + icache-size = < 32768 >; + ALTR,implementation = "fast"; + ALTR,pid-num-bits = < 8 >; + ALTR,tlb-num-ways = < 16 >; + ALTR,tlb-num-entries = < 128 >; + ALTR,tlb-ptr-sz = < 7 >; + ALTR,has-div = < 1 >; + ALTR,has-mul = < 1 >; + ALTR,reset-addr = < 0xc2800000 >; + ALTR,fast-tlb-miss-addr = < 0xc7fff400 >; + ALTR,exception-addr = < 0xd0000020 >; + ALTR,has-initda = < 1 >; + ALTR,has-mmu = < 1 >; + }; //end cpu@0x0 (cpu) + }; //end cpus + + memory@0 { + device_type = "memory"; + reg = < 0x10000000 0x08000000 + 0x07FFF400 0x00000400 >; + }; //end memory@0 + + sopc@0 { + device_type = "soc"; + ranges; + #address-cells = < 1 >; + #size-cells = < 1 >; + compatible = "ALTR,avalon", "simple-bus"; + bus-frequency = < 125000000 >; + + pb_cpu_to_io: bridge@0x8000000 { + compatible = "simple-bus"; + reg = < 0x08000000 0x00800000 >; + #address-cells = < 1 >; + #size-cells = < 1 >; + ranges = < 0x00400000 0x08400000 0x00000020 + 0x00004D40 0x08004D40 0x00000008 + 0x00004D50 0x08004D50 0x00000008 + 0x00004000 0x08004000 0x00000400 + 0x00004400 0x08004400 0x00000040 + 0x00004800 0x08004800 0x00000040 + 0x00002000 0x08002000 0x00002000 + 0x00004C80 0x08004C80 0x00000020 + 0x00004CC0 0x08004CC0 0x00000010 + 0x00004CE0 0x08004CE0 0x00000010 + 0x00004D00 0x08004D00 0x00000010 >; + + timer_1ms: timer@0x400000 { + compatible = "ALTR,timer-1.0"; + reg = < 0x00400000 0x00000020 >; + interrupt-parent = < &cpu >; + interrupts = < 11 >; + clock-frequency = < 125000000 >; + }; //end timer@0x400000 (timer_1ms) + + sysid: sysid@0x4d40 { + compatible = "ALTR,sysid-1.0"; + reg = < 0x00004D40 0x00000008 >; + id = < 0 >; + timestamp = < 1364882880 >; + }; //end sysid@0x4d40 (sysid) + + jtag_uart: serial@0x4d50 { + compatible = "ALTR,juart-1.0"; + reg = < 0x00004D50 0x00000008 >; + interrupt-parent = < &cpu >; + interrupts = < 1 >; + }; //end serial@0x4d50 (jtag_uart) + + tse_mac: ethernet@0x4000 { + compatible = "ALTR,tse-1.0"; + reg = < 0x00004000 0x00000400 + 0x00004400 0x00000040 + 0x00004800 0x00000040 + 0x00002000 0x00002000 >; + reg-names = "control_port", "rx_csr", "tx_csr", "s1"; + interrupt-parent = < &cpu >; + interrupts = < 2 3 >; + interrupt-names = "rx_irq", "tx_irq"; + rx-fifo-depth = < 8192 >; + tx-fifo-depth = < 8192 >; + address-bits = < 48 >; + max-frame-size = < 1518 >; + local-mac-address = [ 02 00 00 00 00 00 ]; + phy-mode = "rgmii-id"; + ALTR,mii-id = < 0 >; + phy-handle = < &phy0 >; + tse_mac_mdio: mdio { + compatible = "altr,tse-mdio"; + #address-cells = < 1 >; + #size-cells = < 0 >; + phy0: ethernet-phy@18 { + reg = < 18 >; + device_type = "ethernet-phy"; + }; + }; + }; //end ethernet@0x4000 (tse_mac) + + uart: serial@0x4c80 { + compatible = "ALTR,uart-1.0"; + reg = < 0x00004C80 0x00000020 >; + interrupt-parent = < &cpu >; + interrupts = < 10 >; + current-speed = < 115200 >; + clock-frequency = < 62500000 >; + }; //end serial@0x4c80 (uart) + + user_led_pio_8out: gpio@0x4cc0 { + compatible = "ALTR,pio-1.0"; + reg = < 0x00004CC0 0x00000010 >; + width = < 8 >; + resetvalue = < 255 >; + #gpio-cells = < 2 >; + gpio-controller; + }; //end gpio@0x4cc0 (user_led_pio_8out) + + user_dipsw_pio_8in: gpio@0x4ce0 { + compatible = "ALTR,pio-1.0"; + reg = < 0x00004CE0 0x00000010 >; + interrupt-parent = < &cpu >; + interrupts = < 8 >; + width = < 8 >; + resetvalue = < 0 >; + edge_type = < 2 >; + level_trigger = < 0 >; + altr,interrupt_type = < 3 >; + #gpio-cells = < 2 >; + gpio-controller; + }; //end gpio@0x4ce0 (user_dipsw_pio_8in) + + user_pb_pio_4in: gpio@0x4d00 { + compatible = "ALTR,pio-1.0"; + reg = < 0x00004D00 0x00000010 >; + interrupt-parent = < &cpu >; + interrupts = < 9 >; + width = < 4 >; + resetvalue = < 0 >; + edge_type = < 2 >; + level_trigger = < 0 >; + altr,interrupt_type = < 3 >; + #gpio-cells = < 2 >; + gpio-controller; + }; //end gpio@0x4d00 (user_pb_pio_4in) + }; //end bridge@0x8000000 (pb_cpu_to_io) + + cfi_flash_64m: flash@0x0 { + compatible = "cfi-flash"; + reg = < 0x00000000 0x04000000 >; + bank-width = < 2 >; + device-width = < 1 >; + #address-cells = < 1 >; + #size-cells = < 1 >; + + partition@800000 { + reg = < 0x00800000 0x01E00000 >; + label = "JFFS2 Filesystem"; + }; //end partition@800000 + }; //end flash@0x0 (cfi_flash_64m) + }; //end sopc@0 + + chosen { + bootargs = "debug console=ttyJ0,115200"; + }; //end chosen +}; //end / diff --git a/arch/nios2/boot/linked_dtb.S b/arch/nios2/boot/linked_dtb.S new file mode 100644 index 0000000000000..071f922db338e --- /dev/null +++ b/arch/nios2/boot/linked_dtb.S @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +.section .dtb.init.rodata,"a" +.incbin "arch/nios2/boot/system.dtb" diff --git a/arch/nios2/configs/3c120_defconfig b/arch/nios2/configs/3c120_defconfig new file mode 100644 index 0000000000000..a7b1bebdaa3a2 --- /dev/null +++ b/arch/nios2/configs/3c120_defconfig @@ -0,0 +1,81 @@ +CONFIG_SYSVIPC=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_SYSCTL_SYSCALL=y +# CONFIG_ELF_CORE is not set +# CONFIG_EPOLL is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_EVENTFD is not set +# CONFIG_SHMEM is not set +# CONFIG_AIO is not set +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MEM_BASE=0x10000000 +CONFIG_NIOS2_HW_MUL_SUPPORT=y +CONFIG_NIOS2_HW_DIV_SUPPORT=y +CONFIG_CUSTOM_CACHE_SETTINGS=y +CONFIG_NIOS2_DCACHE_SIZE=0x8000 +CONFIG_NIOS2_ICACHE_SIZE=0x8000 +# CONFIG_CMDLINE_IGNORE_DTB is not set +CONFIG_PASS_CMDLINE=y +CONFIG_BOOT_LINK_OFFSET=0x00800000 +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_IPV6 is not set +# CONFIG_WIRELESS is not set +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_FW_LOADER is not set +CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_NETDEVICES=y +CONFIG_ALTERA_TSE=y +CONFIG_MARVELL_PHY=y +# CONFIG_WLAN is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO_SERPORT is not set +# CONFIG_VT is not set +CONFIG_SERIAL_ALTERA_JTAGUART=y +CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE=y +CONFIG_SERIAL_ALTERA_UART=y +# CONFIG_HW_RANDOM is not set +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_ALTERA=y +# CONFIG_HWMON is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +# CONFIG_DNOTIFY is not set +# CONFIG_INOTIFY_USER is not set +CONFIG_JFFS2_FS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3_ACL=y +CONFIG_SUNRPC_DEBUG=y +CONFIG_DEBUG_INFO=y +# CONFIG_ENABLE_WARN_DEPRECATED is not set +CONFIG_KGDB=y diff --git a/arch/nios2/include/asm/Kbuild b/arch/nios2/include/asm/Kbuild new file mode 100644 index 0000000000000..3bbfb6d3b3b65 --- /dev/null +++ b/arch/nios2/include/asm/Kbuild @@ -0,0 +1,62 @@ +include include/asm-generic/Kbuild.asm + +header-y += ucontext.h +header-y += traps.h + +generic-y += atomic.h +generic-y += auxvec.h +generic-y += barrier.h +generic-y += bitops.h +generic-y += bitsperlong.h +generic-y += bug.h +generic-y += bugs.h +generic-y += cputime.h +generic-y += current.h +generic-y += device.h +generic-y += div64.h +generic-y += dma.h +generic-y += emergency-restart.h +generic-y += errno.h +generic-y += exec.h +generic-y += fb.h +generic-y += ftrace.h +generic-y += futex.h +generic-y += hardirq.h +generic-y += hash.h +generic-y += hw_irq.h +generic-y += ioctl.h +generic-y += ipcbuf.h +generic-y += irq_regs.h +generic-y += kdebug.h +generic-y += kmap_types.h +generic-y += kvm_para.h +generic-y += local.h +generic-y += mcs_spinlock.h +generic-y += mman.h +generic-y += module.h +generic-y += msgbuf.h +generic-y += param.h +generic-y += percpu.h +generic-y += posix_types.h +generic-y += preempt.h +generic-y += resource.h +generic-y += scatterlist.h +generic-y += sections.h +generic-y += segment.h +generic-y += sembuf.h +generic-y += serial.h +generic-y += shmbuf.h +generic-y += shmparam.h +generic-y += siginfo.h +generic-y += socket.h +generic-y += sockios.h +generic-y += spinlock.h +generic-y += statfs.h +generic-y += termbits.h +generic-y += termios.h +generic-y += topology.h +generic-y += trace_clock.h +generic-y += types.h +generic-y += unaligned.h +generic-y += user.h +generic-y += xor.h diff --git a/arch/nios2/include/asm/asm-macros.h b/arch/nios2/include/asm/asm-macros.h new file mode 100644 index 0000000000000..5b6f166f88076 --- /dev/null +++ b/arch/nios2/include/asm/asm-macros.h @@ -0,0 +1,313 @@ +/* + * Macro used to simplify coding multi-line assembler. + * Some of the bit test macro can simplify down to one line + * depending on the mask value. + * + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef _ASM_NIOS2_ASMMACROS_H +#define _ASM_NIOS2_ASMMACROS_H +/* + * ANDs reg2 with mask and places the result in reg1. + * + * You cannnot use the same register for reg1 & reg2. + */ + +.macro ANDI32 reg1, reg2, mask +.if \mask & 0xffff + .if \mask & 0xffff0000 + movhi \reg1, %hi(\mask) + movui \reg1, %lo(\mask) + and \reg1, \reg1, \reg2 + .else + andi \reg1, \reg2, %lo(\mask) + .endif +.else + andhi \reg1, \reg2, %hi(\mask) +.endif +.endm + +/* + * ORs reg2 with mask and places the result in reg1. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro ORI32 reg1, reg2, mask +.if \mask & 0xffff + .if \mask & 0xffff0000 + orhi \reg1, \reg2, %hi(\mask) + ori \reg1, \reg2, %lo(\mask) + .else + ori \reg1, \reg2, %lo(\mask) + .endif +.else + orhi \reg1, \reg2, %hi(\mask) +.endif +.endm + +/* + * XORs reg2 with mask and places the result in reg1. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro XORI32 reg1, reg2, mask +.if \mask & 0xffff + .if \mask & 0xffff0000 + xorhi \reg1, \reg2, %hi(\mask) + xori \reg1, \reg1, %lo(\mask) + .else + xori \reg1, \reg2, %lo(\mask) + .endif +.else + xorhi \reg1, \reg2, %hi(\mask) +.endif +.endm + +/* + * This is a support macro for BTBZ & BTBNZ. It checks + * the bit to make sure it is valid 32 value. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro BT reg1, reg2, bit +.if \bit > 31 + .err +.else + .if \bit < 16 + andi \reg1, \reg2, (1 << \bit) + .else + andhi \reg1, \reg2, (1 << (\bit - 16)) + .endif +.endif +.endm + +/* + * Tests the bit in reg2 and branches to label if the + * bit is zero. The result of the bit test is stored in reg1. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro BTBZ reg1, reg2, bit, label + BT \reg1, \reg2, \bit + beq \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and branches to label if the + * bit is non-zero. The result of the bit test is stored in reg1. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro BTBNZ reg1, reg2, bit, label + BT \reg1, \reg2, \bit + bne \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then compliments the bit in reg2. + * The result of the bit test is stored in reg1. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTC reg1, reg2, bit +.if \bit > 31 + .err +.else + .if \bit < 16 + andi \reg1, \reg2, (1 << \bit) + xori \reg2, \reg2, (1 << \bit) + .else + andhi \reg1, \reg2, (1 << (\bit - 16)) + xorhi \reg2, \reg2, (1 << (\bit - 16)) + .endif +.endif +.endm + +/* + * Tests the bit in reg2 and then sets the bit in reg2. + * The result of the bit test is stored in reg1. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTS reg1, reg2, bit +.if \bit > 31 + .err +.else + .if \bit < 16 + andi \reg1, \reg2, (1 << \bit) + ori \reg2, \reg2, (1 << \bit) + .else + andhi \reg1, \reg2, (1 << (\bit - 16)) + orhi \reg2, \reg2, (1 << (\bit - 16)) + .endif +.endif +.endm + +/* + * Tests the bit in reg2 and then resets the bit in reg2. + * The result of the bit test is stored in reg1. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTR reg1, reg2, bit +.if \bit > 31 + .err +.else + .if \bit < 16 + andi \reg1, \reg2, (1 << \bit) + andi \reg2, \reg2, %lo(~(1 << \bit)) + .else + andhi \reg1, \reg2, (1 << (\bit - 16)) + andhi \reg2, \reg2, %lo(~(1 << (\bit - 16))) + .endif +.endif +.endm + +/* + * Tests the bit in reg2 and then compliments the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTCBZ reg1, reg2, bit, label + BTC \reg1, \reg2, \bit + beq \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then compliments the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was non-zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTCBNZ reg1, reg2, bit, label + BTC \reg1, \reg2, \bit + bne \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then sets the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTSBZ reg1, reg2, bit, label + BTS \reg1, \reg2, \bit + beq \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then sets the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was non-zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTSBNZ reg1, reg2, bit, label + BTS \reg1, \reg2, \bit + bne \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then resets the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTRBZ reg1, reg2, bit, label + BTR \reg1, \reg2, \bit + bne \reg1, r0, \label +.endm + +/* + * Tests the bit in reg2 and then resets the bit in reg2. + * The result of the bit test is stored in reg1. If the + * original bit was non-zero it branches to label. + * + * It is NOT safe to use the same register for reg1 & reg2. + */ + +.macro BTRBNZ reg1, reg2, bit, label + BTR \reg1, \reg2, \bit + bne \reg1, r0, \label +.endm + +/* + * Tests the bits in mask against reg2 stores the result in reg1. + * If the all the bits in the mask are zero it branches to label. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro TSTBZ reg1, reg2, mask, label + ANDI32 \reg1, \reg2, \mask + beq \reg1, r0, \label +.endm + +/* + * Tests the bits in mask against reg2 stores the result in reg1. + * If the any of the bits in the mask are 1 it branches to label. + * + * It is safe to use the same register for reg1 & reg2. + */ + +.macro TSTBNZ reg1, reg2, mask, label + ANDI32 \reg1, \reg2, \mask + bne \reg1, r0, \label +.endm + +/* + * Pushes reg onto the stack. + */ + +.macro PUSH reg + addi sp, sp, -4 + stw \reg, 0(sp) +.endm + +/* + * Pops the top of the stack into reg. + */ + +.macro POP reg + ldw \reg, 0(sp) + addi sp, sp, 4 +.endm + + +#endif /* _ASM_NIOS2_ASMMACROS_H */ diff --git a/arch/nios2/include/asm/asm-offsets.h b/arch/nios2/include/asm/asm-offsets.h new file mode 100644 index 0000000000000..5b9f5e04a0580 --- /dev/null +++ b/arch/nios2/include/asm/asm-offsets.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include diff --git a/arch/nios2/include/asm/cache.h b/arch/nios2/include/asm/cache.h new file mode 100644 index 0000000000000..f7fd2c6e6b309 --- /dev/null +++ b/arch/nios2/include/asm/cache.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ASM_NIOS2_CACHE_H +#define _ASM_NIOS2_CACHE_H + +#define NIOS2_DCACHE_SIZE CONFIG_NIOS2_DCACHE_SIZE +#define NIOS2_ICACHE_SIZE CONFIG_NIOS2_ICACHE_SIZE +#define NIOS2_DCACHE_LINE_SIZE CONFIG_NIOS2_DCACHE_LINE_SIZE +#define NIOS2_ICACHE_LINE_SHIFT 5 +#define NIOS2_ICACHE_LINE_SIZE (1 << NIOS2_ICACHE_LINE_SHIFT) + +/* bytes per L1 cache line */ +#define L1_CACHE_SHIFT NIOS2_ICACHE_LINE_SHIFT +#define L1_CACHE_BYTES NIOS2_ICACHE_LINE_SIZE + +#define ARCH_DMA_MINALIGN L1_CACHE_BYTES + +#define __cacheline_aligned +#define ____cacheline_aligned + +#endif diff --git a/arch/nios2/include/asm/cacheflush.h b/arch/nios2/include/asm/cacheflush.h new file mode 100644 index 0000000000000..c9d34d7bed27f --- /dev/null +++ b/arch/nios2/include/asm/cacheflush.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003 Microtronix Datacom Ltd. + * Copyright (C) 2000-2002 Greg Ungerer + * + * Ported from m68knommu. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_CACHEFLUSH_H +#define _ASM_NIOS2_CACHEFLUSH_H + +#include + +/* + * This flag is used to indicate that the page pointed to by a pte is clean + * and does not require cleaning before returning it to the user. + */ +#define PG_dcache_clean PG_arch_1 + +struct mm_struct; + +extern void flush_cache_all(void); +extern void flush_cache_mm(struct mm_struct *mm); +extern void flush_cache_dup_mm(struct mm_struct *mm); +extern void flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void flush_cache_page(struct vm_area_struct *vma, unsigned long vmaddr, + unsigned long pfn); +#define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1 +extern void flush_dcache_page(struct page *page); + +extern void flush_icache_range(unsigned long start, unsigned long end); +extern void flush_icache_page(struct vm_area_struct *vma, struct page *page); + +#define flush_cache_vmap(start, end) flush_dcache_range(start, end) +#define flush_cache_vunmap(start, end) flush_dcache_range(start, end) + +extern void copy_to_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, + void *dst, void *src, int len); +extern void copy_from_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, + void *dst, void *src, int len); + +extern void flush_dcache_range(unsigned long start, unsigned long end); +extern void invalidate_dcache_range(unsigned long start, unsigned long end); + +#define flush_dcache_mmap_lock(mapping) do { } while (0) +#define flush_dcache_mmap_unlock(mapping) do { } while (0) + +#endif /* _ASM_NIOS2_CACHEFLUSH_H */ diff --git a/arch/nios2/include/asm/checksum.h b/arch/nios2/include/asm/checksum.h new file mode 100644 index 0000000000000..6bc1f0d5df7be --- /dev/null +++ b/arch/nios2/include/asm/checksum.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS_CHECKSUM_H +#define _ASM_NIOS_CHECKSUM_H + +/* Take these from lib/checksum.c */ +extern __wsum csum_partial(const void *buff, int len, __wsum sum); +extern __wsum csum_partial_copy(const void *src, void *dst, int len, + __wsum sum); +extern __wsum csum_partial_copy_from_user(const void __user *src, void *dst, + int len, __wsum sum, int *csum_err); +#define csum_partial_copy_nocheck(src, dst, len, sum) \ + csum_partial_copy((src), (dst), (len), (sum)) + +extern __sum16 ip_fast_csum(const void *iph, unsigned int ihl); +extern __sum16 ip_compute_csum(const void *buff, int len); + +/* + * Fold a partial checksum + */ +static inline __sum16 csum_fold(__wsum sum) +{ + __asm__ __volatile__( + "add %0, %1, %0\n" + "cmpltu r8, %0, %1\n" + "srli %0, %0, 16\n" + "add %0, %0, r8\n" + "nor %0, %0, %0\n" + : "=r" (sum) + : "r" (sum << 16), "0" (sum) + : "r8"); + return (__force __sum16) sum; +} + +/* + * computes the checksum of the TCP/UDP pseudo-header + * returns a 16-bit checksum, already complemented + */ +#define csum_tcpudp_nofold csum_tcpudp_nofold +static inline __wsum csum_tcpudp_nofold(__be32 saddr, __be32 daddr, + unsigned short len, + unsigned short proto, + __wsum sum) +{ + __asm__ __volatile__( + "add %0, %1, %0\n" + "cmpltu r8, %0, %1\n" + "add %0, %0, r8\n" /* add carry */ + "add %0, %2, %0\n" + "cmpltu r8, %0, %2\n" + "add %0, %0, r8\n" /* add carry */ + "add %0, %3, %0\n" + "cmpltu r8, %0, %3\n" + "add %0, %0, r8\n" /* add carry */ + : "=r" (sum), "=r" (saddr) + : "r" (daddr), "r" ((ntohs(len) << 16) + (proto * 256)), + "0" (sum), + "1" (saddr) + : "r8"); + + return sum; +} + +static inline __sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, + unsigned short len, + unsigned short proto, __wsum sum) +{ + return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum)); +} + +#endif /* _ASM_NIOS_CHECKSUM_H */ diff --git a/arch/nios2/include/asm/cmpxchg.h b/arch/nios2/include/asm/cmpxchg.h new file mode 100644 index 0000000000000..85938711542d8 --- /dev/null +++ b/arch/nios2/include/asm/cmpxchg.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_CMPXCHG_H +#define _ASM_NIOS2_CMPXCHG_H + +#include + +#define xchg(ptr, x) \ + ((__typeof__(*(ptr)))__xchg((unsigned long)(x), (ptr), sizeof(*(ptr)))) + +struct __xchg_dummy { unsigned long a[100]; }; +#define __xg(x) ((volatile struct __xchg_dummy *)(x)) + +static inline unsigned long __xchg(unsigned long x, volatile void *ptr, + int size) +{ + unsigned long tmp, flags; + + local_irq_save(flags); + + switch (size) { + case 1: + __asm__ __volatile__( + "ldb %0, %2\n" + "stb %1, %2\n" + : "=&r" (tmp) + : "r" (x), "m" (*__xg(ptr)) + : "memory"); + break; + case 2: + __asm__ __volatile__( + "ldh %0, %2\n" + "sth %1, %2\n" + : "=&r" (tmp) + : "r" (x), "m" (*__xg(ptr)) + : "memory"); + break; + case 4: + __asm__ __volatile__( + "ldw %0, %2\n" + "stw %1, %2\n" + : "=&r" (tmp) + : "r" (x), "m" (*__xg(ptr)) + : "memory"); + break; + } + + local_irq_restore(flags); + return tmp; +} + +#include +#include + +#endif /* _ASM_NIOS2_CMPXCHG_H */ diff --git a/arch/nios2/include/asm/cpuinfo.h b/arch/nios2/include/asm/cpuinfo.h new file mode 100644 index 0000000000000..eafd7267e46b4 --- /dev/null +++ b/arch/nios2/include/asm/cpuinfo.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_CPUINFO_H +#define _ASM_NIOS2_CPUINFO_H + +#include +#include + +struct cpuinfo { + /* Core CPU configuration */ + char cpu_impl[12]; + u32 cpu_clock_freq; + u32 mmu; + u32 has_div; + u32 has_mul; + u32 has_mulx; + + /* CPU caches */ + u32 icache_line_size; + u32 icache_size; + u32 dcache_line_size; + u32 dcache_size; + + /* TLB */ + u32 tlb_pid_num_bits; /* number of bits used for the PID in TLBMISC */ + u32 tlb_num_ways; + u32 tlb_num_ways_log2; + u32 tlb_num_entries; + u32 tlb_num_lines; + u32 tlb_ptr_sz; + + /* Addresses */ + u32 reset_addr; + u32 exception_addr; + u32 fast_tlb_miss_exc_addr; +}; + +extern struct cpuinfo cpuinfo; + +extern void setup_cpuinfo(void); + +#endif /* _ASM_NIOS2_CPUINFO_H */ diff --git a/arch/nios2/include/asm/delay.h b/arch/nios2/include/asm/delay.h new file mode 100644 index 0000000000000..8070a090c09ec --- /dev/null +++ b/arch/nios2/include/asm/delay.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_DELAY_H +#define _ASM_NIOS2_DELAY_H + +#include + +static inline void __delay(unsigned long loops) +{ + int dummy; + + __asm__ __volatile__( + "1:\n\t" + " beq %0,zero,2f\n\t" + " addi %0, %0, -1\n\t" + " br 1b\n\t" + "2:\n\t" + : "=r" (dummy) /* Need output for optimizer */ + : "0" (loops)); /* %0 Input */ +} + +/* + * Note that 19 * 226 == 4294 ==~ 2^32 / 10^6, so + * loops = (4294 * usecs * loops_per_jiffy * HZ) / 2^32. + * + * The mul instruction gives us loops = (a * b) / 2^32. + * We choose a = usecs * 19 * HZ and b = loops_per_jiffy * 226 + * because this lets us support a wide range of HZ and + * loops_per_jiffy values without either a or b overflowing 2^32. + * Thus we need usecs * HZ <= (2^32 - 1) / 19 = 226050910 and + * loops_per_jiffy <= (2^32 - 1) / 226 = 19004280 + * (which corresponds to ~3800 bogomips at HZ = 100). + * -- paulus + */ +#define __MAX_UDELAY (226050910UL/HZ) /* maximum udelay argument */ +#define __MAX_NDELAY (4294967295UL/HZ) /* maximum ndelay argument */ + +extern unsigned long loops_per_jiffy; + +static inline void __udelay(unsigned int x) +{ + unsigned int loops; + + /* + * Note, if this is compiled with -mhw-mulx it will produce a "mulxuu" + * (at least in toolchain 145) so there is no need for inline + * assembly here anymore, which might in turn be emulated if unsupported + * by the design. + */ + loops = (unsigned int)((((unsigned long long)(x) * + (unsigned long long)(loops_per_jiffy * 226))) >> 32); + +/* + __asm__("mulxuu %0,%1,%2" : "=r" (loops) : + "r" (x), "r" (loops_per_jiffy * 226)); +*/ + __delay(loops); +} + +static inline void __ndelay(unsigned int x) +{ + unsigned int loops; + + /* see comment in __udelay */ + loops = (unsigned int)((((unsigned long long)(x) * + (unsigned long long)(loops_per_jiffy * 5))) >> 32); + +/* + __asm__("mulxuu %0,%1,%2" : "=r" (loops) : + "r" (x), "r" (loops_per_jiffy * 5)); +*/ + __delay(loops); +} + +extern void __bad_udelay(void); /* deliberately undefined */ +extern void __bad_ndelay(void); /* deliberately undefined */ + +#define udelay(n) (__builtin_constant_p(n) ? \ + ((n) > __MAX_UDELAY ? __bad_udelay() : __udelay((n) * (19 * HZ))) : \ + __udelay((n) * (19 * HZ))) + +#define ndelay(n) (__builtin_constant_p(n) ? \ + ((n) > __MAX_NDELAY ? __bad_ndelay() : __ndelay((n) * HZ)) : \ + __ndelay((n) * HZ)) + +#endif /* _ASM_NIOS2_DELAY_H */ diff --git a/arch/nios2/include/asm/dma-mapping.h b/arch/nios2/include/asm/dma-mapping.h new file mode 100644 index 0000000000000..28db117124745 --- /dev/null +++ b/arch/nios2/include/asm/dma-mapping.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#ifndef _ASM_NIOS2_DMA_MAPPING_H +#define _ASM_NIOS2_DMA_MAPPING_H + +#include +#include +#include + +static inline void __dma_sync(void *vaddr, size_t size, + enum dma_data_direction direction) +{ + switch (direction) { + case DMA_FROM_DEVICE: /* invalidate cache */ + invalidate_dcache_range((unsigned long)vaddr, + (unsigned long)(vaddr + size)); + break; + case DMA_TO_DEVICE: /* flush and invalidate cache */ + case DMA_BIDIRECTIONAL: + flush_dcache_range((unsigned long)vaddr, + (unsigned long)(vaddr + size)); + break; + default: + BUG(); + } +} + +#define dma_alloc_noncoherent(d, s, h, f) dma_alloc_coherent(d, s, h, f) +#define dma_free_noncoherent(d, s, v, h) dma_free_coherent(d, s, v, h) + +void *dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag); + +void dma_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle); + +static inline dma_addr_t dma_map_single(struct device *dev, void *ptr, + size_t size, + enum dma_data_direction direction) +{ + __dma_sync(ptr, size, direction); + return virt_to_phys(ptr); +} + +static inline void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, + size_t size, enum dma_data_direction direction) +{ +} + +extern int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction direction); +extern dma_addr_t dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction direction); +extern void dma_unmap_page(struct device *dev, dma_addr_t dma_address, + size_t size, enum dma_data_direction direction); +extern void dma_unmap_sg(struct device *dev, struct scatterlist *sg, + int nhwentries, enum dma_data_direction direction); +extern void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, + size_t size, enum dma_data_direction direction); +extern void dma_sync_single_for_device(struct device *dev, + dma_addr_t dma_handle, size_t size, enum dma_data_direction direction); +extern void dma_sync_single_range_for_cpu(struct device *dev, + dma_addr_t dma_handle, unsigned long offset, size_t size, + enum dma_data_direction direction); +extern void dma_sync_single_range_for_device(struct device *dev, + dma_addr_t dma_handle, unsigned long offset, size_t size, + enum dma_data_direction direction); +extern void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, + int nelems, enum dma_data_direction direction); +extern void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, + int nelems, enum dma_data_direction direction); + +static inline int dma_supported(struct device *dev, u64 mask) +{ + /* we fall back to GFP_DMA when the mask isn't all 1s, + * so we can't guarantee allocations that must be + * within a tighter range than GFP_DMA. + */ + if (mask < 0x00ffffff) + return 0; + + return 1; +} + +static inline int dma_set_mask(struct device *dev, u64 mask) +{ + if (!dev->dma_mask || !dma_supported(dev, mask)) + return -EIO; + + *dev->dma_mask = mask; + + return 0; +} + +static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr) +{ + return 0; +} + +static inline void dma_cache_sync(struct device *dev, void *vaddr, size_t size, + enum dma_data_direction direction) +{ + __dma_sync(vaddr, size, direction); +} + +#endif /* _ASM_NIOS2_DMA_MAPPING_H */ diff --git a/arch/nios2/include/asm/elf.h b/arch/nios2/include/asm/elf.h new file mode 100644 index 0000000000000..3b169442763f8 --- /dev/null +++ b/arch/nios2/include/asm/elf.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_ELF_H +#define _ASM_NIOS2_ELF_H + +#include + +/* + * This is used to ensure we don't load something for the wrong architecture. + */ +#define elf_check_arch(x) ((x)->e_machine == EM_ALTERA_NIOS2) + +#define ELF_PLAT_INIT(_r, load_addr) + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +/* This is the location that an ET_DYN program is loaded if exec'ed. Typical + use of this is to invoke "./ld.so someprog" to test out a new version of + the loader. We need to make sure that it is out of the way of the program + that it will "exec", and that there is sufficient room for the brk. */ + +#define ELF_ET_DYN_BASE 0xD0000000UL + +/* regs is struct pt_regs, pr_reg is elf_gregset_t (which is + now struct_user_regs, they are different) */ + +#define ELF_CORE_COPY_REGS(pr_reg, regs) \ +{ do { \ + /* Bleech. */ \ + pr_reg[0] = regs->r8; \ + pr_reg[1] = regs->r9; \ + pr_reg[2] = regs->r10; \ + pr_reg[3] = regs->r11; \ + pr_reg[4] = regs->r12; \ + pr_reg[5] = regs->r13; \ + pr_reg[6] = regs->r14; \ + pr_reg[7] = regs->r15; \ + pr_reg[8] = regs->r1; \ + pr_reg[9] = regs->r2; \ + pr_reg[10] = regs->r3; \ + pr_reg[11] = regs->r4; \ + pr_reg[12] = regs->r5; \ + pr_reg[13] = regs->r6; \ + pr_reg[14] = regs->r7; \ + pr_reg[15] = regs->orig_r2; \ + pr_reg[16] = regs->ra; \ + pr_reg[17] = regs->fp; \ + pr_reg[18] = regs->sp; \ + pr_reg[19] = regs->gp; \ + pr_reg[20] = regs->estatus; \ + pr_reg[21] = regs->ea; \ + pr_reg[22] = regs->orig_r7; \ + { \ + struct switch_stack *sw = ((struct switch_stack *)regs) - 1; \ + pr_reg[23] = sw->r16; \ + pr_reg[24] = sw->r17; \ + pr_reg[25] = sw->r18; \ + pr_reg[26] = sw->r19; \ + pr_reg[27] = sw->r20; \ + pr_reg[28] = sw->r21; \ + pr_reg[29] = sw->r22; \ + pr_reg[30] = sw->r23; \ + pr_reg[31] = sw->fp; \ + pr_reg[32] = sw->gp; \ + pr_reg[33] = sw->ra; \ + } \ +} while (0); } + +/* This yields a mask that user programs can use to figure out what + instruction set this cpu supports. */ + +#define ELF_HWCAP (0) + +/* This yields a string that ld.so will use to load implementation + specific libraries for optimization. This is more specific in + intent than poking at uname or /proc/cpuinfo. */ + +#define ELF_PLATFORM (NULL) + +#endif /* _ASM_NIOS2_ELF_H */ diff --git a/arch/nios2/include/asm/entry.h b/arch/nios2/include/asm/entry.h new file mode 100644 index 0000000000000..8b7cddd086b02 --- /dev/null +++ b/arch/nios2/include/asm/entry.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * Based on m68knommu asm/entry.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_ENTRY_H +#define _ASM_NIOS2_ENTRY_H + +#ifdef __ASSEMBLY__ + +#include +#include +#include + +/* + * Stack layout in 'ret_from_exception': + * + * This allows access to the syscall arguments in registers r4-r8 + * + * 0(sp) - r8 + * 4(sp) - r9 + * 8(sp) - r10 + * C(sp) - r11 + * 10(sp) - r12 + * 14(sp) - r13 + * 18(sp) - r14 + * 1C(sp) - r15 + * 20(sp) - r1 + * 24(sp) - r2 + * 28(sp) - r3 + * 2C(sp) - r4 + * 30(sp) - r5 + * 34(sp) - r6 + * 38(sp) - r7 + * 3C(sp) - orig_r2 + * 40(sp) - ra + * 44(sp) - fp + * 48(sp) - sp + * 4C(sp) - gp + * 50(sp) - estatus + * 54(sp) - status_extension (NOMMU only) + * 58(sp) - ea + */ + +/* + * Standard Nios2 interrupt entry and exit macros. + * Must be called with interrupts disabled. + */ +.macro SAVE_ALL + rdctl r24, estatus + andi r24, r24, ESTATUS_EU + beq r24, r0, 1f /* In supervisor mode, already on kernel stack */ + + movia r24, _current_thread /* Switch to current kernel stack */ + ldw r24, 0(r24) /* using the thread_info */ + addi r24, r24, THREAD_SIZE-PT_REGS_SIZE + stw sp, PT_SP(r24) /* Save user stack before changing */ + mov sp, r24 + br 2f + +1 : mov r24, sp + addi sp, sp, -PT_REGS_SIZE /* Backup the kernel stack pointer */ + stw r24, PT_SP(sp) +2 : stw r1, PT_R1(sp) + stw r2, PT_R2(sp) + stw r3, PT_R3(sp) + stw r4, PT_R4(sp) + stw r5, PT_R5(sp) + stw r6, PT_R6(sp) + stw r7, PT_R7(sp) + stw r8, PT_R8(sp) + stw r9, PT_R9(sp) + stw r10, PT_R10(sp) + stw r11, PT_R11(sp) + stw r12, PT_R12(sp) + stw r13, PT_R13(sp) + stw r14, PT_R14(sp) + stw r15, PT_R15(sp) + stw r2, PT_ORIG_R2(sp) + stw r7, PT_ORIG_R7(sp) + + stw ra, PT_RA(sp) + stw fp, PT_FP(sp) + stw gp, PT_GP(sp) + rdctl r24, estatus + stw r24, PT_ESTATUS(sp) + stw ea, PT_EA(sp) +.endm + +.macro RESTORE_ALL + ldw r1, PT_R1(sp) /* Restore registers */ + ldw r2, PT_R2(sp) + ldw r3, PT_R3(sp) + ldw r4, PT_R4(sp) + ldw r5, PT_R5(sp) + ldw r6, PT_R6(sp) + ldw r7, PT_R7(sp) + ldw r8, PT_R8(sp) + ldw r9, PT_R9(sp) + ldw r10, PT_R10(sp) + ldw r11, PT_R11(sp) + ldw r12, PT_R12(sp) + ldw r13, PT_R13(sp) + ldw r14, PT_R14(sp) + ldw r15, PT_R15(sp) + ldw ra, PT_RA(sp) + ldw fp, PT_FP(sp) + ldw gp, PT_GP(sp) + ldw r24, PT_ESTATUS(sp) + wrctl estatus, r24 + ldw ea, PT_EA(sp) + ldw sp, PT_SP(sp) /* Restore sp last */ +.endm + +.macro SAVE_SWITCH_STACK + addi sp, sp, -SWITCH_STACK_SIZE + stw r16, SW_R16(sp) + stw r17, SW_R17(sp) + stw r18, SW_R18(sp) + stw r19, SW_R19(sp) + stw r20, SW_R20(sp) + stw r21, SW_R21(sp) + stw r22, SW_R22(sp) + stw r23, SW_R23(sp) + stw fp, SW_FP(sp) + stw gp, SW_GP(sp) + stw ra, SW_RA(sp) +.endm + +.macro RESTORE_SWITCH_STACK + ldw r16, SW_R16(sp) + ldw r17, SW_R17(sp) + ldw r18, SW_R18(sp) + ldw r19, SW_R19(sp) + ldw r20, SW_R20(sp) + ldw r21, SW_R21(sp) + ldw r22, SW_R22(sp) + ldw r23, SW_R23(sp) + ldw fp, SW_FP(sp) + ldw gp, SW_GP(sp) + ldw ra, SW_RA(sp) + addi sp, sp, SWITCH_STACK_SIZE +.endm + +#endif /* __ASSEMBLY__ */ +#endif /* _ASM_NIOS2_ENTRY_H */ diff --git a/arch/nios2/include/asm/futex-irq.h b/arch/nios2/include/asm/futex-irq.h new file mode 100644 index 0000000000000..ade929663dc64 --- /dev/null +++ b/arch/nios2/include/asm/futex-irq.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Based on asm/futex.h from sh platform. + * + */ + +#ifndef __ASM_NIOS2_FUTEX_IRQ_H +#define __ASM_NIOS2_FUTEX_IRQ_H + + +static inline int atomic_futex_op_xchg_set(int oparg, u32 __user *uaddr, + int *oldval) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + + ret = get_user(*oldval, uaddr); + if (!ret) + ret = put_user(oparg, uaddr); + + local_irq_restore(flags); + + return ret; +} + +static inline int atomic_futex_op_xchg_add(int oparg, u32 __user *uaddr, + int *oldval) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + + ret = get_user(*oldval, uaddr); + if (!ret) + ret = put_user(*oldval + oparg, uaddr); + + local_irq_restore(flags); + + return ret; +} + +static inline int atomic_futex_op_xchg_or(int oparg, u32 __user *uaddr, + int *oldval) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + + ret = get_user(*oldval, uaddr); + if (!ret) + ret = put_user(*oldval | oparg, uaddr); + + local_irq_restore(flags); + + return ret; +} + +static inline int atomic_futex_op_xchg_and(int oparg, u32 __user *uaddr, + int *oldval) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + + ret = get_user(*oldval, uaddr); + if (!ret) + ret = put_user(*oldval & oparg, uaddr); + + local_irq_restore(flags); + + return ret; +} + +static inline int atomic_futex_op_xchg_xor(int oparg, u32 __user *uaddr, + int *oldval) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + + ret = get_user(*oldval, uaddr); + if (!ret) + ret = put_user(*oldval ^ oparg, uaddr); + + local_irq_restore(flags); + + return ret; +} + +static inline int atomic_futex_op_cmpxchg_inatomic(u32 *uval, + u32 __user *uaddr, + u32 oldval, u32 newval) +{ + unsigned long flags; + int ret; + u32 prev = 0; + + local_irq_save(flags); + + ret = get_user(prev, uaddr); + if (!ret && oldval == prev) + ret = put_user(newval, uaddr); + + local_irq_restore(flags); + + *uval = prev; + return ret; +} + +#endif /* __ASM_NIOS2_FUTEX_IRQ_H */ diff --git a/arch/nios2/include/asm/futex.h b/arch/nios2/include/asm/futex.h new file mode 100644 index 0000000000000..52b2fa9c3aeb9 --- /dev/null +++ b/arch/nios2/include/asm/futex.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Based on asm/futex.h from sh platform. + * + */ + +#ifndef __ASM_NIOS2_FUTEX_H +#define __ASM_NIOS2_FUTEX_H + +#ifdef __KERNEL__ + +#include +#include +#include + +#include + +static inline int futex_atomic_op_inuser(int encoded_op, u32 __user *uaddr) +{ + int op = (encoded_op >> 28) & 7; + int cmp = (encoded_op >> 24) & 15; + int oparg = (encoded_op << 8) >> 20; + int cmparg = (encoded_op << 20) >> 20; + int oldval = 0, ret; + + if (encoded_op & (FUTEX_OP_OPARG_SHIFT << 28)) + oparg = 1 << oparg; + + if (!access_ok(VERIFY_WRITE, uaddr, sizeof(u32))) + return -EFAULT; + + pagefault_disable(); + + switch (op) { + case FUTEX_OP_SET: + ret = atomic_futex_op_xchg_set(oparg, uaddr, &oldval); + break; + case FUTEX_OP_ADD: + ret = atomic_futex_op_xchg_add(oparg, uaddr, &oldval); + break; + case FUTEX_OP_OR: + ret = atomic_futex_op_xchg_or(oparg, uaddr, &oldval); + break; + case FUTEX_OP_ANDN: + ret = atomic_futex_op_xchg_and(~oparg, uaddr, &oldval); + break; + case FUTEX_OP_XOR: + ret = atomic_futex_op_xchg_xor(oparg, uaddr, &oldval); + break; + default: + ret = -ENOSYS; + break; + } + + pagefault_enable(); + + if (!ret) { + switch (cmp) { + case FUTEX_OP_CMP_EQ: + ret = (oldval == cmparg); + break; + case FUTEX_OP_CMP_NE: + ret = (oldval != cmparg); + break; + case FUTEX_OP_CMP_LT: + ret = (oldval < cmparg); + break; + case FUTEX_OP_CMP_GE: + ret = (oldval >= cmparg); + break; + case FUTEX_OP_CMP_LE: + ret = (oldval <= cmparg); + break; + case FUTEX_OP_CMP_GT: + ret = (oldval > cmparg); + break; + default: + ret = -ENOSYS; + } + } + + return ret; +} + +static inline int +futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, + u32 oldval, u32 newval) +{ + if (!access_ok(VERIFY_WRITE, uaddr, sizeof(u32))) + return -EFAULT; + + return atomic_futex_op_cmpxchg_inatomic(uval, uaddr, oldval, newval); +} + +#endif /* __KERNEL__ */ +#endif /* __ASM_NIOS2_FUTEX_H */ diff --git a/arch/nios2/include/asm/gpio.h b/arch/nios2/include/asm/gpio.h new file mode 100644 index 0000000000000..1bf4061e3536c --- /dev/null +++ b/arch/nios2/include/asm/gpio.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_GPIO_H +#define _ASM_NIOS2_GPIO_H + +#include +#include + +#ifdef CONFIG_GPIOLIB + +#define gpio_get_value __gpio_get_value +#define gpio_set_value __gpio_set_value +#define gpio_cansleep __gpio_cansleep +#define gpio_to_irq __gpio_to_irq + +static inline int irq_to_gpio(unsigned int irq) +{ + return -EINVAL; +} + +#endif /* CONFIG_GPIOLIB */ + +#endif /* _ASM_NIOS2_GPIO_H */ diff --git a/arch/nios2/include/asm/io.h b/arch/nios2/include/asm/io.h new file mode 100644 index 0000000000000..46f4a04388700 --- /dev/null +++ b/arch/nios2/include/asm/io.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_IO_H +#define _ASM_NIOS2_IO_H + +#include + +#define IO_SPACE_LIMIT 0xffffffff + +#ifndef CONFIG_CC_OPTIMIZE_FOR_SIZE +# define __IO_USE_DUFFS +#endif + +#ifdef __IO_USE_DUFFS + +/* Use "Duff's Device" to unroll the loops. */ +#define __IO_OUT_LOOP(a, b, l) \ + do { \ + if (l > 0) { \ + int _n = (l + 7) / 8; \ + switch (l % 8) { \ + case 0: \ + do { \ + *a = *b++; \ + case 7: \ + *a = *b++; \ + case 6: \ + *a = *b++; \ + case 5: \ + *a = *b++; \ + case 4: \ + *a = *b++; \ + case 3: \ + *a = *b++; \ + case 2: \ + *a = *b++; \ + case 1: \ + *a = *b++; \ + } while (--_n > 0); \ + } \ + } \ + } while (0) + +#define __IO_IN_LOOP(a, b, l) \ + do { \ + if (l > 0) { \ + int _n = (l + 7) / 8; \ + switch (l % 8) { \ + case 0: \ + do { \ + *b++ = *a; \ + case 7: \ + *b++ = *a; \ + case 6: \ + *b++ = *a; \ + case 5: \ + *b++ = *a; \ + case 4: \ + *b++ = *a; \ + case 3: \ + *b++ = *a; \ + case 2: \ + *b++ = *a; \ + case 1: \ + *b++ = *a; \ + } while (--_n > 0); \ + } \ + } \ + } while (0) + +#else /* __IO_USE_DUFFS */ + +/* Use simple loops. */ +#define __IO_OUT_LOOP(a, b, l) \ + do { \ + while (l--) \ + *a = *b++; \ + } while (0) + +#define __IO_IN_LOOP(a, b, l) \ + do { \ + while (l--) \ + *b++ = *a; \ + } while (0) + +#endif /* __IO_USE_DUFFS */ + +static inline void io_outsb(unsigned int addr, const void *buf, int len) +{ + volatile unsigned char *ap = (volatile unsigned char *)addr; + unsigned char *bp = (unsigned char *)buf; + __IO_OUT_LOOP(ap, bp, len); +} + +static inline void io_outsw(unsigned int addr, const void *buf, int len) +{ + volatile unsigned short *ap = (volatile unsigned short *)addr; + unsigned short *bp = (unsigned short *)buf; + __IO_OUT_LOOP(ap, bp, len); +} + +static inline void io_outsl(unsigned int addr, const void *buf, int len) +{ + volatile unsigned int *ap = (volatile unsigned int *)addr; + unsigned int *bp = (unsigned int *)buf; + __IO_OUT_LOOP(ap, bp, len); +} + +static inline void io_insb(unsigned int addr, void *buf, int len) +{ + volatile unsigned char *ap = (volatile unsigned char *)addr; + unsigned char *bp = (unsigned char *)buf; + __IO_IN_LOOP(ap, bp, len); +} + +static inline void io_insw(unsigned int addr, void *buf, int len) +{ + volatile unsigned short *ap = (volatile unsigned short *)addr; + unsigned short *bp = (unsigned short *)buf; + __IO_IN_LOOP(ap, bp, len); +} + +static inline void io_insl(unsigned int addr, void *buf, int len) +{ + volatile unsigned int *ap = (volatile unsigned int *)addr; + unsigned int *bp = (unsigned int *)buf; + __IO_IN_LOOP(ap, bp, len); +} + +#undef __IO_OUT_LOOP +#undef __IO_IN_LOOP +#undef __IO_USE_DUFFS + +#define readb_relaxed(addr) readb(addr) +#define readw_relaxed(addr) readw(addr) +#define readl_relaxed(addr) readl(addr) + +#define writeb_relaxed(x, addr) writeb(x, addr) +#define writew_relaxed(x, addr) writew(x, addr) +#define writel_relaxed(x, addr) writel(x, addr) + +#define outsb(a, b, l) io_outsb(a, b, l) +#define outsw(a, b, l) io_outsw(a, b, l) +#define outsl(a, b, l) io_outsl(a, b, l) + +#define insb(a, b, l) io_insb(a, b, l) +#define insw(a, b, l) io_insw(a, b, l) +#define insl(a, b, l) io_insl(a, b, l) + +#include + +/* + * make the short names macros so specific devices + * can override them as required + */ +#define inb(addr) readb(addr) +#define inw(addr) readw(addr) +#define inl(addr) readl(addr) +#define outb(x, addr) ((void) writeb(x, addr)) +#define outw(x, addr) ((void) writew(x, addr)) +#define outl(x, addr) ((void) writel(x, addr)) + +extern void __iomem *__ioremap(unsigned long physaddr, unsigned long size, + unsigned long cacheflag); +extern void __iounmap(void __iomem *addr); + +static inline void __iomem *ioremap(unsigned long physaddr, unsigned long size) +{ + return __ioremap(physaddr, size, 0); +} + +static inline void __iomem *ioremap_nocache(unsigned long physaddr, + unsigned long size) +{ + return __ioremap(physaddr, size, 0); +} + +static inline void __iomem *ioremap_writethrough(unsigned long physaddr, + unsigned long size) +{ + return __ioremap(physaddr, size, 0); +} + +static inline void __iomem *ioremap_fullcache(unsigned long physaddr, + unsigned long size) +{ + return __ioremap(physaddr, size, _PAGE_CACHED); +} + +static inline void iounmap(void __iomem *addr) +{ + __iounmap(addr); +} + +/* Pages to physical address... */ +# define page_to_phys(page) virt_to_phys(page_to_virt(page)) +# define page_to_bus(page) page_to_virt(page) + +/* Macros used for converting between virtual and physical mappings. */ +# define phys_to_virt(vaddr) \ + ((void *)((unsigned long)(vaddr) | CONFIG_KERNEL_REGION_BASE)) +/* Clear top 3 bits */ +# define virt_to_phys(vaddr) \ + ((unsigned long)((unsigned long)(vaddr) & ~0xE0000000)) + +#define virt_to_bus virt_to_phys +#define bus_to_virt phys_to_virt + +#define ioport_map(port, nr) ioremap(port, nr) +#define ioport_unmap(port) iounmap(port) + +/* Macros used for smc91x.c driver */ +#define readsb(p, d, l) insb(p, d, l) +#define readsw(p, d, l) insw(p, d, l) +#define readsl(p, d, l) insl(p, d, l) +#define writesb(p, d, l) outsb(p, d, l) +#define writesw(p, d, l) outsw(p, d, l) +#define writesl(p, d, l) outsl(p, d, l) + +#endif /* _ASM_NIOS2_IO_H */ diff --git a/arch/nios2/include/asm/irq.h b/arch/nios2/include/asm/irq.h new file mode 100644 index 0000000000000..f7e94b9210d37 --- /dev/null +++ b/arch/nios2/include/asm/irq.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_IRQ_H +#define _ASM_NIOS2_IRQ_H + +#define NIOS2_CPU_NR_IRQS 32 +/* Reserve 32 additional interrupts for GPIO IRQs */ +#define NR_IRQS (NIOS2_CPU_NR_IRQS + 32) + +#ifndef NO_IRQ +#define NO_IRQ (-1) +#endif + +#include +#include + +#endif diff --git a/arch/nios2/include/asm/irqflags.h b/arch/nios2/include/asm/irqflags.h new file mode 100644 index 0000000000000..e91f4f0b7c694 --- /dev/null +++ b/arch/nios2/include/asm/irqflags.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#ifndef _ASM_IRQFLAGS_H +#define _ASM_IRQFLAGS_H + +#include + +static inline unsigned long arch_local_save_flags(void) +{ + return RDCTL(CTL_STATUS); +} + +/* + * This will restore ALL status register flags, not only the interrupt + * mask flag. + */ +static inline void arch_local_irq_restore(unsigned long flags) +{ + WRCTL(CTL_STATUS, flags); +} + +static inline void arch_local_irq_disable(void) +{ + unsigned long flags; + flags = arch_local_save_flags(); + arch_local_irq_restore(flags & ~STATUS_PIE); +} + +static inline void arch_local_irq_enable(void) +{ + unsigned long flags; + flags = arch_local_save_flags(); + arch_local_irq_restore(flags | STATUS_PIE); +} + +static inline int arch_irqs_disabled_flags(unsigned long flags) +{ + return (flags & STATUS_PIE) == 0; +} + +static inline int arch_irqs_disabled(void) +{ + return arch_irqs_disabled_flags(arch_local_save_flags()); +} + +static inline unsigned long arch_local_irq_save(void) +{ + unsigned long flags; + flags = arch_local_save_flags(); + arch_local_irq_restore(flags & ~STATUS_PIE); + return flags; +} + +#endif /* _ASM_IRQFLAGS_H */ diff --git a/arch/nios2/include/asm/kgdb.h b/arch/nios2/include/asm/kgdb.h new file mode 100644 index 0000000000000..ec454c0885803 --- /dev/null +++ b/arch/nios2/include/asm/kgdb.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * Based on the code posted by Kazuyasu on the Altera Forum at: + * http://www.alteraforum.com/forum/showpost.php?p=77003&postcount=20 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_KGDB_H +#define _ASM_NIOS2_KGDB_H + +#define CACHE_FLUSH_IS_SAFE 1 +#define BUFMAX 2048 + +enum regnames { + GDB_R0 = 0, + GDB_AT, + GDB_R2, + GDB_R3, + GDB_R4, + GDB_R5, + GDB_R6, + GDB_R7, + GDB_R8, + GDB_R9, + GDB_R10, + GDB_R11, + GDB_R12, + GDB_R13, + GDB_R14, + GDB_R15, + GDB_R16, + GDB_R17, + GDB_R18, + GDB_R19, + GDB_R20, + GDB_R21, + GDB_R22, + GDB_R23, + GDB_ET, + GDB_BT, + GDB_GP, + GDB_SP, + GDB_FP, + GDB_EA, + GDB_BA, + GDB_RA, + GDB_PC, + GDB_STATUS, + GDB_ESTATUS, + GDB_BSTATUS, + GDB_IENABLE, + GDB_IPENDING, + GDB_CPUID, + GDB_CTL6, + GDB_EXCEPTION, + GDB_PTEADDR, + GDB_TLBACC, + GDB_TLBMISC, + GDB_CTL11, + GDB_BADADDR, + GDB_CONFIG, + /* do not change the last entry or anything below! */ + GDB_NUMREGBYTES /* number of registers */ +}; + +#define NUMREGBYTES (GDB_NUMREGBYTES * 4) + +#define BREAK_INSTR_SIZE 4 +static inline void arch_kgdb_breakpoint(void) +{ + /* + * we cannot use 'trap 30' here as the nios2 assembler is bugged and + * does consider any argument an error, so we just encode it directly. + */ + __asm__ __volatile__ (".word 0x003b6fba"); +} + +#endif /* _ASM_NIOS2_KGDB_H */ diff --git a/arch/nios2/include/asm/linkage.h b/arch/nios2/include/asm/linkage.h new file mode 100644 index 0000000000000..1519d60c036d2 --- /dev/null +++ b/arch/nios2/include/asm/linkage.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ASM_NIOS2_LINKAGE_H +#define _ASM_NIOS2_LINKAGE_H + +/* This file is required by include/linux/linkage.h */ +#define __ALIGN .align 4 +#define __ALIGN_STR ".align 4" + +#endif diff --git a/arch/nios2/include/asm/mmu.h b/arch/nios2/include/asm/mmu.h new file mode 100644 index 0000000000000..96ab052830ce4 --- /dev/null +++ b/arch/nios2/include/asm/mmu.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * Taken from m68knommu. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_MMU_H +#define _ASM_NIOS2_MMU_H + +/* Default "unsigned long" context */ +typedef unsigned long mm_context_t; + +#endif /* _ASM_NIOS2_MMU_H */ diff --git a/arch/nios2/include/asm/mmu_context.h b/arch/nios2/include/asm/mmu_context.h new file mode 100644 index 0000000000000..294b4b1f81d4e --- /dev/null +++ b/arch/nios2/include/asm/mmu_context.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 1996, 1997, 1998, 1999 by Ralf Baechle + * Copyright (C) 1999 Silicon Graphics, Inc. + * + * based on MIPS asm/mmu_context.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_MMU_CONTEXT_H +#define _ASM_NIOS2_MMU_CONTEXT_H + +#include + +extern void mmu_context_init(void); +extern unsigned long get_pid_from_context(mm_context_t *ctx); + +/* + * For the fast tlb miss handlers, we keep a pointer to the current pgd. + * processor. + */ +extern pgd_t *pgd_current; + +static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk) +{ +} + +/* + * Initialize the context related info for a new mm_struct instance. + * + * Set all new contexts to 0, that way the generation will never match + * the currently running generation when this context is switched in. + */ +static inline int init_new_context(struct task_struct *tsk, + struct mm_struct *mm) +{ + mm->context = 0; + return 0; +} + +/* + * Destroy context related info for an mm_struct that is about + * to be put to rest. + */ +static inline void destroy_context(struct mm_struct *mm) +{ +} + +void switch_mm(struct mm_struct *prev, struct mm_struct *next, + struct task_struct *tsk); + +static inline void deactivate_mm(struct task_struct *tsk, + struct mm_struct *mm) +{ +} + +/* + * After we have set current->mm to a new value, this activates + * the context for the new mm so we see the new mappings. + */ +void activate_mm(struct mm_struct *prev, struct mm_struct *next); + +#endif /* _ASM_NIOS2_MMU_CONTEXT_H */ diff --git a/arch/nios2/include/asm/mutex.h b/arch/nios2/include/asm/mutex.h new file mode 100644 index 0000000000000..ff6101aa2c715 --- /dev/null +++ b/arch/nios2/include/asm/mutex.h @@ -0,0 +1 @@ +#include diff --git a/arch/nios2/include/asm/page.h b/arch/nios2/include/asm/page.h new file mode 100644 index 0000000000000..5caef25b366d7 --- /dev/null +++ b/arch/nios2/include/asm/page.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * MMU support based on asm/page.h from mips which is: + * + * Copyright (C) 1994 - 1999, 2000, 03 Ralf Baechle + * Copyright (C) 1999, 2000 Silicon Graphics, Inc. + * + * NOMMU support based on asm/page.h from m68knommu. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_PAGE_H +#define _ASM_NIOS2_PAGE_H + +#include +#include + +/* + * PAGE_SHIFT determines the page size + */ +#define PAGE_SHIFT 12 +#define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT) +#define PAGE_MASK (~(PAGE_SIZE - 1)) + +/* + * PAGE_OFFSET -- the first address of the first page of memory. + */ +#define PAGE_OFFSET (CONFIG_MEM_BASE + CONFIG_KERNEL_REGION_BASE) + +#ifndef __ASSEMBLY__ + +/* + * This gives the physical RAM offset. + */ +#define PHYS_OFFSET CONFIG_MEM_BASE + +/* + * It's normally defined only for FLATMEM config but it's + * used in our early mem init code for all memory models. + * So always define it. + */ +#define ARCH_PFN_OFFSET PFN_UP(PHYS_OFFSET) + +#define clear_page(page) memset((page), 0, PAGE_SIZE) +#define copy_page(to, from) memcpy((to), (from), PAGE_SIZE) + +struct page; + +extern void clear_user_page(void *addr, unsigned long vaddr, struct page *page); +extern void copy_user_page(void *vto, void *vfrom, unsigned long vaddr, + struct page *to); + +extern unsigned long shm_align_mask; + +/* + * These are used to make use of C type-checking. + */ +typedef struct page *pgtable_t; +typedef struct { unsigned long pte; } pte_t; +typedef struct { unsigned long pgd; } pgd_t; +typedef struct { unsigned long pgprot; } pgprot_t; + +#define pte_val(x) ((x).pte) +#define pgd_val(x) ((x).pgd) +#define pgprot_val(x) ((x).pgprot) + +#define __pte(x) ((pte_t) { (x) }) +#define __pgd(x) ((pgd_t) { (x) }) +#define __pgprot(x) ((pgprot_t) { (x) }) + +extern unsigned long memory_start; +extern unsigned long memory_end; +extern unsigned long memory_size; + +extern struct page *mem_map; + +#endif /* !__ASSEMBLY__ */ + +# define __pa(x) \ + ((unsigned long)(x) - PAGE_OFFSET + PHYS_OFFSET) +# define __va(x) \ + ((void *)((unsigned long)(x) + PAGE_OFFSET - PHYS_OFFSET)) + +#define page_to_virt(page) \ + ((((page) - mem_map) << PAGE_SHIFT) + PAGE_OFFSET) + +# define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT) +# define pfn_valid(pfn) ((pfn) >= ARCH_PFN_OFFSET && \ + (pfn) < max_mapnr) + +# define virt_to_page(vaddr) pfn_to_page(PFN_DOWN(virt_to_phys(vaddr))) +# define virt_addr_valid(vaddr) pfn_valid(PFN_DOWN(virt_to_phys(vaddr))) + +# define VM_DATA_DEFAULT_FLAGS (VM_READ | VM_WRITE | VM_EXEC | \ + VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC) + +# define UNCAC_ADDR(addr) \ + ((void *)((unsigned)(addr) | CONFIG_IO_REGION_BASE)) +# define CAC_ADDR(addr) \ + ((void *)(((unsigned)(addr) & ~CONFIG_IO_REGION_BASE) | \ + CONFIG_KERNEL_REGION_BASE)) + +#include + +#include + +#endif /* _ASM_NIOS2_PAGE_H */ diff --git a/arch/nios2/include/asm/pci.h b/arch/nios2/include/asm/pci.h new file mode 100644 index 0000000000000..69c86faffe17d --- /dev/null +++ b/arch/nios2/include/asm/pci.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#ifndef __ASM_NIOS2_PCI_H__ +#define __ASM_NIOS2_PCI_H__ + +/* We don't support PCI yet, but some drivers require this file anyway */ + +#endif /* __ASM_NIOS2_PCI_H__ */ diff --git a/arch/nios2/include/asm/pgalloc.h b/arch/nios2/include/asm/pgalloc.h new file mode 100644 index 0000000000000..6e2985e0a7b90 --- /dev/null +++ b/arch/nios2/include/asm/pgalloc.h @@ -0,0 +1,86 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 1994 - 2001, 2003 by Ralf Baechle + * Copyright (C) 1999, 2000, 2001 Silicon Graphics, Inc. + */ + +#ifndef _ASM_NIOS2_PGALLOC_H +#define _ASM_NIOS2_PGALLOC_H + +#include + +static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmd, + pte_t *pte) +{ + set_pmd(pmd, __pmd((unsigned long)pte)); +} + +static inline void pmd_populate(struct mm_struct *mm, pmd_t *pmd, + pgtable_t pte) +{ + set_pmd(pmd, __pmd((unsigned long)page_address(pte))); +} +#define pmd_pgtable(pmd) pmd_page(pmd) + +/* + * Initialize a new pmd table with invalid pointers. + */ +extern void pmd_init(unsigned long page, unsigned long pagetable); + +extern pgd_t *pgd_alloc(struct mm_struct *mm); + +static inline void pgd_free(struct mm_struct *mm, pgd_t *pgd) +{ + free_pages((unsigned long)pgd, PGD_ORDER); +} + +static inline pte_t *pte_alloc_one_kernel(struct mm_struct *mm, + unsigned long address) +{ + pte_t *pte; + + pte = (pte_t *) __get_free_pages(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO, + PTE_ORDER); + + return pte; +} + +static inline pgtable_t pte_alloc_one(struct mm_struct *mm, + unsigned long address) +{ + struct page *pte; + + pte = alloc_pages(GFP_KERNEL | __GFP_REPEAT, PTE_ORDER); + if (pte) { + if (!pgtable_page_ctor(pte)) { + __free_page(pte); + return NULL; + } + clear_highpage(pte); + } + return pte; +} + +static inline void pte_free_kernel(struct mm_struct *mm, pte_t *pte) +{ + free_pages((unsigned long)pte, PTE_ORDER); +} + +static inline void pte_free(struct mm_struct *mm, struct page *pte) +{ + pgtable_page_dtor(pte); + __free_pages(pte, PTE_ORDER); +} + +#define __pte_free_tlb(tlb, pte, addr) \ + do { \ + pgtable_page_dtor(pte); \ + tlb_remove_page((tlb), (pte)); \ + } while (0) + +#define check_pgt_cache() do { } while (0) + +#endif /* _ASM_NIOS2_PGALLOC_H */ diff --git a/arch/nios2/include/asm/pgtable-bits.h b/arch/nios2/include/asm/pgtable-bits.h new file mode 100644 index 0000000000000..ce9e7069aa969 --- /dev/null +++ b/arch/nios2/include/asm/pgtable-bits.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_PGTABLE_BITS_H +#define _ASM_NIOS2_PGTABLE_BITS_H + +/* + * These are actual hardware defined protection bits in the tlbacc register + * which looks like this: + * + * 31 30 ... 26 25 24 23 22 21 20 19 18 ... 1 0 + * ignored........ C R W X G PFN............ + */ +#define _PAGE_GLOBAL (1<<20) +#define _PAGE_EXEC (1<<21) +#define _PAGE_WRITE (1<<22) +#define _PAGE_READ (1<<23) +#define _PAGE_CACHED (1<<24) /* C: data access cacheable */ + +/* + * Software defined bits. They are ignored by the hardware and always read back + * as zero, but can be written as non-zero. + */ +#define _PAGE_PRESENT (1<<25) /* PTE contains a translation */ +#define _PAGE_ACCESSED (1<<26) /* page referenced */ +#define _PAGE_DIRTY (1<<27) /* dirty page */ +#define _PAGE_FILE (1<<28) /* PTE used for file mapping or swap */ + +#endif /* _ASM_NIOS2_PGTABLE_BITS_H */ diff --git a/arch/nios2/include/asm/pgtable.h b/arch/nios2/include/asm/pgtable.h new file mode 100644 index 0000000000000..c4cf0b5906dfa --- /dev/null +++ b/arch/nios2/include/asm/pgtable.h @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * + * Based on asm/pgtable-32.h from mips which is: + * + * Copyright (C) 1994, 95, 96, 97, 98, 99, 2000, 2003 Ralf Baechle + * Copyright (C) 1999, 2000, 2001 Silicon Graphics, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_PGTABLE_H +#define _ASM_NIOS2_PGTABLE_H + +#include +#include +#include +#include +#include + +#include +#include + +#define FIRST_USER_ADDRESS 0 + +#define VMALLOC_START CONFIG_KERNEL_MMU_REGION_BASE +#define VMALLOC_END (CONFIG_KERNEL_REGION_BASE - 1) + +struct mm_struct; + +/* Helper macro */ +#define MKP(x, w, r) __pgprot(_PAGE_PRESENT | _PAGE_CACHED | \ + ((x) ? _PAGE_EXEC : 0) | \ + ((r) ? _PAGE_READ : 0) | \ + ((w) ? _PAGE_WRITE : 0)) +/* + * These are the macros that generic kernel code needs + * (to populate protection_map[]) + */ + +/* Remove W bit on private pages for COW support */ +#define __P000 MKP(0, 0, 0) +#define __P001 MKP(0, 0, 1) +#define __P010 MKP(0, 0, 0) /* COW */ +#define __P011 MKP(0, 0, 1) /* COW */ +#define __P100 MKP(1, 0, 0) +#define __P101 MKP(1, 0, 1) +#define __P110 MKP(1, 0, 0) /* COW */ +#define __P111 MKP(1, 0, 1) /* COW */ + +/* Shared pages can have exact HW mapping */ +#define __S000 MKP(0, 0, 0) +#define __S001 MKP(0, 0, 1) +#define __S010 MKP(0, 1, 0) +#define __S011 MKP(0, 1, 1) +#define __S100 MKP(1, 0, 0) +#define __S101 MKP(1, 0, 1) +#define __S110 MKP(1, 1, 0) +#define __S111 MKP(1, 1, 1) + +/* Used all over the kernel */ +#define PAGE_KERNEL __pgprot(_PAGE_PRESENT | _PAGE_CACHED | _PAGE_READ | \ + _PAGE_WRITE | _PAGE_EXEC | _PAGE_GLOBAL) + +#define PAGE_COPY MKP(0, 0, 1) + +#define PGD_ORDER 0 +#define PTE_ORDER 0 + +#define PTRS_PER_PGD ((PAGE_SIZE << PGD_ORDER) / sizeof(pgd_t)) +#define PTRS_PER_PTE ((PAGE_SIZE << PTE_ORDER) / sizeof(pte_t)) + +#define USER_PTRS_PER_PGD (CONFIG_KERNEL_MMU_REGION_BASE / PGDIR_SIZE) + +#define PGDIR_SHIFT 22 +#define PGDIR_SIZE (1UL << PGDIR_SHIFT) +#define PGDIR_MASK (~(PGDIR_SIZE-1)) + +/* + * ZERO_PAGE is a global shared page that is always zero: used + * for zero-mapped memory areas etc.. + */ +extern unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)]; +#define ZERO_PAGE(vaddr) (virt_to_page(empty_zero_page)) + +extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; +extern pte_t invalid_pte_table[PAGE_SIZE/sizeof(pte_t)]; + +/* + * (pmds are folded into puds so this doesn't get actually called, + * but the define is needed for a generic inline function.) + */ +static inline void set_pmd(pmd_t *pmdptr, pmd_t pmdval) +{ + pmdptr->pud.pgd.pgd = pmdval.pud.pgd.pgd; +} + +/* to find an entry in a page-table-directory */ +#define pgd_index(addr) (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)) +#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr)) + +static inline int pte_write(pte_t pte) \ + { return pte_val(pte) & _PAGE_WRITE; } +static inline int pte_dirty(pte_t pte) \ + { return pte_val(pte) & _PAGE_DIRTY; } +static inline int pte_young(pte_t pte) \ + { return pte_val(pte) & _PAGE_ACCESSED; } +static inline int pte_file(pte_t pte) \ + { return pte_val(pte) & _PAGE_FILE; } +static inline int pte_special(pte_t pte) { return 0; } + +#define pgprot_noncached pgprot_noncached + +static inline pgprot_t pgprot_noncached(pgprot_t _prot) +{ + unsigned long prot = pgprot_val(_prot); + + prot &= ~_PAGE_CACHED; + + return __pgprot(prot); +} + +/* + * FIXME: Today unmapped pages are mapped to the low physical addresses + * and not 0 (to avoid to trigger the false alias detection in the iss) + * Also check pte_clear. + */ +static inline int pte_none(pte_t pte) +{ + return !(pte_val(pte) & ~(_PAGE_GLOBAL|0xf)); +} + +static inline int pte_present(pte_t pte) \ + { return pte_val(pte) & _PAGE_PRESENT; } + +/* + * The following only work if pte_present() is true. + * Undefined behaviour if not.. + */ +static inline pte_t pte_wrprotect(pte_t pte) +{ + pte_val(pte) &= ~_PAGE_WRITE; + return pte; +} + +static inline pte_t pte_mkclean(pte_t pte) +{ + pte_val(pte) &= ~_PAGE_DIRTY; + return pte; +} + +static inline pte_t pte_mkold(pte_t pte) +{ + pte_val(pte) &= ~_PAGE_ACCESSED; + return pte; +} + +static inline pte_t pte_mkwrite(pte_t pte) +{ + pte_val(pte) |= _PAGE_WRITE; + return pte; +} + +static inline pte_t pte_mkdirty(pte_t pte) +{ + pte_val(pte) |= _PAGE_DIRTY; + return pte; +} + +static inline pte_t pte_mkspecial(pte_t pte) { return pte; } + +static inline pte_t pte_mkyoung(pte_t pte) +{ + pte_val(pte) |= _PAGE_ACCESSED; + return pte; +} + +static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) +{ + const unsigned long mask = _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC; + pte_val(pte) = (pte_val(pte) & ~mask) | (pgprot_val(newprot) & mask); + return pte; +} + +static inline int pmd_present(pmd_t pmd) +{ + return (pmd_val(pmd) != (unsigned long) invalid_pte_table) + && (pmd_val(pmd) != 0UL); +} + +static inline void pmd_clear(pmd_t *pmdp) +{ + pmd_val(*pmdp) = (unsigned long) invalid_pte_table; +} + +#define pte_pfn(pte) (pte_val(pte) & 0xfffff) +#define pfn_pte(pfn, prot) (__pte(pfn | pgprot_val(prot))) +#define pte_page(pte) (pfn_to_page(pte_pfn(pte))) + +/* + * Store a linux PTE into the linux page table. + */ +static inline void set_pte(pte_t *ptep, pte_t pteval) +{ + *ptep = pteval; +} + +static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, pte_t pteval) +{ + unsigned long paddr = page_to_virt(pte_page(pteval)); + flush_dcache_range(paddr, paddr + PAGE_SIZE); + set_pte(ptep, pteval); +} + +static inline int pmd_none(pmd_t pmd) +{ + return (pmd_val(pmd) == + (unsigned long) invalid_pte_table) || (pmd_val(pmd) == 0UL); +} + +#define pmd_bad(pmd) (pmd_val(pmd) & ~PAGE_MASK) + +static inline void pte_clear(struct mm_struct *mm, + unsigned long addr, pte_t *ptep) +{ + pte_t null; + + pte_val(null) = (addr >> PAGE_SHIFT) & 0xf; + + set_pte_at(mm, addr, ptep, null); + flush_tlb_one(addr); +} + +/* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ +#define mk_pte(page, prot) (pfn_pte(page_to_pfn(page), prot)) + +#define pte_unmap(pte) do { } while (0) + +/* + * Conversion functions: convert a page and protection to a page entry, + * and a page entry and page directory to the page they refer to. + */ +#define pmd_phys(pmd) virt_to_phys((void *)pmd_val(pmd)) +#define pmd_page(pmd) (pfn_to_page(pmd_phys(pmd) >> PAGE_SHIFT)) +#define pmd_page_vaddr(pmd) pmd_val(pmd) + +#define pte_offset_map(dir, addr) \ + ((pte_t *) page_address(pmd_page(*dir)) + \ + (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))) + +/* to find an entry in a kernel page-table-directory */ +#define pgd_offset_k(addr) pgd_offset(&init_mm, addr) + +/* Get the address to the PTE for a vaddr in specfic directory */ +#define pte_offset_kernel(dir, addr) \ + ((pte_t *) pmd_page_vaddr(*(dir)) + \ + (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))) + +#define pte_ERROR(e) \ + pr_err("%s:%d: bad pte %08lx.\n", \ + __FILE__, __LINE__, pte_val(e)) +#define pgd_ERROR(e) \ + pr_err("%s:%d: bad pgd %08lx.\n", \ + __FILE__, __LINE__, pgd_val(e)) + +/* + * Encode and decode a swap entry (must be !pte_none(pte) && !pte_present(pte) + * && !pte_file(pte)): + * + * 31 30 29 28 27 26 25 24 23 22 21 20 19 18 ... 1 0 + * 0 0 0 0 type. 0 0 0 0 0 0 offset......... + * + * This gives us up to 2**2 = 4 swap files and 2**20 * 4K = 4G per swap file. + * + * Note that the offset field is always non-zero, thus !pte_none(pte) is always + * true. + */ +#define __swp_type(swp) (((swp).val >> 26) & 0x3) +#define __swp_offset(swp) ((swp).val & 0xfffff) +#define __swp_entry(type, off) ((swp_entry_t) { (((type) & 0x3) << 26) \ + | ((off) & 0xfffff) }) +#define __swp_entry_to_pte(swp) ((pte_t) { (swp).val }) +#define __pte_to_swp_entry(pte) ((swp_entry_t) { pte_val(pte) }) + +/* Encode and decode a nonlinear file mapping entry */ +#define PTE_FILE_MAX_BITS 25 +#define pte_to_pgoff(pte) (pte_val(pte) & 0x1ffffff) +#define pgoff_to_pte(off) __pte(((off) & 0x1ffffff) | _PAGE_FILE) + +#define kern_addr_valid(addr) (1) + +#include + +#define pgtable_cache_init() do { } while (0) + +extern void __init paging_init(void); +extern void __init mmu_init(void); + +extern void update_mmu_cache(struct vm_area_struct *vma, + unsigned long address, pte_t *pte); + +#endif /* _ASM_NIOS2_PGTABLE_H */ diff --git a/arch/nios2/include/asm/processor.h b/arch/nios2/include/asm/processor.h new file mode 100644 index 0000000000000..aeff62f08fa22 --- /dev/null +++ b/arch/nios2/include/asm/processor.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * Copyright (C) 2001 Ken Hill (khill@microtronix.com) + * Vic Phillips (vic@microtronix.com) + * + * based on SPARC asm/processor_32.h which is: + * + * Copyright (C) 1994 David S. Miller + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_PROCESSOR_H +#define _ASM_NIOS2_PROCESSOR_H + +#include +#include + +#define NIOS2_FLAG_KTHREAD 0x00000001 /* task is a kernel thread */ + +#define NIOS2_OP_NOP 0x1883a +#define NIOS2_OP_BREAK 0x3da03a + +#ifdef __KERNEL__ + +#define STACK_TOP TASK_SIZE +#define STACK_TOP_MAX STACK_TOP + +#endif /* __KERNEL__ */ + +#ifndef __ASSEMBLY__ + +/* + * Default implementation of macro that returns current + * instruction pointer ("program counter"). + */ +#define current_text_addr() ({ __label__ _l; _l: &&_l; }) + +# define TASK_SIZE 0x7FFF0000UL +# define TASK_UNMAPPED_BASE (PAGE_ALIGN(TASK_SIZE / 3)) + +/* The Nios processor specific thread struct. */ +struct thread_struct { + struct pt_regs *kregs; + + /* Context switch saved kernel state. */ + unsigned long ksp; + unsigned long kpsr; +}; + +#define INIT_MMAP \ + { &init_mm, (0), (0), __pgprot(0x0), VM_READ | VM_WRITE | VM_EXEC } + +# define INIT_THREAD { \ + .kregs = NULL, \ + .ksp = 0, \ + .kpsr = 0, \ +} + +extern void start_thread(struct pt_regs *regs, unsigned long pc, + unsigned long sp); + +struct task_struct; + +/* Free all resources held by a thread. */ +static inline void release_thread(struct task_struct *dead_task) +{ +} + +/* Free current thread data structures etc.. */ +static inline void exit_thread(void) +{ +} + +/* Return saved PC of a blocked thread. */ +#define thread_saved_pc(tsk) ((tsk)->thread.kregs->ea) + +extern unsigned long get_wchan(struct task_struct *p); + +/* Prepare to copy thread state - unlazy all lazy status */ +#define prepare_to_copy(tsk) do { } while (0) + +#define task_pt_regs(p) \ + ((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1) + +/* Used by procfs */ +#define KSTK_EIP(tsk) ((tsk)->thread.kregs->ea) +#define KSTK_ESP(tsk) ((tsk)->thread.kregs->sp) + +#define cpu_relax() barrier() + +#endif /* __ASSEMBLY__ */ + +#endif /* _ASM_NIOS2_PROCESSOR_H */ diff --git a/arch/nios2/include/asm/prom.h b/arch/nios2/include/asm/prom.h new file mode 100644 index 0000000000000..7266e77152c56 --- /dev/null +++ b/arch/nios2/include/asm/prom.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2010 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_PROM_H +#define _ASM_NIOS2_PROM_H + +extern unsigned long early_altera_uart_or_juart_console(void); + +#endif /* _ASM_NIOS2_PROM_H */ diff --git a/arch/nios2/include/asm/ptrace.h b/arch/nios2/include/asm/ptrace.h new file mode 100644 index 0000000000000..ca8588ed8c32e --- /dev/null +++ b/arch/nios2/include/asm/ptrace.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * based on m68k asm/processor.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_PTRACE_H +#define _ASM_NIOS2_PTRACE_H + +#include + +#ifndef __ASSEMBLY__ + +/* Arbitrarily choose the same ptrace numbers as used by the Sparc code. */ +#define PTRACE_GETREGS 12 +#define PTRACE_SETREGS 13 + +/* + * Supervisor mode + */ + +# define user_mode(regs) (((regs)->estatus & ESTATUS_EU)) + + +#define instruction_pointer(regs) ((regs)->ra) +#define profile_pc(regs) instruction_pointer(regs) +#define user_stack_pointer(regs) ((regs)->sp) +extern void show_regs(struct pt_regs *); + +#define current_pt_regs() \ + ((struct pt_regs *)((unsigned long)current_thread_info() + THREAD_SIZE)\ + - 1) +#endif /* __ASSEMBLY__ */ +#endif /* _ASM_NIOS2_PTRACE_H */ diff --git a/arch/nios2/include/asm/registers.h b/arch/nios2/include/asm/registers.h new file mode 100644 index 0000000000000..0e6ffef08b20e --- /dev/null +++ b/arch/nios2/include/asm/registers.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_REGISTERS_H +#define _ASM_NIOS2_REGISTERS_H + +/* control register numbers */ +#define CTL_STATUS 0 +#define CTL_ESTATUS 1 +#define CTL_BSTATUS 2 +#define CTL_IENABLE 3 +#define CTL_IPENDING 4 +#define CTL_CPUID 5 +#define CTL_RSV1 6 +#define CTL_EXCEPTION 7 +#define CTL_PTEADDR 8 +#define CTL_TLBACC 9 +#define CTL_TLBMISC 10 +#define CTL_RSV2 11 +#define CTL_BADADDR 12 +#define CTL_CONFIG 13 +#define CTL_MPUBASE 14 +#define CTL_MPUACC 15 + +/* access control registers using GCC builtins */ +#define RDCTL(r) __builtin_rdctl(r) +#define WRCTL(r, v) __builtin_wrctl(r, v) + +/* status register bits */ +#define STATUS_PIE (1 << 0) /* processor interrupt enable */ +#define STATUS_U (1 << 1) /* user mode */ +#define STATUS_EH (1 << 2) /* Exception mode */ + +/* estatus register bits */ +#define ESTATUS_EPIE (1 << 0) /* processor interrupt enable */ +#define ESTATUS_EU (1 << 1) /* user mode */ +#define ESTATUS_EH (1 << 2) /* Exception mode */ + +/* tlbmisc register bits */ +#define TLBMISC_PID_SHIFT 4 +#define TLBMISC_PID_MASK ((1UL << cpuinfo.tlb_pid_num_bits) - 1) +#define TLBMISC_WAY_MASK 0xf +#define TLBMISC_WAY_SHIFT 20 + +#define TLBMISC_PID (TLBMISC_PID_MASK << TLBMISC_PID_SHIFT) /* TLB PID */ +#define TLBMISC_WE (1 << 18) /* TLB write enable */ +#define TLBMISC_RD (1 << 19) /* TLB read */ +#define TLBMISC_WAY (TLBMISC_WAY_MASK << TLBMISC_WAY_SHIFT) /* TLB way */ + +#endif /* _ASM_NIOS2_REGISTERS_H */ diff --git a/arch/nios2/include/asm/setup.h b/arch/nios2/include/asm/setup.h new file mode 100644 index 0000000000000..a3e05a7f977a5 --- /dev/null +++ b/arch/nios2/include/asm/setup.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_SETUP_H +#define _ASM_NIOS2_SETUP_H + +#include + +#ifndef __ASSEMBLY__ +# ifdef __KERNEL__ + +extern char exception_handler_hook[]; +extern char fast_handler[]; +extern char fast_handler_end[]; + +extern void pagetable_init(void); + +extern void setup_early_printk(void); + +# endif/* __KERNEL__ */ +#endif /* __ASSEMBLY__ */ + +#endif /* _ASM_NIOS2_SETUP_H */ diff --git a/arch/nios2/include/asm/signal.h b/arch/nios2/include/asm/signal.h new file mode 100644 index 0000000000000..e2f8deedda2b1 --- /dev/null +++ b/arch/nios2/include/asm/signal.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2004, Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ASM_NIOS2_SIGNAL_H +#define _ASM_NIOS2_SIGNAL_H + +#include + +/* Most things should be clean enough to redefine this at will, if care + is taken to make libc match. */ + +#define _NSIG 64 +#define _NSIG_BPW 32 +#define _NSIG_WORDS (_NSIG / _NSIG_BPW) + +typedef unsigned long old_sigset_t; /* at least 32 bits */ + +typedef struct { + unsigned long sig[_NSIG_WORDS]; +} sigset_t; + +#include + +#define __ARCH_HAS_SA_RESTORER + +#include +#undef __HAVE_ARCH_SIG_BITOPS + + +#endif /* _ASM_NIOS2_SIGNAL_H */ diff --git a/arch/nios2/include/asm/string.h b/arch/nios2/include/asm/string.h new file mode 100644 index 0000000000000..14dd570d64f70 --- /dev/null +++ b/arch/nios2/include/asm/string.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_STRING_H +#define _ASM_NIOS2_STRING_H + +#ifdef __KERNEL__ + +#define __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMMOVE + +extern void *memset(void *s, int c, size_t count); +extern void *memcpy(void *d, const void *s, size_t count); +extern void *memmove(void *d, const void *s, size_t count); + +#endif /* __KERNEL__ */ + +#endif /* _ASM_NIOS2_STRING_H */ diff --git a/arch/nios2/include/asm/switch_to.h b/arch/nios2/include/asm/switch_to.h new file mode 100644 index 0000000000000..c47b3f4afbcdd --- /dev/null +++ b/arch/nios2/include/asm/switch_to.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#ifndef _ASM_NIOS2_SWITCH_TO_H +#define _ASM_NIOS2_SWITCH_TO_H + +/* + * switch_to(n) should switch tasks to task ptr, first checking that + * ptr isn't the current task, in which case it does nothing. This + * also clears the TS-flag if the task we switched to has used the + * math co-processor latest. + */ +#define switch_to(prev, next, last) \ +{ \ + void *_last; \ + __asm__ __volatile__ ( \ + "mov r4, %1\n" \ + "mov r5, %2\n" \ + "call resume\n" \ + "mov %0,r4\n" \ + : "=r" (_last) \ + : "r" (prev), "r" (next) \ + : "r4", "r5", "r7", "r8", "ra"); \ + (last) = _last; \ +} + +#endif /* _ASM_NIOS2_SWITCH_TO_H */ diff --git a/arch/nios2/include/asm/thread_info.h b/arch/nios2/include/asm/thread_info.h new file mode 100644 index 0000000000000..1f266575beb51 --- /dev/null +++ b/arch/nios2/include/asm/thread_info.h @@ -0,0 +1,120 @@ +/* + * NiosII low-level thread information + * + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * Based on asm/thread_info_no.h from m68k which is: + * + * Copyright (C) 2002 David Howells + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_THREAD_INFO_H +#define _ASM_NIOS2_THREAD_INFO_H + +#ifdef __KERNEL__ + +/* + * Size of the kernel stack for each process. + */ +#define THREAD_SIZE_ORDER 1 +#define THREAD_SIZE 8192 /* 2 * PAGE_SIZE */ + +#ifndef __ASSEMBLY__ + +typedef struct { + unsigned long seg; +} mm_segment_t; + +/* + * low level task data that entry.S needs immediate access to + * - this struct should fit entirely inside of one cache line + * - this struct shares the supervisor stack pages + * - if the contents of this structure are changed, the assembly constants + * must also be changed + */ +struct thread_info { + struct task_struct *task; /* main task structure */ + struct exec_domain *exec_domain; /* execution domain */ + unsigned long flags; /* low level flags */ + __u32 cpu; /* current CPU */ + int preempt_count; /* 0 => preemptable,<0 => BUG */ + mm_segment_t addr_limit; /* thread address space: + 0-0x7FFFFFFF for user-thead + 0-0xFFFFFFFF for kernel-thread + */ + struct restart_block restart_block; + struct pt_regs *regs; +}; + +/* + * macros/functions for gaining access to the thread information structure + * + * preempt_count needs to be 1 initially, until the scheduler is functional. + */ +#define INIT_THREAD_INFO(tsk) \ +{ \ + .task = &tsk, \ + .exec_domain = &default_exec_domain, \ + .flags = 0, \ + .cpu = 0, \ + .preempt_count = INIT_PREEMPT_COUNT, \ + .addr_limit = KERNEL_DS, \ + .restart_block = { \ + .fn = do_no_restart_syscall, \ + }, \ +} + +#define init_thread_info (init_thread_union.thread_info) +#define init_stack (init_thread_union.stack) + +/* how to get the thread information struct from C */ +static inline struct thread_info *current_thread_info(void) +{ + register unsigned long sp asm("sp"); + + return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); +} +#endif /* !__ASSEMBLY__ */ + +/* + * thread information flags + * - these are process state flags that various assembly files may need to + * access + * - pending work-to-be-done flags are in LSW + * - other flags in MSW + */ +#define TIF_SYSCALL_TRACE 0 /* syscall trace active */ +#define TIF_NOTIFY_RESUME 1 /* resumption notification requested */ +#define TIF_SIGPENDING 2 /* signal pending */ +#define TIF_NEED_RESCHED 3 /* rescheduling necessary */ +#define TIF_MEMDIE 4 /* is terminating due to OOM killer */ +#define TIF_SECCOMP 5 /* secure computing */ +#define TIF_SYSCALL_AUDIT 6 /* syscall auditing active */ +#define TIF_RESTORE_SIGMASK 9 /* restore signal mask in do_signal() */ + +#define TIF_POLLING_NRFLAG 16 /* true if poll_idle() is polling + TIF_NEED_RESCHED */ + +#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) +#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) +#define _TIF_SIGPENDING (1 << TIF_SIGPENDING) +#define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED) +#define _TIF_SECCOMP (1 << TIF_SECCOMP) +#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) +#define _TIF_RESTORE_SIGMASK (1 << TIF_RESTORE_SIGMASK) +#define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG) + +/* work to do on interrupt/exception return */ +#define _TIF_WORK_MASK 0x0000FFFE + +/* work to do on any return to u-space */ +# define _TIF_ALLWORK_MASK 0x0000FFFF + +#endif /* __KERNEL__ */ + +#endif /* _ASM_NIOS2_THREAD_INFO_H */ diff --git a/arch/nios2/include/asm/timex.h b/arch/nios2/include/asm/timex.h new file mode 100644 index 0000000000000..524b47d79d3da --- /dev/null +++ b/arch/nios2/include/asm/timex.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_TIMEX_H +#define _ASM_NIOS2_TIMEX_H + +/* Supply dummy tick-rate. Real value will be read from devicetree */ +#define CLOCK_TICK_RATE (HZ * 100000UL) + +#include + +#endif diff --git a/arch/nios2/include/asm/tlb.h b/arch/nios2/include/asm/tlb.h new file mode 100644 index 0000000000000..d3bc648e08b5d --- /dev/null +++ b/arch/nios2/include/asm/tlb.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_TLB_H +#define _ASM_NIOS2_TLB_H + +#define tlb_flush(tlb) flush_tlb_mm((tlb)->mm) + +extern void set_mmu_pid(unsigned long pid); + +/* + * NiosII doesn't need any special per-pte or per-vma handling, except + * we need to flush cache for the area to be unmapped. + */ +#define tlb_start_vma(tlb, vma) \ + do { \ + if (!tlb->fullmm) \ + flush_cache_range(vma, vma->vm_start, vma->vm_end); \ + } while (0) + +#define tlb_end_vma(tlb, vma) do { } while (0) +#define __tlb_remove_tlb_entry(tlb, ptep, address) do { } while (0) + +#include +#include + +#endif /* _ASM_NIOS2_TLB_H */ diff --git a/arch/nios2/include/asm/tlbflush.h b/arch/nios2/include/asm/tlbflush.h new file mode 100644 index 0000000000000..e19652fca1c68 --- /dev/null +++ b/arch/nios2/include/asm/tlbflush.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_TLBFLUSH_H +#define _ASM_NIOS2_TLBFLUSH_H + +struct mm_struct; + +/* + * TLB flushing: + * + * - flush_tlb_all() flushes all processes TLB entries + * - flush_tlb_mm(mm) flushes the specified mm context TLB entries + * - flush_tlb_page(vma, vmaddr) flushes one page + * - flush_tlb_range(vma, start, end) flushes a range of pages + * - flush_tlb_kernel_range(start, end) flushes a range of kernel pages + */ +extern void flush_tlb_all(void); +extern void flush_tlb_mm(struct mm_struct *mm); +extern void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end); +extern void flush_tlb_kernel_range(unsigned long start, unsigned long end); +extern void flush_tlb_one(unsigned long vaddr); + +static inline void flush_tlb_page(struct vm_area_struct *vma, + unsigned long addr) +{ + flush_tlb_one(addr); +} + +#endif /* _ASM_NIOS2_TLBFLUSH_H */ diff --git a/arch/nios2/include/asm/traps.h b/arch/nios2/include/asm/traps.h new file mode 100644 index 0000000000000..82a48473280d8 --- /dev/null +++ b/arch/nios2/include/asm/traps.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_TRAPS_H +#define _ASM_NIOS2_TRAPS_H + +#define TRAP_ID_SYSCALL 0 + +#ifndef __ASSEMBLY__ +void _exception(int signo, struct pt_regs *regs, int code, unsigned long addr); +#endif + +#endif /* _ASM_NIOS2_TRAPS_H */ diff --git a/arch/nios2/include/asm/uaccess.h b/arch/nios2/include/asm/uaccess.h new file mode 100644 index 0000000000000..3e36f536afaba --- /dev/null +++ b/arch/nios2/include/asm/uaccess.h @@ -0,0 +1,233 @@ +/* + * User space memory access functions for Nios II + * + * Copyright (C) 2010-2011, Tobias Klauser + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * Based on asm/uaccess.h from m68knommu + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_UACCESS_H +#define _ASM_NIOS2_UACCESS_H + +#include +#include +#include + +#include + +#define VERIFY_READ 0 +#define VERIFY_WRITE 1 + +/* + * The exception table consists of pairs of addresses: the first is the + * address of an instruction that is allowed to fault, and the second is + * the address at which the program should continue. No registers are + * modified, so it is entirely up to the continuation code to figure out + * what to do. + * + * All the routines below use bits of fixup code that are out of line + * with the main instruction path. This means when everything is well, + * we don't even have to jump over them. Further, they do not intrude + * on our cache or tlb entries. + */ +struct exception_table_entry { + unsigned long insn; + unsigned long fixup; +}; + +extern int fixup_exception(struct pt_regs *regs); + +/* + * Segment stuff + */ +#define MAKE_MM_SEG(s) ((mm_segment_t) { (s) }) +#define USER_DS MAKE_MM_SEG(0x80000000UL) +#define KERNEL_DS MAKE_MM_SEG(0) + +#define get_ds() (KERNEL_DS) + +#define get_fs() (current_thread_info()->addr_limit) +#define set_fs(seg) (current_thread_info()->addr_limit = (seg)) + +#define segment_eq(a, b) ((a).seg == (b).seg) + +#define __access_ok(addr, len) \ + (((signed long)(((long)get_fs().seg) & \ + ((long)(addr) | (((long)(addr)) + (len)) | (len)))) == 0) + +#define access_ok(type, addr, len) \ + likely(__access_ok((unsigned long)(addr), (unsigned long)(len))) + +# define __EX_TABLE_SECTION ".section __ex_table,\"a\"\n" + +/* + * Zero Userspace + */ + +static inline unsigned long __must_check __clear_user(void __user *to, + unsigned long n) +{ + __asm__ __volatile__ ( + "1: stb zero, 0(%1)\n" + " addi %0, %0, -1\n" + " addi %1, %1, 1\n" + " bne %0, zero, 1b\n" + "2:\n" + __EX_TABLE_SECTION + ".word 1b, 2b\n" + ".previous\n" + : "=r" (n), "=r" (to) + : "0" (n), "1" (to) + ); + + return n; +} + +static inline unsigned long __must_check clear_user(void __user *to, + unsigned long n) +{ + if (!access_ok(VERIFY_WRITE, to, n)) + return n; + return __clear_user(to, n); +} + +extern long __copy_from_user(void *to, const void __user *from, + unsigned long n); +extern long __copy_to_user(void __user *to, const void *from, unsigned long n); + +static inline long copy_from_user(void *to, const void __user *from, + unsigned long n) +{ + if (!access_ok(VERIFY_READ, from, n)) + return n; + return __copy_from_user(to, from, n); +} + +static inline long copy_to_user(void __user *to, const void *from, + unsigned long n) +{ + if (!access_ok(VERIFY_WRITE, to, n)) + return n; + return __copy_to_user(to, from, n); +} + +extern long strncpy_from_user(char *__to, const char __user *__from, + long __len); +extern long strnlen_user(const char __user *s, long n); + +#define __copy_from_user_inatomic __copy_from_user +#define __copy_to_user_inatomic __copy_to_user + +/* Optimized macros */ +#define __get_user_asm(val, insn, addr, err) \ +{ \ + __asm__ __volatile__( \ + " movi %0, %3\n" \ + "1: " insn " %1, 0(%2)\n" \ + " movi %0, 0\n" \ + "2:\n" \ + " .section __ex_table,\"a\"\n" \ + " .word 1b, 2b\n" \ + " .previous" \ + : "=&r" (err), "=r" (val) \ + : "r" (addr), "i" (-EFAULT)); \ +} + +#define __get_user_unknown(val, size, ptr, err) do { \ + err = 0; \ + if (copy_from_user(&(val), ptr, size)) { \ + err = -EFAULT; \ + } \ + } while (0) + +#define __get_user_common(val, size, ptr, err) \ +do { \ + switch (size) { \ + case 1: \ + __get_user_asm(val, "ldbu", ptr, err); \ + break; \ + case 2: \ + __get_user_asm(val, "ldhu", ptr, err); \ + break; \ + case 4: \ + __get_user_asm(val, "ldw", ptr, err); \ + break; \ + default: \ + __get_user_unknown(val, size, ptr, err); \ + break; \ + } \ +} while (0) + +#define __get_user(x, ptr) \ + ({ \ + long __gu_err = -EFAULT; \ + const __typeof__(*(ptr)) __user *__gu_ptr = (ptr); \ + unsigned long __gu_val; \ + __get_user_common(__gu_val, sizeof(*(ptr)), __gu_ptr, __gu_err);\ + (x) = (__typeof__(x))__gu_val; \ + __gu_err; \ + }) + +#define get_user(x, ptr) \ +({ \ + long __gu_err = -EFAULT; \ + const __typeof__(*(ptr)) __user *__gu_ptr = (ptr); \ + unsigned long __gu_val = 0; \ + if (access_ok(VERIFY_READ, __gu_ptr, sizeof(*__gu_ptr))) \ + __get_user_common(__gu_val, sizeof(*__gu_ptr), \ + __gu_ptr, __gu_err); \ + (x) = (__typeof__(x))__gu_val; \ + __gu_err; \ +}) + +#define __put_user_asm(val, insn, ptr, err) \ +{ \ + __asm__ __volatile__( \ + " movi %0, %3\n" \ + "1: " insn " %1, 0(%2)\n" \ + " movi %0, 0\n" \ + "2:\n" \ + " .section __ex_table,\"a\"\n" \ + " .word 1b, 2b\n" \ + " .previous\n" \ + : "=&r" (err) \ + : "r" (val), "r" (ptr), "i" (-EFAULT)); \ +} + +#define put_user(x, ptr) \ +({ \ + long __pu_err = -EFAULT; \ + __typeof__(*(ptr)) __user *__pu_ptr = (ptr); \ + __typeof__(*(ptr)) __pu_val = (__typeof(*ptr))(x); \ + if (access_ok(VERIFY_WRITE, __pu_ptr, sizeof(*__pu_ptr))) { \ + switch (sizeof(*__pu_ptr)) { \ + case 1: \ + __put_user_asm(__pu_val, "stb", __pu_ptr, __pu_err); \ + break; \ + case 2: \ + __put_user_asm(__pu_val, "sth", __pu_ptr, __pu_err); \ + break; \ + case 4: \ + __put_user_asm(__pu_val, "stw", __pu_ptr, __pu_err); \ + break; \ + default: \ + /* XXX: This looks wrong... */ \ + __pu_err = 0; \ + if (copy_to_user(__pu_ptr, &(__pu_val), \ + sizeof(*__pu_ptr))) \ + __pu_err = -EFAULT; \ + break; \ + } \ + } \ + __pu_err; \ +}) + +#define __put_user(x, ptr) put_user(x, ptr) + +#endif /* _ASM_NIOS2_UACCESS_H */ diff --git a/arch/nios2/include/asm/ucontext.h b/arch/nios2/include/asm/ucontext.h new file mode 100644 index 0000000000000..5870ef4e92fba --- /dev/null +++ b/arch/nios2/include/asm/ucontext.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * derived from m68knommu + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_UCONTEXT_H +#define _ASM_NIOS2_UCONTEXT_H + +typedef int greg_t; +#define NGREG 32 +typedef greg_t gregset_t[NGREG]; + +struct mcontext { + int version; + gregset_t gregs; +}; + +#define MCONTEXT_VERSION 2 + +struct ucontext { + unsigned long uc_flags; + struct ucontext *uc_link; + stack_t uc_stack; + struct mcontext uc_mcontext; + sigset_t uc_sigmask; /* mask last for extensibility */ +}; + +#endif diff --git a/arch/nios2/include/asm/unistd.h b/arch/nios2/include/asm/unistd.h new file mode 100644 index 0000000000000..7920cf3b08e58 --- /dev/null +++ b/arch/nios2/include/asm/unistd.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2010 Frans Meulenbroeks + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_UNISTD_H +#define _ASM_NIOS2_UNISTD_H + +#include + +#define __ARCH_WANT_IPC_PARSE_VERSION +#define __ARCH_WANT_STAT64 +#define __ARCH_WANT_SYS_ALARM +#define __ARCH_WANT_SYS_GETHOSTNAME +#define __ARCH_WANT_SYS_IPC +#define __ARCH_WANT_SYS_PAUSE +#define __ARCH_WANT_SYS_SGETMASK +#define __ARCH_WANT_SYS_SIGNAL +#define __ARCH_WANT_SYS_TIME +#define __ARCH_WANT_SYS_UTIME +#define __ARCH_WANT_SYS_WAITPID +#define __ARCH_WANT_SYS_SOCKETCALL +#define __ARCH_WANT_SYS_FADVISE64 +#define __ARCH_WANT_SYS_GETPGRP +#define __ARCH_WANT_SYS_LLSEEK +#define __ARCH_WANT_SYS_NICE +#define __ARCH_WANT_SYS_OLDUMOUNT +#define __ARCH_WANT_SYS_OLD_SELECT +#define __ARCH_WANT_SYS_OLD_UNAME +#define __ARCH_WANT_SYS_SIGPENDING +#define __ARCH_WANT_SYS_SIGPROCMASK +#define __ARCH_WANT_SYS_VFORK +#define __ARCH_WANT_SYS_FORK + +#endif /* _ASM_NIOS2_UNISTD_H */ diff --git a/arch/nios2/include/uapi/asm/Kbuild b/arch/nios2/include/uapi/asm/Kbuild new file mode 100644 index 0000000000000..f7cb6faea19ae --- /dev/null +++ b/arch/nios2/include/uapi/asm/Kbuild @@ -0,0 +1,17 @@ +include include/uapi/asm-generic/Kbuild.asm + +header-y += byteorder.h +header-y += elf.h +header-y += fcntl.h +header-y += ioctls.h +header-y += kvm_para.h +header-y += poll.h +header-y += posix_types.h +header-y += processor.h +header-y += ptrace.h +header-y += setup.h +header-y += sigcontext.h +header-y += signal.h +header-y += stat.h +header-y += swab.h +header-y += unistd.h diff --git a/arch/nios2/include/uapi/asm/byteorder.h b/arch/nios2/include/uapi/asm/byteorder.h new file mode 100644 index 0000000000000..3ab5dc20d757c --- /dev/null +++ b/arch/nios2/include/uapi/asm/byteorder.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009 Thomas Chou + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ASM_NIOS2_BYTEORDER_H +#define _ASM_NIOS2_BYTEORDER_H + +#include + +#endif diff --git a/arch/nios2/include/uapi/asm/elf.h b/arch/nios2/include/uapi/asm/elf.h new file mode 100644 index 0000000000000..a5b91ae5cf56f --- /dev/null +++ b/arch/nios2/include/uapi/asm/elf.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#ifndef _UAPI_ASM_NIOS2_ELF_H +#define _UAPI_ASM_NIOS2_ELF_H + +#include + +/* Relocation types */ +#define R_NIOS2_NONE 0 +#define R_NIOS2_S16 1 +#define R_NIOS2_U16 2 +#define R_NIOS2_PCREL16 3 +#define R_NIOS2_CALL26 4 +#define R_NIOS2_IMM5 5 +#define R_NIOS2_CACHE_OPX 6 +#define R_NIOS2_IMM6 7 +#define R_NIOS2_IMM8 8 +#define R_NIOS2_HI16 9 +#define R_NIOS2_LO16 10 +#define R_NIOS2_HIADJ16 11 +#define R_NIOS2_BFD_RELOC_32 12 +#define R_NIOS2_BFD_RELOC_16 13 +#define R_NIOS2_BFD_RELOC_8 14 +#define R_NIOS2_GPREL 15 +#define R_NIOS2_GNU_VTINHERIT 16 +#define R_NIOS2_GNU_VTENTRY 17 +#define R_NIOS2_UJMP 18 +#define R_NIOS2_CJMP 19 +#define R_NIOS2_CALLR 20 +#define R_NIOS2_ALIGN 21 +/* Keep this the last entry. */ +#define R_NIOS2_NUM 22 + +typedef unsigned long elf_greg_t; + +#define ELF_NGREG \ + ((sizeof(struct pt_regs) + sizeof(struct switch_stack)) / \ + sizeof(elf_greg_t)) +typedef elf_greg_t elf_gregset_t[ELF_NGREG]; + +typedef unsigned long elf_fpregset_t; + +/* + * These are used to set parameters in the core dumps. + */ +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2LSB +#define ELF_ARCH EM_ALTERA_NIOS2 + +#endif /* _UAPI_ASM_NIOS2_ELF_H */ diff --git a/arch/nios2/include/uapi/asm/fcntl.h b/arch/nios2/include/uapi/asm/fcntl.h new file mode 100644 index 0000000000000..76d4a69f4fbdd --- /dev/null +++ b/arch/nios2/include/uapi/asm/fcntl.h @@ -0,0 +1,36 @@ +/* + * This file came from the m68k port. + * + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ASM_NIOS2_FCNTL_H +#define _ASM_NIOS2_FCNTL_H + +#define O_DIRECTORY 040000 /* must be a directory */ +#define O_NOFOLLOW 0100000 /* don't follow links */ +#define O_DIRECT 0200000 /* direct disk access hint - currently + ignored */ +#define O_LARGEFILE 0400000 + +#include + +#endif /* _ASM_NIOS2_FCNTL_H */ diff --git a/arch/nios2/include/uapi/asm/ioctls.h b/arch/nios2/include/uapi/asm/ioctls.h new file mode 100644 index 0000000000000..ce0b09c3eaa26 --- /dev/null +++ b/arch/nios2/include/uapi/asm/ioctls.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _ASM_NIOS2_IOCTLS_H +#define _ASM_NIOS2_IOCTLS_H + +#define FIOQSIZE 0x545E +#include + +#endif diff --git a/arch/nios2/include/uapi/asm/poll.h b/arch/nios2/include/uapi/asm/poll.h new file mode 100644 index 0000000000000..5bbf747031ccc --- /dev/null +++ b/arch/nios2/include/uapi/asm/poll.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009 Thomas Chou + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ASM_NIOS2_POLL_H +#define _ASM_NIOS2_POLL_H + +#define POLLWRNORM POLLOUT +#define POLLWRBAND 256 + +#include + +#endif diff --git a/arch/nios2/include/uapi/asm/ptrace.h b/arch/nios2/include/uapi/asm/ptrace.h new file mode 100644 index 0000000000000..e37ca15350ea6 --- /dev/null +++ b/arch/nios2/include/uapi/asm/ptrace.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * based on m68k asm/processor.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _UAPI_ASM_NIOS2_PTRACE_H +#define _UAPI_ASM_NIOS2_PTRACE_H + +#ifndef __ASSEMBLY__ + +/* + * Register numbers used by 'ptrace' system call interface. + */ + +/* GP registers */ +#define PTR_R0 0 +#define PTR_R1 1 +#define PTR_R2 2 +#define PTR_R3 3 +#define PTR_R4 4 +#define PTR_R5 5 +#define PTR_R6 6 +#define PTR_R7 7 +#define PTR_R8 8 +#define PTR_R9 9 +#define PTR_R10 10 +#define PTR_R11 11 +#define PTR_R12 12 +#define PTR_R13 13 +#define PTR_R14 14 +#define PTR_R15 15 +#define PTR_R16 16 +#define PTR_R17 17 +#define PTR_R18 18 +#define PTR_R19 19 +#define PTR_R20 20 +#define PTR_R21 21 +#define PTR_R22 22 +#define PTR_R23 23 +#define PTR_R24 24 +#define PTR_R25 25 +#define PTR_GP 26 +#define PTR_SP 27 +#define PTR_FP 28 +#define PTR_EA 29 +#define PTR_BA 30 +#define PTR_RA 31 +/* Control registers */ +#define PTR_PC 32 +#define PTR_STATUS 33 +#define PTR_ESTATUS 34 +#define PTR_BSTATUS 35 +#define PTR_IENABLE 36 +#define PTR_IPENDING 37 +#define PTR_CPUID 38 +#define PTR_CTL6 39 +#define PTR_CTL7 40 +#define PTR_PTEADDR 41 +#define PTR_TLBACC 42 +#define PTR_TLBMISC 43 + +/* Text/data offsets, needed by gdbserver */ +#define PT_TEXT_ADDR (44*4) +#define PT_TEXT_END_ADDR (45*4) +#define PT_DATA_ADDR (46*4) + +/* this struct defines the way the registers are stored on the + stack during a system call. + + There is a fake_regs in setup.c that has to match pt_regs.*/ + +struct pt_regs { + unsigned long r8; /* r8-r15 Caller-saved GP registers */ + unsigned long r9; + unsigned long r10; + unsigned long r11; + unsigned long r12; + unsigned long r13; + unsigned long r14; + unsigned long r15; + unsigned long r1; /* Assembler temporary */ + unsigned long r2; /* Retval LS 32bits */ + unsigned long r3; /* Retval MS 32bits */ + unsigned long r4; /* r4-r7 Register arguments */ + unsigned long r5; + unsigned long r6; + unsigned long r7; + unsigned long orig_r2; /* Copy of r2 ?? */ + unsigned long ra; /* Return address */ + unsigned long fp; /* Frame pointer */ + unsigned long sp; /* Stack pointer */ + unsigned long gp; /* Global pointer */ + unsigned long estatus; + unsigned long ea; /* Exception return address (pc) */ + unsigned long orig_r7; +}; + +/* + * This is the extended stack used by signal handlers and the context + * switcher: it's pushed after the normal "struct pt_regs". + */ +struct switch_stack { + unsigned long r16; /* r16-r23 Callee-saved GP registers */ + unsigned long r17; + unsigned long r18; + unsigned long r19; + unsigned long r20; + unsigned long r21; + unsigned long r22; + unsigned long r23; + unsigned long fp; + unsigned long gp; + unsigned long ra; +}; + +#endif /* __ASSEMBLY__ */ +#endif /* _UAPI_ASM_NIOS2_PTRACE_H */ diff --git a/arch/nios2/include/uapi/asm/sigcontext.h b/arch/nios2/include/uapi/asm/sigcontext.h new file mode 100644 index 0000000000000..00223471ee8a8 --- /dev/null +++ b/arch/nios2/include/uapi/asm/sigcontext.h @@ -0,0 +1,35 @@ +/* + * Taken from the m68knommu. + * + * Copyright (C) 2004, Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _ASM_NIOS2_SIGCONTEXT_H +#define _ASM_NIOS2_SIGCONTEXT_H + +#include + +struct sigcontext { + struct pt_regs regs; + unsigned long sc_mask; /* old sigmask */ +}; + +#endif diff --git a/arch/nios2/include/uapi/asm/signal.h b/arch/nios2/include/uapi/asm/signal.h new file mode 100644 index 0000000000000..b89779e059cec --- /dev/null +++ b/arch/nios2/include/uapi/asm/signal.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2004, Microtronix Datacom Ltd. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _UAPI_ASM_NIOS2_SIGNAL_H +#define _UAPI_ASM_NIOS2_SIGNAL_H + +#include + +/* Avoid too many header ordering problems. */ +struct siginfo; + +#ifndef __KERNEL__ +/* Here we must cater to libcs that poke about in kernel headers. */ + +#define NSIG 32 +typedef unsigned long sigset_t; + +#endif /* __KERNEL__ */ + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPOLL SIGIO +/* +#define SIGLOST 29 +*/ +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED 31 + +/* These should not be considered constants from userland. */ +#define SIGRTMIN 32 +#define SIGRTMAX _NSIG + +/* + * SA_FLAGS values: + * + * SA_ONSTACK indicates that a registered stack_t will be used. + * SA_RESTART flag to get restarting signals (which were the default long ago) + * SA_NOCLDSTOP flag to turn off SIGCHLD when children stop. + * SA_RESETHAND clears the handler when the signal is delivered. + * SA_NOCLDWAIT flag on SIGCHLD to inhibit zombies. + * SA_NODEFER prevents the current signal from being masked in the handler. + * + * SA_ONESHOT and SA_NOMASK are the historical Linux names for the Single + * Unix names RESETHAND and NODEFER respectively. + */ +#define SA_NOCLDSTOP 0x00000001 +#define SA_NOCLDWAIT 0x00000002 /* not supported yet */ +#define SA_SIGINFO 0x00000004 +#define SA_ONSTACK 0x08000000 +#define SA_RESTART 0x10000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 + +#define SA_NOMASK SA_NODEFER +#define SA_ONESHOT SA_RESETHAND + +/* + * sigaltstack controls + */ +#define SS_ONSTACK 1 +#define SS_DISABLE 2 + +#define MINSIGSTKSZ 2048 +#define SIGSTKSZ 8192 + +#include + +#ifndef __KERNEL__ +/* Here we must cater to libcs that poke about in kernel headers. */ + +struct sigaction { + union { + __sighandler_t _sa_handler; + void (*_sa_sigaction)(int, struct siginfo *, void *); + } _u; + sigset_t sa_mask; + unsigned long sa_flags; + void (*sa_restorer)(void); +}; + +#define sa_handler _u._sa_handler +#define sa_sigaction (_u._sa_sigaction) + +#endif /* __KERNEL__ */ + +typedef struct sigaltstack { + void *ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + +#endif /* _UAPI_ASM_NIOS2_SIGNAL_H */ diff --git a/arch/nios2/include/uapi/asm/stat.h b/arch/nios2/include/uapi/asm/stat.h new file mode 100644 index 0000000000000..7909a7eef7086 --- /dev/null +++ b/arch/nios2/include/uapi/asm/stat.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _ASM_NIOS2_STAT_H +#define _ASM_NIOS2_STAT_H + +struct __old_kernel_stat { + unsigned short st_dev; + unsigned short st_ino; + unsigned short st_mode; + unsigned short st_nlink; + unsigned short st_uid; + unsigned short st_gid; + unsigned short st_rdev; + unsigned long st_size; + unsigned long st_atime; + unsigned long st_mtime; + unsigned long st_ctime; +}; + +struct stat { + unsigned short st_dev; + unsigned short __pad1; + unsigned long st_ino; + unsigned short st_mode; + unsigned short st_nlink; + unsigned short st_uid; + unsigned short st_gid; + unsigned short st_rdev; + unsigned short __pad2; + unsigned long st_size; + unsigned long st_blksize; + unsigned long st_blocks; + unsigned long st_atime; + unsigned long __unused1; + unsigned long st_mtime; + unsigned long __unused2; + unsigned long st_ctime; + unsigned long __unused3; + unsigned long __unused4; + unsigned long __unused5; +}; + +/* This matches struct stat64 in glibc2.1, hence the absolutely + * insane amounts of padding around dev_t's. + */ +struct stat64 { + unsigned long long st_dev; + unsigned char __pad1[4]; + +#define STAT64_HAS_BROKEN_ST_INO 1 + unsigned long __st_ino; + + unsigned int st_mode; + unsigned int st_nlink; + + unsigned long st_uid; + unsigned long st_gid; + + unsigned long long st_rdev; + unsigned char __pad3[4]; + + long long st_size; + unsigned long st_blksize; + unsigned long long st_blocks; /* Number 512-byte blocks allocated. */ + + unsigned long st_atime; + unsigned long st_atime_nsec; + + unsigned long st_mtime; + unsigned long st_mtime_nsec; + + unsigned long st_ctime; + unsigned long st_ctime_nsec; + + unsigned long long st_ino; +}; + +#endif diff --git a/arch/nios2/include/uapi/asm/swab.h b/arch/nios2/include/uapi/asm/swab.h new file mode 100644 index 0000000000000..b4e22ebaeb179 --- /dev/null +++ b/arch/nios2/include/uapi/asm/swab.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 Tobias Klauser + * Copyright (C) 2011 Pyramid Technical Consultants, Inc. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#ifndef _ASM_NIOS2_SWAB_H +#define _ASM_NIOS2_SWAB_H + +#include +#include + +#ifdef CONFIG_NIOS2_CI_SWAB_SUPPORT +#ifdef __GNUC__ + +#define __nios2_swab(x) \ + __builtin_custom_ini(CONFIG_NIOS2_CI_SWAB_NO, (x)) + +static inline __attribute__((const)) __u16 __arch_swab16(__u16 x) +{ + return (__u16) __nios2_swab(((__u32) x) << 16); +} +#define __arch_swab16 __arch_swab16 + +static inline __attribute__((const)) __u32 __arch_swab32(__u32 x) +{ + return (__u32) __nios2_swab(x); +} +#define __arch_swab32 __arch_swab32 + +#endif /* __GNUC__ */ +#endif /* CONFIG_NIOS2_CI_SWAB_SUPPORT */ + +#endif /* _ASM_NIOS2_SWAB_H */ diff --git a/arch/nios2/include/uapi/asm/unistd.h b/arch/nios2/include/uapi/asm/unistd.h new file mode 100644 index 0000000000000..2339c103fa043 --- /dev/null +++ b/arch/nios2/include/uapi/asm/unistd.h @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2010 Frans Meulenbroeks + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#ifndef _UAPI_ASM_NIOS2_UNISTD_H +#define _UAPI_ASM_NIOS2_UNISTD_H + +/* + * TRAP 0 isr expects the the syscall # in r3, and arguments in r4, r5, ... + * Return value in r2 and error/ok flag in r7. + */ + +#define __NR_restart_syscall 0 +#define __NR_exit 1 +#define __NR_fork 2 +#define __NR_read 3 +#define __NR_write 4 +#define __NR_open 5 +#define __NR_close 6 +#define __NR_waitpid 7 +#define __NR_creat 8 +#define __NR_link 9 +#define __NR_unlink 10 +#define __NR_execve 11 +#define __NR_chdir 12 +#define __NR_time 13 +#define __NR_mknod 14 +#define __NR_chmod 15 +#define __NR_chown 16 + /* 17 __NR_break obsolete */ + /* 18 __NR_oldstat obsolete */ +#define __NR_lseek 19 +#define __NR_getpid 20 +#define __NR_mount 21 +#define __NR_umount 22 +#define __NR_setuid 23 +#define __NR_getuid 24 +#define __NR_stime 25 +#define __NR_ptrace 26 +#define __NR_alarm 27 + /* 28 __NR_oldfstat obsolete */ +#define __NR_pause 29 +#define __NR_utime 30 + /* 31 __NR_stty obsolete */ + /* 32 __NR_gtty obsolete */ +#define __NR_access 33 +#define __NR_nice 34 + /* 35 __NR_ftime obsolete */ +#define __NR_sync 36 +#define __NR_kill 37 +#define __NR_rename 38 +#define __NR_mkdir 39 +#define __NR_rmdir 40 +#define __NR_dup 41 +#define __NR_pipe 42 +#define __NR_times 43 + /* 44 __NR_prof obsolete */ +#define __NR_brk 45 +#define __NR_setgid 46 +#define __NR_getgid 47 + /* 48 __NR_signal obsolete */ +#define __NR_geteuid 49 +#define __NR_getegid 50 +#define __NR_acct 51 +#define __NR_umount2 52 + /* 53 __NR_lock obsolete */ +#define __NR_ioctl 54 +#define __NR_fcntl 55 + /* 56 __NR_mpx obsolete */ +#define __NR_setpgid 57 + /* 58 __NR_ulimit obsolete */ + /* 59 __NR_oldolduname obsolete */ +#define __NR_umask 60 +#define __NR_chroot 61 +#define __NR_ustat 62 +#define __NR_dup2 63 +#define __NR_getppid 64 +#define __NR_getpgrp 65 +#define __NR_setsid 66 +#define __NR_sigaction 67 + /* 68 __NR_sgetmask obsolete */ + /* 69 __NR_ssetmask obsolete */ +#define __NR_setreuid 70 +#define __NR_setregid 71 +#define __NR_sigsuspend 72 +#define __NR_sigpending 73 +#define __NR_sethostname 74 +#define __NR_setrlimit 75 +#define __NR_getrlimit 76 +#define __NR_getrusage 77 +#define __NR_gettimeofday 78 +#define __NR_settimeofday 79 +#define __NR_getgroups 80 +#define __NR_setgroups 81 +#define __NR_select 82 +#define __NR_symlink 83 + /* 84 __NR_oldlstat obsolete */ +#define __NR_readlink 85 +#define __NR_uselib 86 +#define __NR_swapon 87 +#define __NR_reboot 88 + /* 89 __NR_readdir obsolete */ +#define __NR_mmap 90 +#define __NR_munmap 91 +#define __NR_truncate 92 +#define __NR_ftruncate 93 +#define __NR_fchmod 94 +#define __NR_fchown 95 +#define __NR_getpriority 96 +#define __NR_setpriority 97 + /* 98 __NR_profil obsolete */ +#define __NR_statfs 99 +#define __NR_fstatfs 100 + /* 101 __NR_ioperm obsolete */ +#define __NR_socketcall 102 +#define __NR_syslog 103 +#define __NR_setitimer 104 +#define __NR_getitimer 105 +#define __NR_stat 106 +#define __NR_lstat 107 +#define __NR_fstat 108 + /* 109 __NR_olduname obsolete */ + /* 110 __NR_iopl obsolete */ +#define __NR_vhangup 111 + /* 112 __NR_idle obsolete */ +#define __NR_nios2cmpxchg 113 +#define __NR_wait4 114 +#define __NR_swapoff 115 +#define __NR_sysinfo 116 +#define __NR_ipc 117 +#define __NR_fsync 118 +#define __NR_sigreturn 119 +#define __NR_clone 120 +#define __NR_setdomainname 121 +#define __NR_uname 122 +#define __NR_cacheflush 123 +#define __NR_adjtimex 124 +#define __NR_mprotect 125 +#define __NR_sigprocmask 126 + /* 127 __NR_create_module obsolete */ +#define __NR_init_module 128 +#define __NR_delete_module 129 + /* 130 __NR_get_kernel_syms obsolete */ +#define __NR_quotactl 131 +#define __NR_getpgid 132 +#define __NR_fchdir 133 +#define __NR_bdflush 134 +#define __NR_sysfs 135 +#define __NR_personality 136 + /* 137 __NR_afs_syscall obsolete */ +#define __NR_setfsuid 138 +#define __NR_setfsgid 139 +#define __NR__llseek 140 +#define __NR_getdents 141 +#define __NR__newselect 142 +#define __NR_flock 143 +#define __NR_msync 144 /* obsolete */ +#define __NR_readv 145 +#define __NR_writev 146 +#define __NR_getsid 147 +#define __NR_fdatasync 148 +#define __NR__sysctl 149 +#define __NR_mlock 150 +#define __NR_munlock 151 +#define __NR_mlockall 152 +#define __NR_munlockall 153 +#define __NR_sched_setparam 154 +#define __NR_sched_getparam 155 +#define __NR_sched_setscheduler 156 +#define __NR_sched_getscheduler 157 +#define __NR_sched_yield 158 +#define __NR_sched_get_priority_max 159 +#define __NR_sched_get_priority_min 160 +#define __NR_sched_rr_get_interval 161 +#define __NR_nanosleep 162 +#define __NR_mremap 163 +#define __NR_setresuid 164 +#define __NR_getresuid 165 +#define __NR_getpagesize 166 + /* 167 __NR_query_module obsolete */ +#define __NR_poll 168 +#define __NR_nfsservctl 169 +#define __NR_setresgid 170 +#define __NR_getresgid 171 +#define __NR_prctl 172 +#define __NR_rt_sigreturn 173 +#define __NR_rt_sigaction 174 +#define __NR_rt_sigprocmask 175 +#define __NR_rt_sigpending 176 +#define __NR_rt_sigtimedwait 177 +#define __NR_rt_sigqueueinfo 178 +#define __NR_rt_sigsuspend 179 + /* 180 we cannot have both NR_pread and NR_pread64 */ + /* 181 we cannot have both NR_pwrite and NR_pwrite64 */ +#define __NR_lchown 182 +#define __NR_getcwd 183 +#define __NR_capget 184 +#define __NR_capset 185 +#define __NR_sigaltstack 186 +#define __NR_sendfile 187 + /* 188 __NR_getpmsg obsolete */ + /* 189 __NR_putpmsg obsolete */ +#define __NR_vfork 190 +#define __NR_ugetrlimit 191 +#define __NR_mmap2 192 +#define __NR_truncate64 193 +#define __NR_ftruncate64 194 +#define __NR_stat64 195 +#define __NR_lstat64 196 +#define __NR_fstat64 197 +#define __NR_chown32 198 +#define __NR_getuid32 199 +#define __NR_getgid32 200 +#define __NR_geteuid32 201 +#define __NR_getegid32 202 +#define __NR_setreuid32 203 +#define __NR_setregid32 204 +#define __NR_getgroups32 205 +#define __NR_setgroups32 206 +#define __NR_fchown32 207 +#define __NR_setresuid32 208 +#define __NR_getresuid32 209 +#define __NR_setresgid32 210 +#define __NR_getresgid32 211 +#define __NR_lchown32 212 +#define __NR_setuid32 213 +#define __NR_setgid32 214 +#define __NR_setfsuid32 215 +#define __NR_setfsgid32 216 +#define __NR_pivot_root 217 + /* 218 unused */ + /* 219 unused */ +#define __NR_getdents64 220 +#define __NR_gettid 221 +#define __NR_tkill 222 +#define __NR_setxattr 223 +#define __NR_lsetxattr 224 +#define __NR_fsetxattr 225 +#define __NR_getxattr 226 +#define __NR_lgetxattr 227 +#define __NR_fgetxattr 228 +#define __NR_listxattr 229 +#define __NR_llistxattr 230 +#define __NR_flistxattr 231 +#define __NR_removexattr 232 +#define __NR_lremovexattr 233 +#define __NR_fremovexattr 234 +#define __NR_futex 235 +#define __NR_sendfile64 236 +#define __NR_mincore 237 +#define __NR_madvise 238 +#define __NR_fcntl64 239 +#define __NR_readahead 240 +#define __NR_io_setup 241 +#define __NR_io_destroy 242 +#define __NR_io_getevents 243 +#define __NR_io_submit 244 +#define __NR_io_cancel 245 +#define __NR_fadvise64 246 +#define __NR_exit_group 247 +#define __NR_lookup_dcookie 248 +#define __NR_epoll_create 249 +#define __NR_epoll_ctl 250 +#define __NR_epoll_wait 251 +#define __NR_remap_file_pages 252 +#define __NR_set_tid_address 253 +#define __NR_timer_create 254 +#define __NR_timer_settime 255 +#define __NR_timer_gettime 256 +#define __NR_timer_getoverrun 257 +#define __NR_timer_delete 258 +#define __NR_clock_settime 259 +#define __NR_clock_gettime 260 +#define __NR_clock_getres 261 +#define __NR_clock_nanosleep 262 +#define __NR_statfs64 263 +#define __NR_fstatfs64 264 +#define __NR_tgkill 265 +#define __NR_utimes 266 +#define __NR_fadvise64_64 267 +#define __NR_mbind 268 +#define __NR_get_mempolicy 269 +#define __NR_set_mempolicy 270 +#define __NR_mq_open 271 +#define __NR_mq_unlink 272 +#define __NR_mq_timedsend 273 +#define __NR_mq_timedreceive 274 +#define __NR_mq_notify 275 +#define __NR_mq_getsetattr 276 +#define __NR_waitid 277 + /* 278 __NR_sys_setaltroot obsolete */ +#define __NR_add_key 279 +#define __NR_request_key 280 +#define __NR_keyctl 281 +#define __NR_ioprio_set 282 +#define __NR_ioprio_get 283 +#define __NR_inotify_init 284 +#define __NR_inotify_add_watch 285 +#define __NR_inotify_rm_watch 286 +#define __NR_migrate_pages 287 +#define __NR_openat 288 +#define __NR_mkdirat 289 +#define __NR_mknodat 290 +#define __NR_fchownat 291 +#define __NR_futimesat 292 +#define __NR_fstatat64 293 +#define __NR_unlinkat 294 +#define __NR_renameat 295 +#define __NR_linkat 296 +#define __NR_symlinkat 297 +#define __NR_readlinkat 298 +#define __NR_fchmodat 299 +#define __NR_faccessat 300 +#define __NR_pselect6 301 +#define __NR_ppoll 302 +#define __NR_unshare 303 +#define __NR_set_robust_list 304 +#define __NR_get_robust_list 305 +#define __NR_splice 306 +#define __NR_sync_file_range 307 +#define __NR_tee 308 +#define __NR_vmsplice 309 +#define __NR_move_pages 310 +#define __NR_sched_setaffinity 311 +#define __NR_sched_getaffinity 312 +#define __NR_kexec_load 313 +#define __NR_getcpu 314 +#define __NR_epoll_pwait 315 +#define __NR_utimensat 316 +#define __NR_signalfd 317 +#define __NR_timerfd_create 318 +#define __NR_eventfd 319 +#define __NR_pread64 320 +#define __NR_pwrite64 321 +#define __NR_fallocate 322 +#define __NR_timerfd_settime 323 +#define __NR_timerfd_gettime 324 +#define __NR_signalfd4 325 +#define __NR_eventfd2 326 +#define __NR_epoll_create1 327 +#define __NR_dup3 328 +#define __NR_pipe2 329 +#define __NR_inotify_init1 330 +#define __NR_preadv 331 +#define __NR_pwritev 332 +#define __NR_rt_tgsigqueueinfo 333 +#define __NR_perf_event_open 334 +#define __NR_recvmmsg 335 +#define __NR_fanotify_init 336 +#define __NR_fanotify_mark 337 +#define __NR_prlimit64 338 +#define __NR_name_to_handle_at 339 +#define __NR_open_by_handle_at 340 +#define __NR_clock_adjtime 341 +#define __NR_syncfs 342 +#define __NR_sendmmsg 343 +#define __NR_setns 344 +#define __NR_process_vm_readv 345 +#define __NR_process_vm_writev 346 +#define __NR_kcmp 347 +#define __NR_finit_module 348 +#define __NR_sched_setattr 349 +#define __NR_sched_getattr 350 +#define __NR_renameat2 351 + +#define NR_syscalls 352 + +#endif /* _UAPI_ASM_NIOS2_UNISTD_H */ diff --git a/arch/nios2/kernel/Makefile b/arch/nios2/kernel/Makefile new file mode 100644 index 0000000000000..6ae94056cff59 --- /dev/null +++ b/arch/nios2/kernel/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for the nios2 linux kernel. +# + +extra-y := head.o vmlinux.lds + +obj-y += entry.o + +obj-y += cpuinfo.o insnemu.o irq.o nios2_ksyms.o process.o prom.o ptrace.o \ + setup.o signal.o sys_nios2.o syscalltable.o time.o traps.o + +obj-$(CONFIG_MODULES) += module.o +obj-$(CONFIG_CONSOLE) += console.o +obj-$(CONFIG_EARLY_PRINTK) += early_printk.o +obj-$(CONFIG_ALIGNMENT_TRAP) += misaligned.o +obj-$(CONFIG_KGDB) += kgdb.o diff --git a/arch/nios2/kernel/asm-offsets.c b/arch/nios2/kernel/asm-offsets.c new file mode 100644 index 0000000000000..3852f5c970aa9 --- /dev/null +++ b/arch/nios2/kernel/asm-offsets.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + /* struct task_struct */ + OFFSET(TASK_THREAD, task_struct, thread); + BLANK(); + + /* struct thread_struct */ + OFFSET(THREAD_KSP, thread_struct, ksp); + OFFSET(THREAD_KPSR, thread_struct, kpsr); + BLANK(); + + /* struct pt_regs */ + OFFSET(PT_ORIG_R2, pt_regs, orig_r2); + OFFSET(PT_ORIG_R7, pt_regs, orig_r7); + + OFFSET(PT_R1, pt_regs, r1); + OFFSET(PT_R2, pt_regs, r2); + OFFSET(PT_R3, pt_regs, r3); + OFFSET(PT_R4, pt_regs, r4); + OFFSET(PT_R5, pt_regs, r5); + OFFSET(PT_R6, pt_regs, r6); + OFFSET(PT_R7, pt_regs, r7); + OFFSET(PT_R8, pt_regs, r8); + OFFSET(PT_R9, pt_regs, r9); + OFFSET(PT_R10, pt_regs, r10); + OFFSET(PT_R11, pt_regs, r11); + OFFSET(PT_R12, pt_regs, r12); + OFFSET(PT_R13, pt_regs, r13); + OFFSET(PT_R14, pt_regs, r14); + OFFSET(PT_R15, pt_regs, r15); + OFFSET(PT_EA, pt_regs, ea); + OFFSET(PT_RA, pt_regs, ra); + OFFSET(PT_FP, pt_regs, fp); + OFFSET(PT_SP, pt_regs, sp); + OFFSET(PT_GP, pt_regs, gp); + OFFSET(PT_ESTATUS, pt_regs, estatus); + DEFINE(PT_REGS_SIZE, sizeof(struct pt_regs)); + BLANK(); + + /* struct switch_stack */ + OFFSET(SW_R16, switch_stack, r16); + OFFSET(SW_R17, switch_stack, r17); + OFFSET(SW_R18, switch_stack, r18); + OFFSET(SW_R19, switch_stack, r19); + OFFSET(SW_R20, switch_stack, r20); + OFFSET(SW_R21, switch_stack, r21); + OFFSET(SW_R22, switch_stack, r22); + OFFSET(SW_R23, switch_stack, r23); + OFFSET(SW_FP, switch_stack, fp); + OFFSET(SW_GP, switch_stack, gp); + OFFSET(SW_RA, switch_stack, ra); + DEFINE(SWITCH_STACK_SIZE, sizeof(struct switch_stack)); + BLANK(); + + /* struct thread_info */ + OFFSET(TI_TASK, thread_info, task); + OFFSET(TI_FLAGS, thread_info, flags); + OFFSET(TI_PREEMPT_COUNT, thread_info, preempt_count); + BLANK(); + + return 0; +} diff --git a/arch/nios2/kernel/cpuinfo.c b/arch/nios2/kernel/cpuinfo.c new file mode 100644 index 0000000000000..9ae38029b473f --- /dev/null +++ b/arch/nios2/kernel/cpuinfo.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011 Tobias Klauser + * + * Based on cpuinfo.c from microblaze + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +struct cpuinfo cpuinfo; + +#define err_cpu(x) \ + pr_err("ERROR: Nios II " x " different for kernel and DTS\n"); + +static inline u32 fcpu(struct device_node *cpu, const char *n) +{ + u32 val = 0; + int ret; + char buf[strlen(n) + 1]; + + ret = of_property_read_u32(cpu, n, &val); + if (ret && !strncmp(n, "altr,", 5)) { + strcpy(buf, n); + strncpy(buf, "ALTR,", 5); + of_property_read_u32(cpu, buf, &val); + } + + return val; +} + +static inline u32 fcpu_has(struct device_node *cpu, const char *n) +{ + const void *ret; + char buf[strlen(n) + 1]; + + ret = of_get_property(cpu, n, NULL); + if (!ret && !strncmp(n, "altr,", 5)) { + strcpy(buf, n); + strncpy(buf, "ALTR,", 5); + ret = of_get_property(cpu, buf, NULL); + } + return ret ? 1 : 0; +} + +void __init setup_cpuinfo(void) +{ + struct device_node *cpu; + const char *str; + int len; + + cpu = of_find_node_by_type(NULL, "cpu"); + if (!cpu) + panic("%s: No CPU found in devicetree!\n", __func__); + + if (!fcpu_has(cpu, "altr,has-initda")) + panic("initda instruction is unimplemented. Please update your " + "hardware system to have more than 4-byte line data " + "cache\n"); + + cpuinfo.cpu_clock_freq = fcpu(cpu, "clock-frequency"); + + str = of_get_property(cpu, "altr,implementation", &len); + if (str) + strlcpy(cpuinfo.cpu_impl, str, sizeof(cpuinfo.cpu_impl)); + else + strcpy(cpuinfo.cpu_impl, ""); + + cpuinfo.has_div = fcpu_has(cpu, "altr,has-div"); + cpuinfo.has_mul = fcpu_has(cpu, "altr,has-mul"); + cpuinfo.has_mulx = fcpu_has(cpu, "altr,has-mulx"); + +#ifdef CONFIG_NIOS2_HW_DIV_SUPPORT + if (!cpuinfo.has_div) + err_cpu("DIV"); +#endif +#ifdef CONFIG_NIOS2_HW_MUL_SUPPORT + if (!cpuinfo.has_mul) + err_cpu("MUL"); +#endif +#ifdef CONFIG_NIOS2_HW_MULX_SUPPORT + if (!cpuinfo.has_mulx) + err_cpu("MULX"); +#endif + + cpuinfo.tlb_num_ways = fcpu(cpu, "altr,tlb-num-ways"); + if (!cpuinfo.tlb_num_ways) + panic("altr,tlb-num-ways can't be 0. Please check your hardware " + "system\n"); + cpuinfo.icache_line_size = fcpu(cpu, "icache-line-size"); + cpuinfo.icache_size = fcpu(cpu, "icache-size"); + if (CONFIG_NIOS2_ICACHE_SIZE != cpuinfo.icache_size) + pr_warn("Warning: icache size configuration mismatch " + "(0x%x vs 0x%x) of CONFIG_NIOS2_ICACHE_SIZE vs " + "device tree icache-size\n", + CONFIG_NIOS2_ICACHE_SIZE, cpuinfo.icache_size); + + cpuinfo.dcache_line_size = fcpu(cpu, "dcache-line-size"); + if (CONFIG_NIOS2_DCACHE_LINE_SIZE != cpuinfo.dcache_line_size) + pr_warn("Warning: dcache line size configuration mismatch " + "(0x%x vs 0x%x) of CONFIG_NIOS2_DCACHE_LINE_SIZE vs " + "device tree dcache-line-size\n", + CONFIG_NIOS2_DCACHE_LINE_SIZE, cpuinfo.dcache_line_size); + + cpuinfo.dcache_size = fcpu(cpu, "dcache-size"); + if (CONFIG_NIOS2_DCACHE_SIZE != cpuinfo.dcache_size) + pr_warn("Warning: dcache size configuration mismatch " + "(0x%x vs 0x%x) of CONFIG_NIOS2_DCACHE_SIZE vs " + "device tree dcache-size\n", + CONFIG_NIOS2_DCACHE_SIZE, cpuinfo.dcache_size); + + cpuinfo.tlb_pid_num_bits = fcpu(cpu, "altr,pid-num-bits"); + cpuinfo.tlb_num_ways_log2 = ilog2(cpuinfo.tlb_num_ways); + cpuinfo.tlb_num_entries = fcpu(cpu, "altr,tlb-num-entries"); + cpuinfo.tlb_num_lines = cpuinfo.tlb_num_entries / cpuinfo.tlb_num_ways; + cpuinfo.tlb_ptr_sz = fcpu(cpu, "altr,tlb-ptr-sz"); + + cpuinfo.reset_addr = fcpu(cpu, "altr,reset-addr"); + cpuinfo.exception_addr = fcpu(cpu, "altr,exception-addr"); + cpuinfo.fast_tlb_miss_exc_addr = fcpu(cpu, "altr,fast-tlb-miss-addr"); +} + +#ifdef CONFIG_PROC_FS + +/* + * Get CPU information for use by the procfs. + */ +static int show_cpuinfo(struct seq_file *m, void *v) +{ + int count = 0; + const u32 clockfreq = cpuinfo.cpu_clock_freq; + + count = seq_printf(m, + "CPU:\t\tNios II/%s\n" + "MMU:\t\t%s\n" + "FPU:\t\tnone\n" + "Clocking:\t%u.%02u MHz\n" + "BogoMips:\t%lu.%02lu\n" + "Calibration:\t%lu loops\n", + cpuinfo.cpu_impl, + cpuinfo.mmu ? "present" : "none", + clockfreq / 1000000, (clockfreq / 100000) % 10, + (loops_per_jiffy * HZ) / 500000, + ((loops_per_jiffy * HZ) / 5000) % 100, + (loops_per_jiffy * HZ)); + + count += seq_printf(m, + "HW:\n" + " MUL:\t\t%s\n" + " MULX:\t\t%s\n" + " DIV:\t\t%s\n", + cpuinfo.has_mul ? "yes" : "no", + cpuinfo.has_mulx ? "yes" : "no", + cpuinfo.has_div ? "yes" : "no"); + + count += seq_printf(m, + "Icache:\t\t%ukB, line length: %u\n", + cpuinfo.icache_size >> 10, + cpuinfo.icache_line_size); + + count += seq_printf(m, + "Dcache:\t\t%ukB, line length: %u\n", + cpuinfo.dcache_size >> 10, + cpuinfo.dcache_line_size); + + count += seq_printf(m, + "TLB:\t\t%u ways, %u entries, %u PID bits\n", + cpuinfo.tlb_num_ways, + cpuinfo.tlb_num_entries, + cpuinfo.tlb_pid_num_bits); + + return 0; +} + +static void *cpuinfo_start(struct seq_file *m, loff_t *pos) +{ + unsigned long i = *pos; + + return i < num_possible_cpus() ? (void *) (i + 1) : NULL; +} + +static void *cpuinfo_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return cpuinfo_start(m, pos); +} + +static void cpuinfo_stop(struct seq_file *m, void *v) +{ +} + +const struct seq_operations cpuinfo_op = { + .start = cpuinfo_start, + .next = cpuinfo_next, + .stop = cpuinfo_stop, + .show = show_cpuinfo +}; + +#endif /* CONFIG_PROC_FS */ diff --git a/arch/nios2/kernel/early_printk.c b/arch/nios2/kernel/early_printk.c new file mode 100644 index 0000000000000..1e06fb4e944e1 --- /dev/null +++ b/arch/nios2/kernel/early_printk.c @@ -0,0 +1,116 @@ +/* + * Early printk for Nios2. + * + * Copyright (C) 2010, Tobias Klauser + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include + +static unsigned long base_addr; + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE) + +#define ALTERA_JTAGUART_DATA_REG 0 +#define ALTERA_JTAGUART_CONTROL_REG 4 +#define ALTERA_JTAGUART_CONTROL_WSPACE_MSK 0xFFFF0000 +#define ALTERA_JTAGUART_CONTROL_AC_MSK 0x00000400 + +#define JUART_GET_CR() \ + __builtin_ldwio((void *)(base_addr + ALTERA_JTAGUART_CONTROL_REG)) +#define JUART_SET_CR(v) \ + __builtin_stwio((void *)(base_addr + ALTERA_JTAGUART_CONTROL_REG), v) +#define JUART_SET_TX(v) \ + __builtin_stwio((void *)(base_addr + ALTERA_JTAGUART_DATA_REG), v) + +static void early_console_write(struct console *con, const char *s, unsigned n) +{ + unsigned long status; + + while (n-- && *s) { + while (((status = JUART_GET_CR()) + & ALTERA_JTAGUART_CONTROL_WSPACE_MSK) == 0) { +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS) + if ((status & ALTERA_JTAGUART_CONTROL_AC_MSK) == 0) + return; /* no connection activity */ +#endif + } + JUART_SET_TX(*s); + s++; + } +} + +#elif defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE) + +#define ALTERA_UART_TXDATA_REG 4 +#define ALTERA_UART_STATUS_REG 8 +#define ALTERA_UART_STATUS_TRDY 0x0040 + +#define UART_GET_SR() \ + __builtin_ldwio((void *)(base_addr + ALTERA_UART_STATUS_REG)) +#define UART_SET_TX(v) \ + __builtin_stwio((void *)(base_addr + ALTERA_UART_TXDATA_REG), v) + +static void early_console_putc(char c) +{ + while (!(UART_GET_SR() & ALTERA_UART_STATUS_TRDY)) + ; + + UART_SET_TX(c); +} + +static void early_console_write(struct console *con, const char *s, unsigned n) +{ + while (n-- && *s) { + early_console_putc(*s); + if (*s == '\n') + early_console_putc('\r'); + s++; + } +} + +#else +# error Neither SERIAL_ALTERA_JTAGUART_CONSOLE nor SERIAL_ALTERA_UART_CONSOLE \ +selected +#endif + +static struct console early_console_prom = { + .name = "early", + .write = early_console_write, + .flags = CON_PRINTBUFFER | CON_BOOT, + .index = -1 +}; + +void __init setup_early_printk(void) +{ +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE) || \ + defined(CONFIG_SERIAL_ALTERA_UART_CONSOLE) + base_addr = early_altera_uart_or_juart_console(); +#else + base_addr = 0; +#endif + + if (!base_addr) + return; + +#if defined(CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE_BYPASS) + /* Clear activity bit so BYPASS doesn't stall if we've used JTAG for + * downloading the kernel. This might cause early data to be lost even + * if the JTAG terminal is running. + */ + JUART_SET_CR(JUART_GET_CR() | ALTERA_JTAGUART_CONTROL_AC_MSK); +#endif + + early_console = &early_console_prom; + register_console(early_console); + pr_info("early_console initialized at 0x%08lx\n", base_addr); +} diff --git a/arch/nios2/kernel/entry.S b/arch/nios2/kernel/entry.S new file mode 100644 index 0000000000000..249002c7869d2 --- /dev/null +++ b/arch/nios2/kernel/entry.S @@ -0,0 +1,555 @@ +/* + * linux/arch/nios2/kernel/entry.S + * + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2009, Wind River Systems Inc + * + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * Based on: + * + * linux/arch/nios2/kernel/entry.S + * + * Copyright (C) 1999-2002, Greg Ungerer (gerg@snapgear.com) + * Copyright (C) 1998 D. Jeff Dionne , + * Kenneth Albanowski , + * Copyright (C) 2000 Lineo Inc. (www.lineo.com) + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * Based on: + * + * linux/arch/m68knommu/kernel/entry.S + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Linux/m68k support by Hamish Macdonald + * + * 68060 fixes by Jesper Skov + * ColdFire support by Greg Ungerer (gerg@snapgear.com) + * 5307 fixes by David W. Miller + * linux 2.4 support David McCullough + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +.macro GET_THREAD_INFO reg +.if THREAD_SIZE & 0xffff0000 + andhi \reg, sp, %hi(~(THREAD_SIZE-1)) +.else + addi \reg, r0, %lo(~(THREAD_SIZE-1)) + and \reg, \reg, sp +.endif +.endm + +/* FIXME: Lots of these exceptions need to be handled */ +.section .rodata +.align 4 +exception_table: + .word unhandled_exception /* 0 - Reset */ + .word unhandled_exception /* 1 - Processor-only Reset */ + .word external_interrupt /* 2 - Interrupt */ + .word handle_trap /* 3 - Trap Instruction */ + + .word instruction_trap /* 4 - Unimplemented instruction */ + .word handle_illegal /* 5 - Illegal instruction */ + .word handle_unaligned /* 6 - Misaligned data access */ + .word handle_unaligned /* 7 - Misaligned destination address */ + + .word handle_diverror /* 8 - Division error */ + .word protection_exception_ba /* 9 - Supervisor-only instr. address */ + .word protection_exception_instr /* 10 - Supervisor only instruction */ + .word protection_exception_ba /* 11 - Supervisor only data address */ + + .word unhandled_exception /* 12 - Double TLB miss (data) */ + .word protection_exception_pte /* 13 - TLB permission violation (x) */ + .word protection_exception_pte /* 14 - TLB permission violation (r) */ + .word protection_exception_pte /* 15 - TLB permission violation (w) */ + + .word unhandled_exception /* 16 - MPU region violation */ + +trap_table: + .word handle_system_call /* 0 */ + .word instruction_trap /* 1 */ + .word instruction_trap /* 2 */ + .word instruction_trap /* 3 */ + .word instruction_trap /* 4 */ + .word instruction_trap /* 5 */ + .word instruction_trap /* 6 */ + .word instruction_trap /* 7 */ + .word instruction_trap /* 8 */ + .word instruction_trap /* 9 */ + .word instruction_trap /* 10 */ + .word instruction_trap /* 11 */ + .word instruction_trap /* 12 */ + .word instruction_trap /* 13 */ + .word instruction_trap /* 14 */ + .word instruction_trap /* 15 */ + .word instruction_trap /* 16 */ + .word instruction_trap /* 17 */ + .word instruction_trap /* 18 */ + .word instruction_trap /* 19 */ + .word instruction_trap /* 20 */ + .word instruction_trap /* 21 */ + .word instruction_trap /* 22 */ + .word instruction_trap /* 23 */ + .word instruction_trap /* 24 */ + .word instruction_trap /* 25 */ + .word instruction_trap /* 26 */ + .word instruction_trap /* 27 */ + .word instruction_trap /* 28 */ + .word instruction_trap /* 29 */ +#ifdef CONFIG_KGDB + .word handle_kgdb_breakpoint /* 30 KGDB breakpoint */ +#else + .word instruction_trap // 30 +#endif + .word handle_breakpoint // 31 + +.text +.set noat +.set nobreak + +ENTRY(inthandler) + SAVE_ALL + /* Clear EH bit before we get a new excpetion in the kernel + * and after we have saved it to the exception frame. This is done + * wheter it's trap, tlb-miss or interrupt. If we don't do this + * estatus is not updated the next exception. + */ + rdctl r24, status + movi r9, %lo(~STATUS_EH) + and r24, r24, r9 + wrctl status, r24 + + /* Read cause and vector and branch to the associated handler */ + mov r4, sp + rdctl r5, exception + movia r9, exception_table + add r24, r9, r5 + ldw r24, 0(r24) + jmp r24 + + +/*********************************************************************** + * Handle traps + *********************************************************************** + */ +ENTRY(handle_trap) + ldw r24, -4(ea) /* instruction that caused the exception */ + srli r24, r24, 4 + andi r24, r24, 0x7c + movia r9,trap_table + add r24, r24, r9 + ldw r24, 0(r24) + jmp r24 + + +/*********************************************************************** + * Handle system calls + *********************************************************************** + */ +ENTRY(handle_system_call) + /* Enable interrupts */ + rdctl r10, status + ori r10, r10, STATUS_PIE + wrctl status, r10 + + /* Reload registers destroyed by common code. */ + ldw r4, PT_R4(sp) + ldw r5, PT_R5(sp) + + /* Check that the requested system call is within limits */ + movui r1, NR_syscalls + bgeu r2, r1, ret_invsyscall + slli r1, r2, 2 + movhi r11, %hiadj(sys_call_table) + add r1, r1, r11 + ldw r1, %lo(sys_call_table)(r1) + beq r1, r0, ret_invsyscall + + /* Get thread info pointer */ + movi r11, %lo(0xfffff000) + and r11, sp, r11 + ldw r11, TI_FLAGS(r11) + + /* If someone is ptrace:ing us, take the long way. */ + BTBNZ r11, r11, TIF_SYSCALL_TRACE, traced_system_call + + /* Execute the system call */ + callr r1 + + /* If the syscall returns a negative result: + * Set r7 to 1 to indicate error, + * Negate r2 to get a positive error code + * If the syscall returns zero or a positive value: + * Set r7 to 0. + * The sigreturn system calls will skip the code below by + * adding to register ra. To avoid destroying registers + * + * FIXME: We probably need an orig_r7 + */ +translate_rc_and_ret: + movi r1, 0 + bge r2, zero, 3f + sub r2, zero, r2 + movi r1, 1 +3: + stw r2, PT_R2(sp) + stw r1, PT_R7(sp) +end_translate_rc_and_ret: + +ret_from_exception: + ldw r1, PT_ESTATUS(sp) + /* if so, skip resched, signals */ + TSTBNZ r1, r1, ESTATUS_EU, Luser_return + +restore_all: + rdctl r10, status /* disable intrs */ + andi r10, r10, %lo(~STATUS_PIE) + wrctl status, r10 + RESTORE_ALL + eret + + /* If the syscall number was invalid return ENOSYS */ +ret_invsyscall: + movi r2, -ENOSYS + br translate_rc_and_ret + + /* This implements the same as above, except it calls + * syscall_trace before and after the syscall in order + * for utilities like strace and gdb to work. + */ +traced_system_call: + SAVE_SWITCH_STACK + call syscall_trace + RESTORE_SWITCH_STACK + + /* r2 and r7-r9 might be destroyed by syscall_trace and we need to + * restore them before calling our syscall. + */ + ldw r2, PT_R2(sp) + ldw r4, PT_R4(sp) + ldw r5, PT_R5(sp) + ldw r6, PT_R6(sp) + ldw r7, PT_R7(sp) + ldw r8, PT_R8(sp) + ldw r9, PT_R9(sp) + + /* Fetch the syscall function, we don't need to check the boundaries + * since this is already done. + */ + slli r1, r2, 2 + movhi r11,%hiadj(sys_call_table) + add r1, r1, r11 + ldw r1, %lo(sys_call_table)(r1) + + callr r1 + + /* If the syscall returns a negative result: + * Set r7 to 1 to indicate error, + * Negate r2 to get a positive error code + * If the syscall returns zero or a positive value: + * Set r7 to 0. + * The sigreturn system calls will skip the code below by + * adding to register ra. To avoid destroying registers + * + * FIXME: We probably need an orig_r7 + */ +translate_rc_and_ret2: + movi r1, 0 + bge r2, zero, 4f + sub r2, zero, r2 + movi r1, 1 +4: + stw r2, PT_R2(sp) + stw r1, PT_R7(sp) +end_translate_rc_and_ret2: + SAVE_SWITCH_STACK + call syscall_trace + RESTORE_SWITCH_STACK + br ret_from_exception + +Luser_return: + GET_THREAD_INFO r11 /* get thread_info pointer */ + ldw r10, TI_FLAGS(r11) /* get thread_info->flags */ + ANDI32 r11, r10, _TIF_WORK_MASK + beq r11, r0, restore_all /* Nothing to do */ + BTBZ r1, r10, TIF_NEED_RESCHED, Lsignal_return + + /* Reschedule work */ + call schedule + br ret_from_exception + +Lsignal_return: + ANDI32 r1, r10, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME + beq r1, r0, restore_all + mov r4, sp /* pt_regs */ + SAVE_SWITCH_STACK + movi r5, 1 /* in_syscall = 1 */ + call do_notify_resume + RESTORE_SWITCH_STACK + br restore_all + + + +/*********************************************************************** + * Handle external interrupts. + *********************************************************************** + */ +/* + * This is the generic interrupt handler (for all hardware interrupt + * sources). It figures out the vector number and calls the appropriate + * interrupt service routine directly. + */ +external_interrupt: + rdctl r12, ipending + rdctl r9, ienable + and r12, r12, r9 + /* skip if no interrupt is pending */ + beq r12, r0, ret_from_interrupt + + movi r24, -1 + stw r24, PT_ORIG_R2(sp) + + /* + * Process an external hardware interrupt. + */ + + addi ea, ea, -4 /* re-issue the interrupted instruction */ + stw ea, PT_EA(sp) +2: movi r4, %lo(-1) /* Start from bit position 0, + highest priority */ + /* This is the IRQ # for handler call */ +1: andi r10, r12, 1 /* Isolate bit we are interested in */ + srli r12, r12, 1 /* shift count is costly without hardware + multiplier */ + addi r4, r4, 1 + beq r10, r0, 1b + mov r5, sp /* Setup pt_regs pointer for handler call */ + call do_IRQ + rdctl r12, ipending /* check again if irq still pending */ + rdctl r9, ienable /* Isolate possible interrupts */ + and r12, r12, r9 + bne r12, r0, 2b + /* br ret_from_interrupt */ /* fall throught to ret_from_interrupt */ + +ENTRY(ret_from_interrupt) + ldw r1, PT_ESTATUS(sp) /* check if returning to kernel */ + TSTBNZ r1, r1, ESTATUS_EU, Luser_return + +#ifdef CONFIG_PREEMPT + GET_THREAD_INFO r1 + ldw r4, TI_PREEMPT_COUNT(r1) + bne r4, r0, restore_all + +need_resched: + ldw r4, TI_FLAGS(r1) /* ? Need resched set */ + BTBZ r10, r4, TIF_NEED_RESCHED, restore_all + ldw r4, PT_ESTATUS(sp) /* ? Interrupts off */ + andi r10, r4, ESTATUS_EPIE + beq r10, r0, restore_all + movia r4, PREEMPT_ACTIVE + stw r4, TI_PREEMPT_COUNT(r1) + rdctl r10, status /* enable intrs again */ + ori r10, r10 ,STATUS_PIE + wrctl status, r10 + PUSH r1 + call schedule + POP r1 + mov r4, r0 + stw r4, TI_PREEMPT_COUNT(r1) + rdctl r10, status /* disable intrs */ + andi r10, r10, %lo(~STATUS_PIE) + wrctl status, r10 + br need_resched +#else + br restore_all +#endif + +/*********************************************************************** + * Syscalls implemented in assembly + *********************************************************************** + */ + +ENTRY(sys_nios2cmpxchg) + + /* r4 pointer to exchange variable */ + /* r5 old value */ + /* r6 new value */ + /* r8 - old interrupt status (assert this to enabled?) */ + /* r9 - temp */ + /* Disable interrupts (keep old status in r8) */ + rdctl r8, status + andi r9, r8, %lo(~STATUS_PIE) + wrctl status, r9 + + /* Make sure we skip the r2/r7 translation code when we return. */ + addi ra, ra, (end_translate_rc_and_ret - translate_rc_and_ret) + +ldw1: ldw r2, 0(r4) + bne r2, r5, .L6 + + /* We had a match, store the new value */ +stw1: stw r6, 0(r4) +.L6: + /* Reenable interrupts */ + wrctl status, r8 + + /* Store return value */ + stw r2, PT_R2(sp) + /* Indicate everything is ok */ + movi r2, 0 + stw r2, PT_R7(sp) + ret +fault: + movi r2, EFAULT + stw r2, PT_R2(sp) + movi r2, 1 + stw r2, PT_R7(sp) + wrctl status, r8 + ret + + /* setup the exception table */ + .section __ex_table, "a" + .word ldw1, fault + .word stw1, fault + .previous + + + +/*********************************************************************** + * A few syscall wrappers + *********************************************************************** + */ + +ENTRY(__sys_fork) + SAVE_SWITCH_STACK + call sys_fork + RESTORE_SWITCH_STACK + ret + +ENTRY(__sys_vfork) + SAVE_SWITCH_STACK + call sys_vfork + RESTORE_SWITCH_STACK + ret + +ENTRY(__sys_clone) + mov r4,sp + SAVE_SWITCH_STACK + call nios2_clone + RESTORE_SWITCH_STACK + ret + +ENTRY(sys_sigreturn) + mov r4, sp + SAVE_SWITCH_STACK + call do_sigreturn + RESTORE_SWITCH_STACK + addi ra, ra, (end_translate_rc_and_ret - translate_rc_and_ret) + ret + +ENTRY(sys_rt_sigreturn) + SAVE_SWITCH_STACK + mov r4, sp + call do_rt_sigreturn + RESTORE_SWITCH_STACK + addi ra, ra, (end_translate_rc_and_ret - translate_rc_and_ret) + ret + +/*********************************************************************** + * A few other wrappers and stubs + *********************************************************************** + */ +protection_exception_pte: + rdctl r6, pteaddr + slli r6, r6, 10 + call do_page_fault + br ret_from_exception + +protection_exception_ba: + rdctl r6, badaddr + call do_page_fault + br ret_from_exception + +protection_exception_instr: + call handle_supervisor_instr + br ret_from_exception + +handle_breakpoint: + call breakpoint_c + br ret_from_exception + +#ifdef CONFIG_ALIGNMENT_TRAP +handle_unaligned: + SAVE_SWITCH_STACK + call handle_unaligned_c + RESTORE_SWITCH_STACK + br ret_from_exception +#else +handle_unaligned: + call handle_unaligned_c + br ret_from_exception +#endif + +handle_illegal: + call handle_illegal_c + br ret_from_exception + +handle_diverror: + call handle_diverror_c + br ret_from_exception + +#ifdef CONFIG_KGDB +handle_kgdb_breakpoint: + call kgdb_breakpoint_c + br ret_from_exception +#endif + +/* + * Beware - when entering resume, prev (the current task) is + * in r4, next (the new task) is in r5, don't change these + * registers. + */ +ENTRY(resume) + + rdctl r7, status /* save thread status reg */ + stw r7, TASK_THREAD + THREAD_KPSR(r4) + + andi r7, r7, %lo(~STATUS_PIE) /* disable interrupts */ + wrctl status, r7 + + SAVE_SWITCH_STACK + stw sp, TASK_THREAD + THREAD_KSP(r4)/* save kernel stack pointer */ + ldw sp, TASK_THREAD + THREAD_KSP(r5)/* restore new thread stack */ + movia r24, _current_thread /* save thread */ + GET_THREAD_INFO r1 + stw r1, 0(r24) + RESTORE_SWITCH_STACK + + ldw r7, TASK_THREAD + THREAD_KPSR(r5)/* restore thread status reg */ + wrctl status, r7 + ret + +ENTRY(ret_from_fork) + call schedule_tail + br ret_from_exception + +ENTRY(ret_from_kernel_thread) + call schedule_tail + mov r4,r17 /* arg */ + callr r16 /* function */ + br ret_from_exception diff --git a/arch/nios2/kernel/head.S b/arch/nios2/kernel/head.S new file mode 100644 index 0000000000000..372ce4a33018b --- /dev/null +++ b/arch/nios2/kernel/head.S @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * Copyright (C) 2004 Microtronix Datacom Ltd + * Copyright (C) 2001 Vic Phillips, Microtronix Datacom Ltd. + * + * Based on head.S for Altera's Excalibur development board with nios processor + * + * Based on the following from the Excalibur sdk distribution: + * NA_MemoryMap.s, NR_JumpToStart.s, NR_Setup.s, NR_CWPManager.s + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * ZERO_PAGE is a special page that is used for zero-initialized + * data and COW. + */ +.data +.global empty_zero_page +.align 12 +empty_zero_page: + .space PAGE_SIZE + +/* + * This global variable is used as an extension to the nios' + * STATUS register to emulate a user/supervisor mode. + */ + .data + .align 2 + .set noat + + .global _current_thread +_current_thread: + .long 0 +/* + * Input(s): passed from u-boot + * r4 - Optional pointer to a board information structure. + * r5 - Optional pointer to the physical starting address of the init RAM + * disk. + * r6 - Optional pointer to the physical ending address of the init RAM + * disk. + * r7 - Optional pointer to the physical starting address of any kernel + * command-line parameters. + */ + +/* + * First executable code - detected and jumped to by the ROM bootstrap + * if the code resides in flash (looks for "Nios" at offset 0x0c from + * the potential executable image). + */ + __HEAD +ENTRY(_start) + wrctl status, r0 /* Disable interrupts */ + + /* Initialize all cache lines within the instruction cache */ + movia r1, NIOS2_ICACHE_SIZE + movui r2, NIOS2_ICACHE_LINE_SIZE + +icache_init: + initi r1 + sub r1, r1, r2 + bgt r1, r0, icache_init + br 1f + + /* + * This is the default location for the exception handler. Code in jump + * to our handler + */ +ENTRY(exception_handler_hook) + movia r24, inthandler + jmp r24 + +ENTRY(fast_handler) + nextpc et +helper: + stw r3, r3save - helper(et) + + rdctl r3 , pteaddr + srli r3, r3, 12 + slli r3, r3, 2 + movia et, pgd_current + + ldw et, 0(et) + add r3, et, r3 + ldw et, 0(r3) + + rdctl r3, pteaddr + andi r3, r3, 0xfff + add et, r3, et + ldw et, 0(et) + wrctl tlbacc, et + nextpc et +helper2: + ldw r3, r3save - helper2(et) + subi ea, ea, 4 + eret +r3save: + .word 0x0 +ENTRY(fast_handler_end) + +1: + /* + * After the instruction cache is initialized, the data cache must + * also be initialized. + */ + movia r1, NIOS2_DCACHE_SIZE + movui r2, NIOS2_DCACHE_LINE_SIZE + +dcache_init: + initd 0(r1) + sub r1, r1, r2 + bgt r1, r0, dcache_init + + nextpc r1 /* Find out where we are */ +chkadr: + movia r2, chkadr + beq r1, r2,finish_move /* We are running in RAM done */ + addi r1, r1,(_start - chkadr) /* Source */ + movia r2, _start /* Destination */ + movia r3, __bss_start /* End of copy */ + +loop_move: /* r1: src, r2: dest, r3: last dest */ + ldw r8, 0(r1) /* load a word from [r1] */ + stw r8, 0(r2) /* store a word to dest [r2] */ + flushd 0(r2) /* Flush cache for safety */ + addi r1, r1, 4 /* inc the src addr */ + addi r2, r2, 4 /* inc the dest addr */ + blt r2, r3, loop_move + + movia r1, finish_move /* VMA(_start)->l1 */ + jmp r1 /* jmp to _start */ + +finish_move: + + /* Mask off all possible interrupts */ + wrctl ienable, r0 + + /* Clear .bss */ + movia r2, __bss_start + movia r1, __bss_stop +1: + stb r0, 0(r2) + addi r2, r2, 1 + bne r1, r2, 1b + + movia r1, init_thread_union /* set stack at top of the task union */ + addi sp, r1, THREAD_SIZE + movia r2, _current_thread /* Remember current thread */ + stw r1, 0(r2) + + movia r1, nios2_boot_init /* save args r4-r7 passed from u-boot */ + callr r1 + + movia r1, start_kernel /* call start_kernel as a subroutine */ + callr r1 + + /* If we return from start_kernel, break to the oci debugger and + * buggered we are. + */ + break + + /* End of startup code */ +.set at diff --git a/arch/nios2/kernel/insnemu.S b/arch/nios2/kernel/insnemu.S new file mode 100644 index 0000000000000..9cfed86df8fa5 --- /dev/null +++ b/arch/nios2/kernel/insnemu.S @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2003-2013 Altera Corporation + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include + +.set noat +.set nobreak + +/* +* Explicitly allow the use of r1 (the assembler temporary register) +* within this code. This register is normally reserved for the use of +* the compiler. +*/ + +ENTRY(instruction_trap) + ldw r1, PT_R1(sp) // Restore registers + ldw r2, PT_R2(sp) + ldw r3, PT_R3(sp) + ldw r4, PT_R4(sp) + ldw r5, PT_R5(sp) + ldw r6, PT_R6(sp) + ldw r7, PT_R7(sp) + ldw r8, PT_R8(sp) + ldw r9, PT_R9(sp) + ldw r10, PT_R10(sp) + ldw r11, PT_R11(sp) + ldw r12, PT_R12(sp) + ldw r13, PT_R13(sp) + ldw r14, PT_R14(sp) + ldw r15, PT_R15(sp) + ldw ra, PT_RA(sp) + ldw fp, PT_FP(sp) + ldw gp, PT_GP(sp) + ldw et, PT_ESTATUS(sp) + wrctl estatus, et + ldw ea, PT_EA(sp) + ldw et, PT_SP(sp) /* backup sp in et */ + + addi sp, sp, PT_REGS_SIZE + + /* INSTRUCTION EMULATION + * --------------------- + * + * Nios II processors generate exceptions for unimplemented instructions. + * The routines below emulate these instructions. Depending on the + * processor core, the only instructions that might need to be emulated + * are div, divu, mul, muli, mulxss, mulxsu, and mulxuu. + * + * The emulations match the instructions, except for the following + * limitations: + * + * 1) The emulation routines do not emulate the use of the exception + * temporary register (et) as a source operand because the exception + * handler already has modified it. + * + * 2) The routines do not emulate the use of the stack pointer (sp) or + * the exception return address register (ea) as a destination because + * modifying these registers crashes the exception handler or the + * interrupted routine. + * + * Detailed Design + * --------------- + * + * The emulation routines expect the contents of integer registers r0-r31 + * to be on the stack at addresses sp, 4(sp), 8(sp), ... 124(sp). The + * routines retrieve source operands from the stack and modify the + * destination register's value on the stack prior to the end of the + * exception handler. Then all registers except the destination register + * are restored to their previous values. + * + * The instruction that causes the exception is found at address -4(ea). + * The instruction's OP and OPX fields identify the operation to be + * performed. + * + * One instruction, muli, is an I-type instruction that is identified by + * an OP field of 0x24. + * + * muli AAAAA,BBBBB,IIIIIIIIIIIIIIII,-0x24- + * 27 22 6 0 <-- LSB of field + * + * The remaining emulated instructions are R-type and have an OP field + * of 0x3a. Their OPX fields identify them. + * + * R-type AAAAA,BBBBB,CCCCC,XXXXXX,NNNNN,-0x3a- + * 27 22 17 11 6 0 <-- LSB of field + * + * + * Opcode Encoding. muli is identified by its OP value. Then OPX & 0x02 + * is used to differentiate between the division opcodes and the + * remaining multiplication opcodes. + * + * Instruction OP OPX OPX & 0x02 + * ----------- ---- ---- ---------- + * muli 0x24 + * divu 0x3a 0x24 0 + * div 0x3a 0x25 0 + * mul 0x3a 0x27 != 0 + * mulxuu 0x3a 0x07 != 0 + * mulxsu 0x3a 0x17 != 0 + * mulxss 0x3a 0x1f != 0 + */ + + + /* + * Save everything on the stack to make it easy for the emulation + * routines to retrieve the source register operands. + */ + + addi sp, sp, -128 + stw zero, 0(sp) /* Save zero on stack to avoid special case for r0. */ + stw r1, 4(sp) + stw r2, 8(sp) + stw r3, 12(sp) + stw r4, 16(sp) + stw r5, 20(sp) + stw r6, 24(sp) + stw r7, 28(sp) + stw r8, 32(sp) + stw r9, 36(sp) + stw r10, 40(sp) + stw r11, 44(sp) + stw r12, 48(sp) + stw r13, 52(sp) + stw r14, 56(sp) + stw r15, 60(sp) + stw r16, 64(sp) + stw r17, 68(sp) + stw r18, 72(sp) + stw r19, 76(sp) + stw r20, 80(sp) + stw r21, 84(sp) + stw r22, 88(sp) + stw r23, 92(sp) + /* Don't bother to save et. It's already been changed. */ + rdctl r5, estatus + stw r5, 100(sp) + + stw gp, 104(sp) + stw et, 108(sp) /* et containts previous sp value. */ + stw fp, 112(sp) + stw ea, 116(sp) + stw ra, 120(sp) + + + /* + * Split the instruction into its fields. We need 4*A, 4*B, and 4*C as + * offsets to the stack pointer for access to the stored register values. + */ + ldw r2,-4(ea) /* r2 = AAAAA,BBBBB,IIIIIIIIIIIIIIII,PPPPPP */ + roli r3, r2, 7 /* r3 = BBB,IIIIIIIIIIIIIIII,PPPPPP,AAAAA,BB */ + roli r4, r3, 3 /* r4 = IIIIIIIIIIIIIIII,PPPPPP,AAAAA,BBBBB */ + roli r5, r4, 2 /* r5 = IIIIIIIIIIIIII,PPPPPP,AAAAA,BBBBB,II */ + srai r4, r4, 16 /* r4 = (sign-extended) IMM16 */ + roli r6, r5, 5 /* r6 = XXXX,NNNNN,PPPPPP,AAAAA,BBBBB,CCCCC,XX */ + andi r2, r2, 0x3f /* r2 = 00000000000000000000000000,PPPPPP */ + andi r3, r3, 0x7c /* r3 = 0000000000000000000000000,AAAAA,00 */ + andi r5, r5, 0x7c /* r5 = 0000000000000000000000000,BBBBB,00 */ + andi r6, r6, 0x7c /* r6 = 0000000000000000000000000,CCCCC,00 */ + + /* Now + * r2 = OP + * r3 = 4*A + * r4 = IMM16 (sign extended) + * r5 = 4*B + * r6 = 4*C + */ + + /* + * Get the operands. + * + * It is necessary to check for muli because it uses an I-type + * instruction format, while the other instructions are have an R-type + * format. + * + * Prepare for either multiplication or division loop. + * They both loop 32 times. + */ + movi r14, 32 + + add r3, r3, sp /* r3 = address of A-operand. */ + ldw r3, 0(r3) /* r3 = A-operand. */ + movi r7, 0x24 /* muli opcode (I-type instruction format) */ + beq r2, r7, mul_immed /* muli doesn't use the B register as a source */ + + add r5, r5, sp /* r5 = address of B-operand. */ + ldw r5, 0(r5) /* r5 = B-operand. */ + /* r4 = SSSSSSSSSSSSSSSS,-----IMM16------ */ + /* IMM16 not needed, align OPX portion */ + /* r4 = SSSSSSSSSSSSSSSS,CCCCC,-OPX--,00000 */ + srli r4, r4, 5 /* r4 = 00000,SSSSSSSSSSSSSSSS,CCCCC,-OPX-- */ + andi r4, r4, 0x3f /* r4 = 00000000000000000000000000,-OPX-- */ + + /* Now + * r2 = OP + * r3 = src1 + * r5 = src2 + * r4 = OPX (no longer can be muli) + * r6 = 4*C + */ + + + /* + * Multiply or Divide? + */ + andi r7, r4, 0x02 /* For R-type multiply instructions, + OPX & 0x02 != 0 */ + bne r7, zero, multiply + + + /* DIVISION + * + * Divide an unsigned dividend by an unsigned divisor using + * a shift-and-subtract algorithm. The example below shows + * 43 div 7 = 6 for 8-bit integers. This classic algorithm uses a + * single register to store both the dividend and the quotient, + * allowing both values to be shifted with a single instruction. + * + * remainder dividend:quotient + * --------- ----------------- + * initialize 00000000 00101011: + * shift 00000000 0101011:_ + * remainder >= divisor? no 00000000 0101011:0 + * shift 00000000 101011:0_ + * remainder >= divisor? no 00000000 101011:00 + * shift 00000001 01011:00_ + * remainder >= divisor? no 00000001 01011:000 + * shift 00000010 1011:000_ + * remainder >= divisor? no 00000010 1011:0000 + * shift 00000101 011:0000_ + * remainder >= divisor? no 00000101 011:00000 + * shift 00001010 11:00000_ + * remainder >= divisor? yes 00001010 11:000001 + * remainder -= divisor - 00000111 + * ---------- + * 00000011 11:000001 + * shift 00000111 1:000001_ + * remainder >= divisor? yes 00000111 1:0000011 + * remainder -= divisor - 00000111 + * ---------- + * 00000000 1:0000011 + * shift 00000001 :0000011_ + * remainder >= divisor? no 00000001 :00000110 + * + * The quotient is 00000110. + */ + +divide: + /* + * Prepare for division by assuming the result + * is unsigned, and storing its "sign" as 0. + */ + movi r17, 0 + + + /* Which division opcode? */ + xori r7, r4, 0x25 /* OPX of div */ + bne r7, zero, unsigned_division + + + /* + * OPX is div. Determine and store the sign of the quotient. + * Then take the absolute value of both operands. + */ + xor r17, r3, r5 /* MSB contains sign of quotient */ + bge r3,zero,dividend_is_nonnegative + sub r3, zero, r3 /* -r3 */ +dividend_is_nonnegative: + bge r5, zero, divisor_is_nonnegative + sub r5, zero, r5 /* -r5 */ +divisor_is_nonnegative: + + +unsigned_division: + /* Initialize the unsigned-division loop. */ + movi r13, 0 /* remainder = 0 */ + + /* Now + * r3 = dividend : quotient + * r4 = 0x25 for div, 0x24 for divu + * r5 = divisor + * r13 = remainder + * r14 = loop counter (already initialized to 32) + * r17 = MSB contains sign of quotient + */ + + + /* + * for (count = 32; count > 0; --count) + * { + */ +divide_loop: + + /* + * Division: + * + * (remainder:dividend:quotient) <<= 1; + */ + slli r13, r13, 1 + cmplt r7, r3, zero /* r7 = MSB of r3 */ + or r13, r13, r7 + slli r3, r3, 1 + + + /* + * if (remainder >= divisor) + * { + * set LSB of quotient + * remainder -= divisor; + * } + */ + bltu r13, r5, div_skip + ori r3, r3, 1 + sub r13, r13, r5 +div_skip: + + /* + * } + */ + subi r14, r14, 1 + bne r14, zero, divide_loop + + + /* Now + * r3 = quotient + * r4 = 0x25 for div, 0x24 for divu + * r6 = 4*C + * r17 = MSB contains sign of quotient + */ + + + /* + * Conditionally negate signed quotient. If quotient is unsigned, + * the sign already is initialized to 0. + */ + bge r17, zero, quotient_is_nonnegative + sub r3, zero, r3 /* -r3 */ + quotient_is_nonnegative: + + + /* + * Final quotient is in r3. + */ + add r6, r6, sp + stw r3, 0(r6) /* write quotient to stack */ + br restore_registers + + + + + /* MULTIPLICATION + * + * A "product" is the number that one gets by summing a "multiplicand" + * several times. The "multiplier" specifies the number of copies of the + * multiplicand that are summed. + * + * Actual multiplication algorithms don't use repeated addition, however. + * Shift-and-add algorithms get the same answer as repeated addition, and + * they are faster. To compute the lower half of a product (pppp below) + * one shifts the product left before adding in each of the partial + * products (a * mmmm) through (d * mmmm). + * + * To compute the upper half of a product (PPPP below), one adds in the + * partial products (d * mmmm) through (a * mmmm), each time following + * the add by a right shift of the product. + * + * mmmm + * * abcd + * ------ + * #### = d * mmmm + * #### = c * mmmm + * #### = b * mmmm + * #### = a * mmmm + * -------- + * PPPPpppp + * + * The example above shows 4 partial products. Computing actual Nios II + * products requires 32 partials. + * + * It is possible to compute the result of mulxsu from the result of + * mulxuu because the only difference between the results of these two + * opcodes is the value of the partial product associated with the sign + * bit of rA. + * + * mulxsu = mulxuu - (rA < 0) ? rB : 0; + * + * It is possible to compute the result of mulxss from the result of + * mulxsu because the only difference between the results of these two + * opcodes is the value of the partial product associated with the sign + * bit of rB. + * + * mulxss = mulxsu - (rB < 0) ? rA : 0; + * + */ + +mul_immed: + /* Opcode is muli. Change it into mul for remainder of algorithm. */ + mov r6, r5 /* Field B is dest register, not field C. */ + mov r5, r4 /* Field IMM16 is src2, not field B. */ + movi r4, 0x27 /* OPX of mul is 0x27 */ + +multiply: + /* Initialize the multiplication loop. */ + movi r9, 0 /* mul_product = 0 */ + movi r10, 0 /* mulxuu_product = 0 */ + mov r11, r5 /* save original multiplier for mulxsu and mulxss */ + mov r12, r5 /* mulxuu_multiplier (will be shifted) */ + movi r16, 1 /* used to create "rori B,A,1" from "ror B,A,r16" */ + + /* Now + * r3 = multiplicand + * r5 = mul_multiplier + * r6 = 4 * dest_register (used later as offset to sp) + * r7 = temp + * r9 = mul_product + * r10 = mulxuu_product + * r11 = original multiplier + * r12 = mulxuu_multiplier + * r14 = loop counter (already initialized) + * r16 = 1 + */ + + + /* + * for (count = 32; count > 0; --count) + * { + */ +multiply_loop: + + /* + * mul_product <<= 1; + * lsb = multiplier & 1; + */ + slli r9, r9, 1 + andi r7, r12, 1 + + /* + * if (lsb == 1) + * { + * mulxuu_product += multiplicand; + * } + */ + beq r7, zero, mulx_skip + add r10, r10, r3 + cmpltu r7, r10, r3 /* Save the carry from the MSB of mulxuu_product. */ + ror r7, r7, r16 /* r7 = 0x80000000 on carry, or else 0x00000000 */ +mulx_skip: + + /* + * if (MSB of mul_multiplier == 1) + * { + * mul_product += multiplicand; + * } + */ + bge r5, zero, mul_skip + add r9, r9, r3 +mul_skip: + + /* + * mulxuu_product >>= 1; logical shift + * mul_multiplier <<= 1; done with MSB + * mulx_multiplier >>= 1; done with LSB + */ + srli r10, r10, 1 + or r10, r10, r7 /* OR in the saved carry bit. */ + slli r5, r5, 1 + srli r12, r12, 1 + + + /* + * } + */ + subi r14, r14, 1 + bne r14, zero, multiply_loop + + + /* + * Multiply emulation loop done. + */ + + /* Now + * r3 = multiplicand + * r4 = OPX + * r6 = 4 * dest_register (used later as offset to sp) + * r7 = temp + * r9 = mul_product + * r10 = mulxuu_product + * r11 = original multiplier + */ + + + /* Calculate address for result from 4 * dest_register */ + add r6, r6, sp + + + /* + * Select/compute the result based on OPX. + */ + + + /* OPX == mul? Then store. */ + xori r7, r4, 0x27 + beq r7, zero, store_product + + /* It's one of the mulx.. opcodes. Move over the result. */ + mov r9, r10 + + /* OPX == mulxuu? Then store. */ + xori r7, r4, 0x07 + beq r7, zero, store_product + + /* Compute mulxsu + * + * mulxsu = mulxuu - (rA < 0) ? rB : 0; + */ + bge r3, zero, mulxsu_skip + sub r9, r9, r11 +mulxsu_skip: + + /* OPX == mulxsu? Then store. */ + xori r7, r4, 0x17 + beq r7, zero, store_product + + /* Compute mulxss + * + * mulxss = mulxsu - (rB < 0) ? rA : 0; + */ + bge r11,zero,mulxss_skip + sub r9, r9, r3 +mulxss_skip: + /* At this point, assume that OPX is mulxss, so store*/ + + +store_product: + stw r9, 0(r6) + + +restore_registers: + /* No need to restore r0. */ + ldw r5, 100(sp) + wrctl estatus, r5 + + ldw r1, 4(sp) + ldw r2, 8(sp) + ldw r3, 12(sp) + ldw r4, 16(sp) + ldw r5, 20(sp) + ldw r6, 24(sp) + ldw r7, 28(sp) + ldw r8, 32(sp) + ldw r9, 36(sp) + ldw r10, 40(sp) + ldw r11, 44(sp) + ldw r12, 48(sp) + ldw r13, 52(sp) + ldw r14, 56(sp) + ldw r15, 60(sp) + ldw r16, 64(sp) + ldw r17, 68(sp) + ldw r18, 72(sp) + ldw r19, 76(sp) + ldw r20, 80(sp) + ldw r21, 84(sp) + ldw r22, 88(sp) + ldw r23, 92(sp) + /* Does not need to restore et */ + ldw gp, 104(sp) + + ldw fp, 112(sp) + ldw ea, 116(sp) + ldw ra, 120(sp) + ldw sp, 108(sp) /* last restore sp */ + eret + +.set at +.set break + diff --git a/arch/nios2/kernel/irq.c b/arch/nios2/kernel/irq.c new file mode 100644 index 0000000000000..3ab4370a7c04c --- /dev/null +++ b/arch/nios2/kernel/irq.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2008 Thomas Chou + * + * based on irq.c from m68k which is: + * + * Copyright (C) 2007 Greg Ungerer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +asmlinkage void do_IRQ(int hwirq, struct pt_regs *regs) +{ + struct pt_regs *oldregs = set_irq_regs(regs); + int irq; + + irq_enter(); + irq = irq_find_mapping(NULL, hwirq); + generic_handle_irq(irq); + irq_exit(); + + set_irq_regs(oldregs); +} + +static void chip_unmask(struct irq_data *d) +{ + u32 ien; + ien = RDCTL(CTL_IENABLE); + ien |= (1 << d->hwirq); + WRCTL(CTL_IENABLE, ien); +} + +static void chip_mask(struct irq_data *d) +{ + u32 ien; + ien = RDCTL(CTL_IENABLE); + ien &= ~(1 << d->hwirq); + WRCTL(CTL_IENABLE, ien); +} + +static struct irq_chip m_irq_chip = { + .name = "NIOS2-INTC", + .irq_unmask = chip_unmask, + .irq_mask = chip_mask, +}; + +static int irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_and_handler(virq, &m_irq_chip, handle_level_irq); + + return 0; +} + +static struct irq_domain_ops irq_ops = { + .map = irq_map, + .xlate = irq_domain_xlate_onecell, +}; + +void __init init_IRQ(void) +{ + struct irq_domain *domain; + struct device_node *node; + + node = of_find_compatible_node(NULL, NULL, "ALTR,nios2-1.0"); + if (!node) + node = of_find_compatible_node(NULL, NULL, "altr,nios2-1.1"); + + BUG_ON(!node); + + domain = irq_domain_add_linear(node, NIOS2_CPU_NR_IRQS, &irq_ops, NULL); + BUG_ON(!domain); + + irq_set_default_host(domain); + of_node_put(node); +} diff --git a/arch/nios2/kernel/kgdb.c b/arch/nios2/kernel/kgdb.c new file mode 100644 index 0000000000000..b506d45f55a2a --- /dev/null +++ b/arch/nios2/kernel/kgdb.c @@ -0,0 +1,143 @@ +/* + * Nios2 KGDB support + * + * Copyright (C) 2011 Tobias Klauser + * + * Based on the code posted by Kazuyasu on the Altera Forum at: + * http://www.alteraforum.com/forum/showpost.php?p=77003&postcount=20 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +static int wait_for_remote_debugger; + +void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + gdb_regs[GDB_R0] = 0; + gdb_regs[GDB_AT] = regs->r1; + gdb_regs[GDB_R2] = regs->r2; + gdb_regs[GDB_R3] = regs->r3; + gdb_regs[GDB_R4] = regs->r4; + gdb_regs[GDB_R5] = regs->r5; + gdb_regs[GDB_R6] = regs->r6; + gdb_regs[GDB_R7] = regs->r7; + gdb_regs[GDB_R8] = regs->r8; + gdb_regs[GDB_R9] = regs->r9; + gdb_regs[GDB_R10] = regs->r10; + gdb_regs[GDB_R11] = regs->r11; + gdb_regs[GDB_R12] = regs->r12; + gdb_regs[GDB_R13] = regs->r13; + gdb_regs[GDB_R14] = regs->r14; + gdb_regs[GDB_R15] = regs->r15; + + gdb_regs[GDB_RA] = regs->ra; + gdb_regs[GDB_FP] = regs->fp; + gdb_regs[GDB_SP] = regs->sp; + gdb_regs[GDB_GP] = regs->gp; + gdb_regs[GDB_ESTATUS] = regs->estatus; + gdb_regs[GDB_PC] = regs->ea; +} + +void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + regs->r1 = gdb_regs[GDB_AT]; + regs->r2 = gdb_regs[GDB_R2]; + regs->r3 = gdb_regs[GDB_R3]; + regs->r4 = gdb_regs[GDB_R4]; + regs->r5 = gdb_regs[GDB_R5]; + regs->r6 = gdb_regs[GDB_R6]; + regs->r7 = gdb_regs[GDB_R7]; + regs->r8 = gdb_regs[GDB_R8]; + regs->r9 = gdb_regs[GDB_R9]; + regs->r10 = gdb_regs[GDB_R10]; + regs->r11 = gdb_regs[GDB_R11]; + regs->r12 = gdb_regs[GDB_R12]; + regs->r13 = gdb_regs[GDB_R13]; + regs->r14 = gdb_regs[GDB_R14]; + regs->r15 = gdb_regs[GDB_R15]; + + regs->ra = gdb_regs[GDB_RA]; + regs->fp = gdb_regs[GDB_FP]; + regs->sp = gdb_regs[GDB_SP]; + regs->gp = gdb_regs[GDB_GP]; + regs->estatus = gdb_regs[GDB_ESTATUS]; + regs->ea = gdb_regs[GDB_PC]; +} + +void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p) +{ + gdb_regs[GDB_SP] = p->thread.kregs->sp; + gdb_regs[GDB_PC] = p->thread.kregs->ea; +} + +void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc) +{ + regs->ea = pc; +} + +int kgdb_arch_handle_exception(int vector, int signo, int err_code, + char *remcom_in_buffer, char *remcom_out_buffer, + struct pt_regs *regs) +{ + char *ptr; + unsigned long addr; + + switch (remcom_in_buffer[0]) { + case 's': + case 'c': + /* handle the optional parameters */ + ptr = &remcom_in_buffer[1]; + if (kgdb_hex2long(&ptr, &addr)) + regs->ea = addr; + + return 0; + } + + return -1; /* this means that we do not want to exit from the handler */ +} + +asmlinkage void kgdb_breakpoint_c(struct pt_regs *regs) +{ + /* + * The breakpoint entry code has moved the PC on by 4 bytes, so we must + * move it back. This could be done on the host but we do it here + */ + if (!wait_for_remote_debugger) + regs->ea -= 4; + else /* pass the first trap 30 code */ + wait_for_remote_debugger = 0; + + kgdb_handle_exception(30, SIGTRAP, 0, regs); +} + +int kgdb_arch_init(void) +{ + wait_for_remote_debugger = 1; + return 0; +} + +void kgdb_arch_exit(void) +{ + /* Nothing to do */ +} + +struct kgdb_arch arch_kgdb_ops = { + /* Breakpoint instruction: trap 30 */ + .gdb_bpt_instr = { 0xba, 0x6f, 0x3b, 0x00 }, +}; diff --git a/arch/nios2/kernel/misaligned.c b/arch/nios2/kernel/misaligned.c new file mode 100644 index 0000000000000..f8845ab72af03 --- /dev/null +++ b/arch/nios2/kernel/misaligned.c @@ -0,0 +1,319 @@ +/* + * linux/arch/nios2/kernel/misaligned.c + * + * basic emulation for mis-aligned accesses on the NIOS II cpu + * modeled after the version for arm in arm/alignment.c + * + * Brad Parker + * Copyright (C) 2010 Ambient Corporation + * Copyright (c) 2010 Altera Corporation, San Jose, California, USA. + * Copyright (c) 2010 Arrow Electronics, Inc. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of + * this archive for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* instructions we emulate */ +#define INST_LDHU 0x0b +#define INST_STH 0x0d +#define INST_LDH 0x0f +#define INST_STW 0x15 +#define INST_LDW 0x17 + +static unsigned long ma_user, ma_kern, ma_skipped, ma_half, ma_word; + +static unsigned int ma_usermode; +#define UM_WARN 0x01 +#define UM_FIXUP 0x02 +#define UM_SIGNAL 0x04 +#define KM_WARN 0x08 + +/* see arch/nios2/include/asm/ptrace.h */ +static u8 sys_stack_frame_reg_offset[] = { + /* struct pt_regs */ + 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 0, + /* struct switch_stack */ + 16, 17, 18, 19, 20, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int reg_offsets[32]; + +static inline u32 get_reg_val(struct pt_regs *fp, int reg) +{ + u8 *p = ((u8 *)fp) + reg_offsets[reg]; + return *(u32 *)p; +} + +static inline void put_reg_val(struct pt_regs *fp, int reg, u32 val) +{ + u8 *p = ((u8 *)fp) + reg_offsets[reg]; + *(u32 *)p = val; +} + +/* + * (mis)alignment handler + */ +asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause) +{ + u32 isn, addr, val; + int in_kernel; + u8 a, b, d0, d1, d2, d3; + u16 imm16; + unsigned int fault; + + /* back up one instruction */ + fp->ea -= 4; + + if (fixup_exception(fp)) { + ma_skipped++; + return; + } + + in_kernel = !user_mode(fp); + + isn = *(unsigned long *)(fp->ea); + + fault = 0; + + /* do fixup if in kernel or mode turned on */ + if (in_kernel || (ma_usermode & UM_FIXUP)) { + /* decompose instruction */ + a = (isn >> 27) & 0x1f; + b = (isn >> 22) & 0x1f; + imm16 = (isn >> 6) & 0xffff; + addr = get_reg_val(fp, a) + imm16; + + /* do fixup to saved registers */ + switch (isn & 0x3f) { + case INST_LDHU: + fault |= __get_user(d0, (u8 *)(addr+0)); + fault |= __get_user(d1, (u8 *)(addr+1)); + val = (d1 << 8) | d0; + put_reg_val(fp, b, val); + ma_half++; + break; + case INST_STH: + val = get_reg_val(fp, b); + d1 = val >> 8; + d0 = val >> 0; + + pr_debug("sth: ra=%d (%08x) rb=%d (%08x), imm16 %04x addr %08x val %08x\n", + a, get_reg_val(fp, a), + b, get_reg_val(fp, b), + imm16, addr, val); + + if (in_kernel) { + *(u8 *)(addr+0) = d0; + *(u8 *)(addr+1) = d1; + } else { + fault |= __put_user(d0, (u8 *)(addr+0)); + fault |= __put_user(d1, (u8 *)(addr+1)); + } + ma_half++; + break; + case INST_LDH: + fault |= __get_user(d0, (u8 *)(addr+0)); + fault |= __get_user(d1, (u8 *)(addr+1)); + val = (short)((d1 << 8) | d0); + put_reg_val(fp, b, val); + ma_half++; + break; + case INST_STW: + val = get_reg_val(fp, b); + d3 = val >> 24; + d2 = val >> 16; + d1 = val >> 8; + d0 = val >> 0; + if (in_kernel) { + *(u8 *)(addr+0) = d0; + *(u8 *)(addr+1) = d1; + *(u8 *)(addr+2) = d2; + *(u8 *)(addr+3) = d3; + } else { + fault |= __put_user(d0, (u8 *)(addr+0)); + fault |= __put_user(d1, (u8 *)(addr+1)); + fault |= __put_user(d2, (u8 *)(addr+2)); + fault |= __put_user(d3, (u8 *)(addr+3)); + } + ma_word++; + break; + case INST_LDW: + fault |= __get_user(d0, (u8 *)(addr+0)); + fault |= __get_user(d1, (u8 *)(addr+1)); + fault |= __get_user(d2, (u8 *)(addr+2)); + fault |= __get_user(d3, (u8 *)(addr+3)); + val = (d3 << 24) | (d2 << 16) | (d1 << 8) | d0; + put_reg_val(fp, b, val); + ma_word++; + break; + } + } + + addr = RDCTL(CTL_BADADDR); + cause >>= 2; + + if (fault) { + if (in_kernel) { + pr_err("fault during kernel misaligned fixup @ %#lx; addr 0x%08lx; isn=0x%08x\n", + fp->ea, (long unsigned int)addr, + (unsigned int)isn); + } else { + pr_err("fault during user misaligned fixup @ %#lx; isn=%08x addr=0x%08x sp=0x%08lx pid=%d\n", + fp->ea, + (unsigned int)isn, addr, fp->sp, + current->pid); + + _exception(SIGSEGV, fp, SEGV_MAPERR, fp->ea); + return; + } + } + + /* + * kernel mode - + * note exception and skip bad instruction (return) + */ + if (in_kernel) { + ma_kern++; + fp->ea += 4; + + if (ma_usermode & KM_WARN) { + pr_err("kernel unaligned access @ %#lx; BADADDR 0x%08lx; cause=%d, isn=0x%08lx\n", + fp->ea, + (long unsigned int)addr, cause, + (long unsigned int)isn); + /* show_regs(fp); */ + } + + return; + } + + ma_user++; + + /* + * user mode - + * possibly warn, + * possibly send SIGBUS signal to process + */ + if (ma_usermode & UM_WARN) { + pr_err("user unaligned access @ %#lx; isn=0x%08lx ea=0x%08lx ra=0x%08lx sp=0x%08lx\n", + (unsigned long)addr, (unsigned long)isn, + fp->ea, fp->ra, fp->sp); + } + + if (ma_usermode & UM_SIGNAL) + _exception(SIGBUS, fp, BUS_ADRALN, fp->ea); + else + fp->ea += 4; /* else advance */ +} + +#ifdef CONFIG_PROC_FS +static const char * const usermode_action[] = { + "ignored", /* 0 */ + "warn", /* 1 */ + "fixup", /* 2 */ + "fixup+warn", /* 3 */ + "signal", /* 4 */ + "signal+warn", /* 5 */ + "signal+fixup", + "signal+fixup+warn" +}; + +static int misaligned_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "User:\t\t%lu\n", ma_user); + seq_printf(m, "Kernel:\t\t%lu\n", ma_kern); + seq_printf(m, "Skipped:\t%lu\n", ma_skipped); + seq_printf(m, "Half:\t\t%lu\n", ma_half); + seq_printf(m, "Word:\t\t%lu\n", ma_word); + seq_printf(m, "User faults:\t%i (%s)\n", ma_usermode, + usermode_action[ma_usermode & 7]); + + return 0; +} + +static int proc_misaligned_open(struct inode *inode, struct file *file) +{ + return single_open(file, misaligned_proc_show, NULL); +} + +static ssize_t proc_misaligned_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos) +{ + char mode; + + if (count > 0) { + if (get_user(mode, buffer)) + return -EFAULT; + if (mode >= '0' && mode <= '5') + ma_usermode = mode - '0'; + } + + return count; +} + +static const struct file_operations misalign_fops = { + .open = proc_misaligned_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = proc_misaligned_write, +}; +#endif /* CONFIG_PROC_FS */ + +static void __init misaligned_calc_reg_offsets(void) +{ + int i, r, offset; + + /* pre-calc offsets of registers on sys call stack frame */ + offset = 0; + + /* struct pt_regs */ + for (i = 0; i < 16; i++) { + r = sys_stack_frame_reg_offset[i]; + reg_offsets[r] = offset; + offset += 4; + } + + /* struct switch_stack */ + offset = -sizeof(struct switch_stack); + for (i = 16; i < 32; i++) { + r = sys_stack_frame_reg_offset[i]; + reg_offsets[r] = offset; + offset += 4; + } +} + + +static int __init misaligned_init(void) +{ +#ifdef CONFIG_PROC_FS + + struct proc_dir_entry *res; + + res = proc_create("misalign", S_IWUSR | S_IRUGO, NULL, &misalign_fops); + if (!res) + return -ENOMEM; +#endif + + /* default mode - silent fix */ + ma_usermode = UM_FIXUP | KM_WARN; + + misaligned_calc_reg_offsets(); + + return 0; +} + +fs_initcall(misaligned_init); diff --git a/arch/nios2/kernel/module.c b/arch/nios2/kernel/module.c new file mode 100644 index 0000000000000..4c014543c3c7d --- /dev/null +++ b/arch/nios2/kernel/module.c @@ -0,0 +1,148 @@ +/* + * Kernel module support for Nios II. + * + * Copyright (C) 2004 Microtronix Datacom Ltd. + * Written by Wentao Xu + * Copyright (C) 2001, 2003 Rusty Russell + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * FIXME: modules should NOT be allocated with kmalloc for (obvious) reasons. + * But we do it for now to avoid relocation issues. CALL26/PCREL26 cannot reach + * from 0x80000000 (vmalloc area) to 0xc00000000 (kernel) (kmalloc returns + * addresses in 0xc0000000) + * + * We should really have some trampolines for this instead. + */ + +void *module_alloc(unsigned long size) +{ + if (size == 0) + return NULL; + return kmalloc(size, GFP_KERNEL); +} + +/* Free memory returned from module_alloc */ +void module_free(struct module *mod, void *module_region) +{ + kfree(module_region); + /* + * FIXME: If module_region == mod->init_region, trim exception + * table entries. + */ +} + +int apply_relocate_add(Elf32_Shdr *sechdrs, const char *strtab, + unsigned int symindex, unsigned int relsec, + struct module *mod) +{ + unsigned int i; + Elf32_Rela *rela = (void *)sechdrs[relsec].sh_addr; + + pr_debug("Applying relocate section %u to %u\n", relsec, + sechdrs[relsec].sh_info); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rela); i++) { + /* This is where to make the change */ + uint32_t word; + uint32_t *loc + = ((void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rela[i].r_offset); + /* This is the symbol it is referring to. Note that all + undefined symbols have been resolved. */ + Elf32_Sym *sym + = ((Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rela[i].r_info)); + uint32_t v = sym->st_value + rela[i].r_addend; + pr_debug("reltype %d 0x%x name:<%s>\n", + ELF32_R_TYPE(rela[i].r_info), + rela[i].r_offset, strtab + sym->st_name); + + switch (ELF32_R_TYPE(rela[i].r_info)) { + case R_NIOS2_NONE: + break; + case R_NIOS2_BFD_RELOC_32: + *loc += v; + break; + case R_NIOS2_PCREL16: + v -= (uint32_t)loc + 4; + if ((int32_t)v > 0x7fff || + (int32_t)v < -(int32_t)0x8000) { + pr_err("module %s: relocation overflow\n", + mod->name); + return -ENOEXEC; + } + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + break; + case R_NIOS2_CALL26: + if (v & 3) { + pr_err("module %s: dangerous relocation\n", + mod->name); + return -ENOEXEC; + } + if ((v >> 28) != ((uint32_t)loc >> 28)) { + pr_err("module %s: relocation overflow\n", + mod->name); + return -ENOEXEC; + } + *loc = (*loc & 0x3f) | ((v >> 2) << 6); + break; + case R_NIOS2_HI16: + word = *loc; + *loc = ((((word >> 22) << 16) | + ((v >> 16) & 0xffff)) << 6) | (word & 0x3f); + break; + case R_NIOS2_LO16: + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + break; + case R_NIOS2_HIADJ16: + { + Elf32_Addr word2; + + word = *loc; + word2 = ((v >> 16) + ((v >> 15) & 1)) & 0xffff; + *loc = ((((word >> 22) << 16) | word2) << 6) | + (word & 0x3f); + } + break; + + default: + pr_err("module %s: Unknown reloc: %u\n", + mod->name, ELF32_R_TYPE(rela[i].r_info)); + return -ENOEXEC; + } + } + + return 0; +} + +int module_finalize(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs, + struct module *me) +{ + flush_cache_all(); + return 0; +} + +void module_arch_cleanup(struct module *mod) +{ +} diff --git a/arch/nios2/kernel/nios2_ksyms.c b/arch/nios2/kernel/nios2_ksyms.c new file mode 100644 index 0000000000000..eaf7f0cee5fac --- /dev/null +++ b/arch/nios2/kernel/nios2_ksyms.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include +#include + +/* string functions */ + +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memmove); + +/* + * libgcc functions - functions that are used internally by the + * compiler... (prototypes are not correct though, but that + * doesn't really matter since they're not versioned). + */ +#define DECLARE_EXPORT(name) extern void name(void); EXPORT_SYMBOL(name) + +DECLARE_EXPORT(__gcc_bcmp); +DECLARE_EXPORT(__divdi3); +DECLARE_EXPORT(__divsi3); +DECLARE_EXPORT(__moddi3); +DECLARE_EXPORT(__modsi3); +DECLARE_EXPORT(__udivdi3); +DECLARE_EXPORT(__udivmoddi4); +DECLARE_EXPORT(__udivsi3); +DECLARE_EXPORT(__umoddi3); +DECLARE_EXPORT(__umodsi3); +DECLARE_EXPORT(__muldi3); diff --git a/arch/nios2/kernel/process.c b/arch/nios2/kernel/process.c new file mode 100644 index 0000000000000..4467dbbea6a26 --- /dev/null +++ b/arch/nios2/kernel/process.c @@ -0,0 +1,262 @@ +/* + * Architecture-dependent parts of process handling. + * + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * based on arch/m68knommu/kernel/process.c which is: + * + * Copyright (C) 2000-2002 David McCullough + * Copyright (C) 1995 Hamish Macdonald + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include + +#include +#include +#include + +asmlinkage void ret_from_fork(void); +asmlinkage void ret_from_kernel_thread(void); + +void (*pm_power_off)(void) = NULL; +EXPORT_SYMBOL(pm_power_off); + +void arch_cpu_idle(void) +{ + local_irq_enable(); +} + +/* + * The development boards have no way to pull a board reset. Just jump to the + * cpu reset address and let the boot loader or the code in head.S take care of + * resetting peripherals. + */ +void machine_restart(char *__unused) +{ + pr_notice("Machine restart (%08x)...\n", cpuinfo.reset_addr); + local_irq_disable(); + __asm__ __volatile__ ( + "jmp %0\n\t" + : + : "r" (cpuinfo.reset_addr) + : "r4"); +} + +void machine_halt(void) +{ + pr_notice("Machine halt...\n"); + local_irq_disable(); + for (;;) + ; +} + +/* + * There is no way to power off the development boards. So just spin for now. If + * we ever have a way of resetting a board using a GPIO we should add that here. + */ +void machine_power_off(void) +{ + pr_notice("Machine power off...\n"); + local_irq_disable(); + for (;;) + ; +} + +void show_regs(struct pt_regs *regs) +{ + pr_notice("\n"); + show_regs_print_info(KERN_DEFAULT); + + pr_notice("r1: %08lx r2: %08lx r3: %08lx r4: %08lx\n", + regs->r1, regs->r2, regs->r3, regs->r4); + + pr_notice("r5: %08lx r6: %08lx r7: %08lx r8: %08lx\n", + regs->r5, regs->r6, regs->r7, regs->r8); + + pr_notice("r9: %08lx r10: %08lx r11: %08lx r12: %08lx\n", + regs->r9, regs->r10, regs->r11, regs->r12); + + pr_notice("r13: %08lx r14: %08lx r15: %08lx\n", + regs->r13, regs->r14, regs->r15); + + pr_notice("ra: %08lx fp: %08lx sp: %08lx gp: %08lx\n", + regs->ra, regs->fp, regs->sp, regs->gp); + + pr_notice("ea: %08lx estatus: %08lx\n", + regs->ea, regs->estatus); +} + +void flush_thread(void) +{ + set_fs(USER_DS); +} + +int copy_thread(unsigned long clone_flags, + unsigned long usp, unsigned long arg, struct task_struct *p) +{ + struct pt_regs *childregs = task_pt_regs(p); + struct pt_regs *regs; + struct switch_stack *stack; + struct switch_stack *childstack = + ((struct switch_stack *)childregs) - 1; + + if (unlikely(p->flags & PF_KTHREAD)) { + memset(childstack, 0, + sizeof(struct switch_stack) + sizeof(struct pt_regs)); + + childstack->r16 = usp; /* fn */ + childstack->r17 = arg; + childstack->ra = (unsigned long) ret_from_kernel_thread; + childregs->estatus = STATUS_PIE; + childregs->sp = (unsigned long) childstack; + + p->thread.ksp = (unsigned long) childstack; + p->thread.kregs = childregs; + return 0; + } + + regs = current_pt_regs(); + *childregs = *regs; + childregs->r2 = 0; /* Set the return value for the child. */ + childregs->r7 = 0; + + stack = ((struct switch_stack *) regs) - 1; + *childstack = *stack; + childstack->ra = (unsigned long)ret_from_fork; + p->thread.kregs = childregs; + p->thread.ksp = (unsigned long) childstack; + + if (usp) + childregs->sp = usp; + + /* Initialize tls register. */ + if (clone_flags & CLONE_SETTLS) + childstack->r23 = regs->r7; + + return 0; +} + +/* + * Generic dumping code. Used for panic and debug. + */ +void dump(struct pt_regs *fp) +{ + unsigned long *sp; + unsigned char *tp; + int i; + + pr_emerg("\nCURRENT PROCESS:\n\n"); + pr_emerg("COMM=%s PID=%d\n", current->comm, current->pid); + + if (current->mm) { + pr_emerg("TEXT=%08x-%08x DATA=%08x-%08x BSS=%08x-%08x\n", + (int) current->mm->start_code, + (int) current->mm->end_code, + (int) current->mm->start_data, + (int) current->mm->end_data, + (int) current->mm->end_data, + (int) current->mm->brk); + pr_emerg("USER-STACK=%08x KERNEL-STACK=%08x\n\n", + (int) current->mm->start_stack, + (int)(((unsigned long) current) + THREAD_SIZE)); + } + + pr_emerg("PC: %08lx\n", fp->ea); + pr_emerg(KERN_EMERG "SR: %08lx SP: %08lx\n", + (long) fp->estatus, (long) fp); + + pr_emerg("r1: %08lx r2: %08lx r3: %08lx\n", + fp->r1, fp->r2, fp->r3); + + pr_emerg("r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n", + fp->r4, fp->r5, fp->r6, fp->r7); + pr_emerg("r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n", + fp->r8, fp->r9, fp->r10, fp->r11); + pr_emerg("r12: %08lx r13: %08lx r14: %08lx r15: %08lx\n", + fp->r12, fp->r13, fp->r14, fp->r15); + pr_emerg("or2: %08lx ra: %08lx fp: %08lx sp: %08lx\n", + fp->orig_r2, fp->ra, fp->fp, fp->sp); + pr_emerg("\nUSP: %08x TRAPFRAME: %08x\n", + (unsigned int) fp->sp, (unsigned int) fp); + + pr_emerg("\nCODE:"); + tp = ((unsigned char *) fp->ea) - 0x20; + for (sp = (unsigned long *) tp, i = 0; (i < 0x40); i += 4) { + if ((i % 0x10) == 0) + pr_emerg("\n%08x: ", (int) (tp + i)); + pr_emerg("%08x ", (int) *sp++); + } + pr_emerg("\n"); + + pr_emerg("\nKERNEL STACK:"); + tp = ((unsigned char *) fp) - 0x40; + for (sp = (unsigned long *) tp, i = 0; (i < 0xc0); i += 4) { + if ((i % 0x10) == 0) + pr_emerg("\n%08x: ", (int) (tp + i)); + pr_emerg("%08x ", (int) *sp++); + } + pr_emerg("\n"); + pr_emerg("\n"); + + pr_emerg("\nUSER STACK:"); + tp = (unsigned char *) (fp->sp - 0x10); + for (sp = (unsigned long *) tp, i = 0; (i < 0x80); i += 4) { + if ((i % 0x10) == 0) + pr_emerg("\n%08x: ", (int) (tp + i)); + pr_emerg("%08x ", (int) *sp++); + } + pr_emerg("\n\n"); +} + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long fp, pc; + unsigned long stack_page; + int count = 0; + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + + stack_page = (unsigned long)p; + fp = ((struct switch_stack *)p->thread.ksp)->fp; /* ;dgt2 */ + do { + if (fp < stack_page+sizeof(struct task_struct) || + fp >= 8184+stack_page) /* ;dgt2;tmp */ + return 0; + pc = ((unsigned long *)fp)[1]; + if (!in_sched_functions(pc)) + return pc; + fp = *(unsigned long *) fp; + } while (count++ < 16); /* ;dgt2;tmp */ + return 0; +} + +/* + * Do necessary setup to start up a newly executed thread. + * Will startup in user mode (status_extension = 0). + */ +void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) +{ + memset((void *) regs, 0, sizeof(struct pt_regs)); + regs->estatus = ESTATUS_EPIE | ESTATUS_EU; + regs->ea = pc; + regs->sp = sp; +} + +#include + +/* Fill in the FPU structure for a core dump. */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r) +{ + return 0; /* Nios2 has no FPU and thus no FPU registers */ +} diff --git a/arch/nios2/kernel/prom.c b/arch/nios2/kernel/prom.c new file mode 100644 index 0000000000000..a5aebdcd5882d --- /dev/null +++ b/arch/nios2/kernel/prom.c @@ -0,0 +1,117 @@ +/* + * Device tree support + * + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Thomas Chou + * + * Based on MIPS support for CONFIG_OF device tree support + * + * Copyright (C) 2010 Cisco Systems Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +void __init early_init_dt_add_memory_arch(u64 base, u64 size) +{ + u64 kernel_start = (u64)virt_to_phys(_text); + + if (!memory_size && + (kernel_start >= base) && (kernel_start < (base + size))) + memory_size = size; + + return; +} + +void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align) +{ + return __alloc_bootmem(size, align, __pa(MAX_DMA_ADDRESS)); +} + +void __init early_init_devtree(void *params) +{ + __be32 *dtb = (u32*)__dtb_start; +#if defined(CONFIG_NIOS2_DTB_AT_PHYS_ADDR) + if (be32_to_cpup((__be32 *)CONFIG_NIOS2_DTB_PHYS_ADDR) == + OF_DT_HEADER) { + params = (void *)CONFIG_NIOS2_DTB_PHYS_ADDR; + early_init_dt_scan(params); + return; + } +#endif + if (be32_to_cpu((__be32) *dtb) == OF_DT_HEADER) + params = (void *)__dtb_start; + + early_init_dt_scan(params); + +} + +#ifdef CONFIG_EARLY_PRINTK +static int __init early_init_dt_scan_serial(unsigned long node, + const char *uname, int depth, void *data) +{ + u64 *addr64 = (u64 *) data; + char *p; + + /* only consider serial nodes */ + if (strncmp(uname, "serial", 6) != 0) + return 0; + + p = of_get_flat_dt_prop(node, "compatible", NULL); + if (!p) + return 0; + + /* + * We found an altera_jtaguart but it wasn't configured for console, so + * skip it. + */ +#ifndef CONFIG_SERIAL_ALTERA_JTAGUART_CONSOLE + if (strncmp(p, "ALTR,juart", 10) == 0) + return 0; +#endif + + /* + * Same for altera_uart. + */ +#ifndef CONFIG_SERIAL_ALTERA_UART_CONSOLE + if (strncmp(p, "ALTR,uart", 9) == 0) + return 0; +#endif + + *addr64 = fdt_translate_address((const void *)initial_boot_params, + node); + + return *addr64 == OF_BAD_ADDR ? 0 : 1; +} + +unsigned long __init early_altera_uart_or_juart_console(void) +{ + u64 base = 0; + + if (of_scan_flat_dt(early_init_dt_scan_serial, &base)) + return (unsigned long)(base + CONFIG_IO_REGION_BASE); + else + return 0; +} +#endif /* CONFIG_EARLY_PRINTK */ diff --git a/arch/nios2/kernel/ptrace.c b/arch/nios2/kernel/ptrace.c new file mode 100644 index 0000000000000..5db61796b2c11 --- /dev/null +++ b/arch/nios2/kernel/ptrace.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * + * based on arch/m68knommu/kernel/ptrace.c + * + * Copyright (C) 1994 by Hamish Macdonald + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* determines which bits in the SR the user has access to. */ +/* 1 = access 0 = no access */ +#define SR_MASK 0x00000000 + +/* Find the stack offset for a register, relative to thread.ksp. */ +#define PT_REG(reg) ((long)&((struct pt_regs *)0)->reg) +#define SW_REG(reg) ((long)&((struct switch_stack *)0)->reg \ + - sizeof(struct switch_stack)) + +/* Mapping from PT_xxx to the stack offset at which the register is + * saved. + */ +static int regoff[] = { + -1, PT_REG(r1), PT_REG(r2), PT_REG(r3), + PT_REG(r4), PT_REG(r5), PT_REG(r6), PT_REG(r7), + PT_REG(r8), PT_REG(r9), PT_REG(r10), PT_REG(r11), + PT_REG(r12), PT_REG(r13), PT_REG(r14), PT_REG(r15), /* reg 15 */ + SW_REG(r16), SW_REG(r17), SW_REG(r18), SW_REG(r19), + SW_REG(r20), SW_REG(r21), SW_REG(r22), SW_REG(r23), + -1, -1, PT_REG(gp), PT_REG(sp), + PT_REG(fp), PT_REG(ea), -1, PT_REG(ra), /* reg 31 */ + PT_REG(ea), -1, -1, -1, /* use ea for pc */ + -1, -1, -1, -1, + -1, -1, -1, -1 /* reg 43 */ +}; + +/* + * Get contents of register REGNO in task TASK. + */ +static inline long get_reg(struct task_struct *task, int regno) +{ + unsigned long *addr; + + if (regno >= ARRAY_SIZE(regoff) || regoff[regno] == -1) + return 0; + + addr = (unsigned long *)((char *)task->thread.kregs + regoff[regno]); + return *addr; +} + +/* + * Write contents of register REGNO in task TASK. + */ +static inline int put_reg(struct task_struct *task, int regno, + unsigned long data) +{ + unsigned long *addr; + + if (regno >= ARRAY_SIZE(regoff) || regoff[regno] == -1) + return -1; + + addr = (unsigned long *)((char *)task->thread.kregs + regoff[regno]); + *addr = data; + return 0; +} + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Nothing special to do here, no processor debug support. + */ +void ptrace_disable(struct task_struct *child) +{ +} + +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + unsigned long tmp; + unsigned int i; + int ret; + + switch (request) { + /* read the word at location addr in the USER area. */ + case PTRACE_PEEKUSR: + pr_debug("PEEKUSR: addr=0x%08lx\n", addr); + ret = -EIO; + if (addr & 3) + break; + + addr = addr >> 2; /* temporary hack. */ + ret = -EIO; + if (addr < ARRAY_SIZE(regoff)) + tmp = get_reg(child, addr); + else if (addr == PT_TEXT_ADDR / 4) + tmp = child->mm->start_code; + else if (addr == PT_DATA_ADDR / 4) + tmp = child->mm->start_data; + else if (addr == PT_TEXT_END_ADDR / 4) + tmp = child->mm->end_code; + else + break; + ret = put_user(tmp, (unsigned long *) data); + pr_debug("PEEKUSR: rdword=0x%08lx\n", tmp); + break; + /* write the word at location addr in the USER area */ + case PTRACE_POKEUSR: + pr_debug("POKEUSR: addr=0x%08lx, data=0x%08lx\n", addr, data); + ret = -EIO; + if (addr & 3) + break; + + addr = addr >> 2; /* temporary hack. */ + + if (addr == PTR_ESTATUS) { + data &= SR_MASK; + data |= get_reg(child, PTR_ESTATUS) & ~(SR_MASK); + } + if (addr < ARRAY_SIZE(regoff)) { + if (put_reg(child, addr, data)) + break; + ret = 0; + break; + } + break; + /* Get all gp regs from the child. */ + case PTRACE_GETREGS: + pr_debug("GETREGS\n"); + for (i = 0; i < ARRAY_SIZE(regoff); i++) { + tmp = get_reg(child, i); + if (put_user(tmp, (unsigned long *) data)) { + ret = -EFAULT; + break; + } + data += sizeof(long); + } + ret = 0; + break; + /* Set all gp regs in the child. */ + case PTRACE_SETREGS: + pr_debug("SETREGS\n"); + for (i = 0; i < ARRAY_SIZE(regoff); i++) { + if (get_user(tmp, (unsigned long *) data)) { + ret = -EFAULT; + break; + } + if (i == PTR_ESTATUS) { + tmp &= SR_MASK; + tmp |= get_reg(child, PTR_ESTATUS) & ~(SR_MASK); + } + put_reg(child, i, tmp); + data += sizeof(long); + } + ret = 0; + break; + default: + ret = ptrace_request(child, request, addr, data); + } + + return ret; +} + +asmlinkage void syscall_trace(void) +{ + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + if (!(current->ptrace & PT_PTRACED)) + return; + current->exit_code = SIGTRAP; + current->state = TASK_STOPPED; + ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) + ? 0x80 : 0)); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } +} diff --git a/arch/nios2/kernel/setup.c b/arch/nios2/kernel/setup.c new file mode 100644 index 0000000000000..f591c803a4a37 --- /dev/null +++ b/arch/nios2/kernel/setup.c @@ -0,0 +1,229 @@ +/* + * Nios2-specific parts of system setup + * + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * Copyright (C) 2001 Vic Phillips + * + * based on kernel/setup.c from m68knommu + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +unsigned long memory_start; +EXPORT_SYMBOL(memory_start); + +unsigned long memory_end; +EXPORT_SYMBOL(memory_end); + +unsigned long memory_size; + +/* r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11*/ +/* r12 r13 r14 r15 or2 ra fp sp gp es ste ea*/ +static struct pt_regs fake_regs = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0}; + +/* Copy a short hook instruction sequence to the exception address */ +static inline void copy_exception_handler(unsigned int addr) +{ + unsigned int start = (unsigned int) exception_handler_hook; + volatile unsigned int tmp = 0; + + if (start == addr) { + /* The CPU exception address already points to the handler. */ + return; + } + + /* FIXME: check overlap of source and destination address here? */ + + __asm__ __volatile__ ( + "ldw %2,0(%0)\n" + "stw %2,0(%1)\n" + "ldw %2,4(%0)\n" + "stw %2,4(%1)\n" + "ldw %2,8(%0)\n" + "stw %2,8(%1)\n" + "flushd 0(%1)\n" + "flushd 4(%1)\n" + "flushd 8(%1)\n" + "flushi %1\n" + "addi %1,%1,4\n" + "flushi %1\n" + "addi %1,%1,4\n" + "flushi %1\n" + "flushp\n" + : /* no output registers */ + : "r" (start), "r" (addr), "r" (tmp) + : "memory" + ); +} + +/* Copy the fast TLB miss handler */ +static inline void copy_fast_tlb_miss_handler(unsigned int addr) +{ + unsigned int start = (unsigned int) fast_handler; + unsigned int end = (unsigned int) fast_handler_end; + volatile unsigned int tmp = 0; + + /* FIXME: check overlap of source and destination address here? */ + + __asm__ __volatile__ ( + "1:\n" + " ldw %3,0(%0)\n" + " stw %3,0(%1)\n" + " flushd 0(%1)\n" + " flushi %1\n" + " flushp\n" + " addi %0,%0,4\n" + " addi %1,%1,4\n" + " bne %0,%2,1b\n" + : /* no output registers */ + : "r" (start), "r" (addr), "r" (end), "r" (tmp) + : "memory" + ); +} + +/* + * save args passed from u-boot, called from head.S + * + * @r4: NIOS magic + * @r5: initrd start + * @r6: initrd end or fdt + * @r7: kernel command line + */ +asmlinkage void __init nios2_boot_init(unsigned r4, unsigned r5, unsigned r6, + unsigned r7) +{ + unsigned dtb_passed = 0; + char cmdline_passed[COMMAND_LINE_SIZE] = { 0, }; + +#if defined(CONFIG_PASS_CMDLINE) + if (r4 == 0x534f494e) { /* r4 is magic NIOS */ +#if defined(CONFIG_BLK_DEV_INITRD) + if (r5) { /* initramfs */ + initrd_start = r5; + initrd_end = r6; + } +#endif /* CONFIG_BLK_DEV_INITRD */ + dtb_passed = r6; + + if (r7) + strncpy(cmdline_passed, (char *)r7, COMMAND_LINE_SIZE); + } +#endif + + early_init_devtree((void *)dtb_passed); + +#ifndef CONFIG_CMDLINE_FORCE + if (cmdline_passed[0]) + strncpy(boot_command_line, cmdline_passed, COMMAND_LINE_SIZE); +#ifdef CONFIG_CMDLINE_IGNORE_DTB + else + strncpy(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE); +#endif +#endif +} + +void __init setup_arch(char **cmdline_p) +{ + int bootmap_size; + + console_verbose(); + +#ifdef CONFIG_EARLY_PRINTK + setup_early_printk(); +#endif + + memory_start = PAGE_ALIGN((unsigned long)__pa(_end)); + memory_end = (unsigned long) CONFIG_MEM_BASE + memory_size; + + init_mm.start_code = (unsigned long) _stext; + init_mm.end_code = (unsigned long) _etext; + init_mm.end_data = (unsigned long) _edata; + init_mm.brk = (unsigned long) _end; + init_task.thread.kregs = &fake_regs; + + /* Keep a copy of command line */ + *cmdline_p = boot_command_line; + + /* + * give all the memory to the bootmap allocator, tell it to put the + * boot mem_map at the start of memory + */ + pr_debug("init_bootmem_node(?,%#lx, %#x, %#lx)\n", + PFN_UP(memory_start), PFN_DOWN(PHYS_OFFSET), + PFN_DOWN(memory_end)); + bootmap_size = init_bootmem_node(NODE_DATA(0), + PFN_UP(memory_start), + PFN_DOWN(PHYS_OFFSET), + PFN_DOWN(memory_end)); + + /* + * free the usable memory, we have to make sure we do not free + * the bootmem bitmap so we then reserve it after freeing it :-) + */ + pr_debug("free_bootmem(%#lx, %#lx)\n", + memory_start, memory_end - memory_start); + free_bootmem(memory_start, memory_end - memory_start); + + /* + * Reserve the bootmem bitmap itself as well. We do this in two + * steps (first step was init_bootmem()) because this catches + * the (very unlikely) case of us accidentally initializing the + * bootmem allocator with an invalid RAM area. + * + * Arguments are start, size + */ + pr_debug("reserve_bootmem(%#lx, %#x)\n", memory_start, bootmap_size); + reserve_bootmem(memory_start, bootmap_size, BOOTMEM_DEFAULT); + +#ifdef CONFIG_BLK_DEV_INITRD + if (initrd_start) { + reserve_bootmem(virt_to_phys((void *)initrd_start), + initrd_end - initrd_start, BOOTMEM_DEFAULT); + } +#endif /* CONFIG_BLK_DEV_INITRD */ + + unflatten_and_copy_device_tree(); + + setup_cpuinfo(); + + copy_exception_handler(cpuinfo.exception_addr); + + mmu_init(); + + copy_fast_tlb_miss_handler(cpuinfo.fast_tlb_miss_exc_addr); + + /* + * Initialize MMU context handling here because data from cpuinfo is + * needed for this. + */ + mmu_context_init(); + + /* + * get kmalloc into gear + */ + paging_init(); + +#if defined(CONFIG_VT) && defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +} diff --git a/arch/nios2/kernel/signal.c b/arch/nios2/kernel/signal.c new file mode 100644 index 0000000000000..22879a167e3b1 --- /dev/null +++ b/arch/nios2/kernel/signal.c @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011-2012 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * Copyright (C) 1991, 1992 Linus Torvalds + * + * This file is based on kernel/signal.c from m68knommu. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +static int do_signal(struct pt_regs *regs, int in_syscall); + +/* + * Do a signal return; undo the signal stack. + * + * Keep the return code on the stack quadword aligned! + * That makes the cache flush below easier. + */ + +struct sigframe { + char retcode[12]; + unsigned long extramask[_NSIG_WORDS-1]; + struct sigcontext sc; +}; + +struct rt_sigframe { + char retcode[12]; + struct siginfo info; + struct ucontext uc; +}; + +static inline int restore_sigcontext(struct pt_regs *regs, + struct sigcontext *usc, void *fp, int *pr2) +{ + int err = 0; + int estatus; + + /* Always make any pending restarted system calls return -EINTR */ + current_thread_info()->restart_block.fn = do_no_restart_syscall; + + estatus = regs->estatus; + + /* get previous pt_regs */ + if (copy_from_user(regs, &usc->regs, sizeof(*regs))) + goto badframe; + + /* Prevent user from being able to change + * certain processor status bits. Currently nothing. + */ + regs->estatus = (estatus & 0xffffffff) | (regs->estatus & 0); + regs->orig_r2 = -1; /* disable syscall checks */ + + *pr2 = regs->r2; + + return err; + +badframe: + return 1; +} + +static inline int rt_restore_ucontext(struct pt_regs *regs, + struct switch_stack *sw, + struct ucontext *uc, int *pr2) +{ + int temp; + greg_t *gregs = uc->uc_mcontext.gregs; + int err; + + err = __get_user(temp, &uc->uc_mcontext.version); + if (temp != MCONTEXT_VERSION) + goto badframe; + /* restore passed registers */ + /* FIXME: What registers should/shoudn't be saved ? + */ + err |= __get_user(regs->r1, &gregs[0]); + err |= __get_user(regs->r2, &gregs[1]); + err |= __get_user(regs->r3, &gregs[2]); + err |= __get_user(regs->r4, &gregs[3]); + err |= __get_user(regs->r5, &gregs[4]); + err |= __get_user(regs->r6, &gregs[5]); + err |= __get_user(regs->r7, &gregs[6]); + err |= __get_user(regs->r8, &gregs[7]); + err |= __get_user(regs->r9, &gregs[8]); + err |= __get_user(regs->r10, &gregs[9]); + err |= __get_user(regs->r11, &gregs[10]); + err |= __get_user(regs->r12, &gregs[11]); + err |= __get_user(regs->r13, &gregs[12]); + err |= __get_user(regs->r14, &gregs[13]); + err |= __get_user(regs->r15, &gregs[14]); + err |= __get_user(sw->r16, &gregs[15]); + err |= __get_user(sw->r17, &gregs[16]); + err |= __get_user(sw->r18, &gregs[17]); + err |= __get_user(sw->r19, &gregs[18]); + err |= __get_user(sw->r20, &gregs[19]); + err |= __get_user(sw->r21, &gregs[20]); + err |= __get_user(sw->r22, &gregs[21]); + err |= __get_user(sw->r23, &gregs[22]); + /* gregs[23] is handled below */ + err |= __get_user(sw->fp, &gregs[24]); /* Verify, should this be + settable */ + err |= __get_user(sw->gp, &gregs[25]); /* Verify, should this be + settable */ + + err |= __get_user(temp, &gregs[26]); /* Not really necessary no user + settable bits */ + err |= __get_user(regs->ea, &gregs[27]); + + err |= __get_user(regs->ra, &gregs[23]); + err |= __get_user(regs->sp, &gregs[28]); + + regs->estatus = (regs->estatus & 0xffffffff); + regs->orig_r2 = -1; /* disable syscall checks */ + + err |= restore_altstack(&uc->uc_stack); + if (err) + goto badframe; + + *pr2 = regs->r2; + return err; + +badframe: + return 1; +} + +asmlinkage int do_sigreturn(struct pt_regs *regs) +{ + struct sigframe *frame = (struct sigframe *) regs->sp; + sigset_t set; + int rval; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__get_user(set.sig[0], &frame->sc.sc_mask) || + (_NSIG_WORDS > 1 && + __copy_from_user(&set.sig[1], &frame->extramask, + sizeof(frame->extramask)))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->sc, frame + 1, &rval)) + goto badframe; + return rval; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int do_rt_sigreturn(struct switch_stack *sw) +{ + struct pt_regs *regs = (struct pt_regs *)(sw + 1); + /* Verify, can we follow the stack back */ + struct rt_sigframe *frame = (struct rt_sigframe *) regs->sp; + sigset_t set; + int rval; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (rt_restore_ucontext(regs, sw, &frame->uc, &rval)) + goto badframe; + + return rval; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +static int setup_sigcontext(struct sigcontext *sc, struct pt_regs *regs, + unsigned long mask) +{ + int err = 0; + + err |= __put_user(mask, &sc->sc_mask); + err |= copy_to_user(&sc->regs, regs, sizeof(*regs)); + return err; +} + +static inline int rt_setup_ucontext(struct ucontext *uc, struct pt_regs *regs) +{ + struct switch_stack *sw = (struct switch_stack *)regs - 1; + greg_t *gregs = uc->uc_mcontext.gregs; + int err = 0; + + err |= __put_user(MCONTEXT_VERSION, &uc->uc_mcontext.version); + err |= __put_user(regs->r1, &gregs[0]); + err |= __put_user(regs->r2, &gregs[1]); + err |= __put_user(regs->r3, &gregs[2]); + err |= __put_user(regs->r4, &gregs[3]); + err |= __put_user(regs->r5, &gregs[4]); + err |= __put_user(regs->r6, &gregs[5]); + err |= __put_user(regs->r7, &gregs[6]); + err |= __put_user(regs->r8, &gregs[7]); + err |= __put_user(regs->r9, &gregs[8]); + err |= __put_user(regs->r10, &gregs[9]); + err |= __put_user(regs->r11, &gregs[10]); + err |= __put_user(regs->r12, &gregs[11]); + err |= __put_user(regs->r13, &gregs[12]); + err |= __put_user(regs->r14, &gregs[13]); + err |= __put_user(regs->r15, &gregs[14]); + err |= __put_user(sw->r16, &gregs[15]); + err |= __put_user(sw->r17, &gregs[16]); + err |= __put_user(sw->r18, &gregs[17]); + err |= __put_user(sw->r19, &gregs[18]); + err |= __put_user(sw->r20, &gregs[19]); + err |= __put_user(sw->r21, &gregs[20]); + err |= __put_user(sw->r22, &gregs[21]); + err |= __put_user(sw->r23, &gregs[22]); + err |= __put_user(regs->ra, &gregs[23]); + err |= __put_user(sw->fp, &gregs[24]); + err |= __put_user(sw->gp, &gregs[25]); + err |= __put_user(regs->ea, &gregs[27]); + err |= __put_user(regs->sp, &gregs[28]); + return err; +} + +static inline void push_cache(unsigned long vaddr) +{ + flush_dcache_range(vaddr, vaddr + 12); + flush_icache_range(vaddr, vaddr + 12); +} + +static inline void *get_sigframe(struct ksignal *ksig, struct pt_regs *regs, + size_t frame_size) +{ + unsigned long usp; + + /* Default to using normal stack. */ + usp = regs->sp; + + /* This is the X/Open sanctioned signal stack switching. */ + usp = sigsp(usp, ksig); + + /* Verify, is it 32 or 64 bit aligned */ + return (void *)((usp - frame_size) & -8UL); +} + +static int setup_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct sigframe *frame; + int err = 0; + + frame = get_sigframe(ksig, regs, sizeof(*frame)); + + if (_NSIG_WORDS > 1) + err |= copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + + err |= setup_sigcontext(&frame->sc, regs, set->sig[0]); + + /* Set up to return from userspace. */ + regs->ra = (unsigned long) &frame->retcode[0]; + + /* movi r2,__NR_sigreturn */ + err |= __put_user(0x00800004 + (__NR_sigreturn << 6), + (long *)(frame->retcode)); + /* trap */ + err |= __put_user(0x003b683a, (long *)(frame->retcode + 4)); + + if (err) + return -EFAULT; + + push_cache((unsigned long) &frame->retcode); + + /* Set up registers for signal handler */ + regs->sp = (unsigned long) frame; + regs->r4 = ksig->sig; + regs->ea = (unsigned long) ksig->ka.sa.sa_handler; + return 0; +} + +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct rt_sigframe *frame; + int err = 0; + + frame = get_sigframe(ksig, regs, sizeof(*frame)); + + err |= copy_siginfo_to_user(&frame->info, &ksig->info); + + /* Create the ucontext. */ + err |= __put_user(0, &frame->uc.uc_flags); + err |= __put_user(0, &frame->uc.uc_link); + err |= __save_altstack(&frame->uc.uc_stack, regs->sp); + err |= rt_setup_ucontext(&frame->uc, regs); + err |= copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + /* Set up to return from userspace. */ + regs->ra = (unsigned long) &frame->retcode[0]; + + /* movi r2,__NR_rt_sigreturn */ + err |= __put_user(0x00800004 + (__NR_rt_sigreturn << 6), + (long *)(frame->retcode)); + /* trap */ + err |= __put_user(0x003b683a, (long *)(frame->retcode + 4)); + + if (err) + return -EFAULT; + + push_cache((unsigned long) &frame->retcode); + + /* Set up registers for signal handler */ + regs->sp = (unsigned long) frame; + regs->r4 = ksig->sig; + regs->r5 = (unsigned long) &frame->info; + regs->r6 = (unsigned long) &frame->uc; + regs->ea = (unsigned long) ksig->ka.sa.sa_handler; + return 0; +} + +static inline void handle_restart(struct pt_regs *regs, struct k_sigaction *ka, + int has_handler) +{ + switch (regs->r2) { + case ERESTART_RESTARTBLOCK: + case ERESTARTNOHAND: + regs->r2 = EINTR; + regs->r7 = 1; + break; + case ERESTARTSYS: + if (has_handler && !(ka->sa.sa_flags & SA_RESTART)) { + regs->r2 = EINTR; + regs->r7 = 1; + break; + } + /* fallthrough */ + case ERESTARTNOINTR: + regs->r2 = regs->orig_r2; + regs->r7 = regs->orig_r7; + regs->ea -= 4; + break; + } +} + +/* + * OK, we're invoking a handler + */ +static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) +{ + int ret; + sigset_t *oldset = sigmask_to_save(); + + /* set up the stack frame */ + if (ksig->ka.sa.sa_flags & SA_SIGINFO) + ret = setup_rt_frame(ksig, oldset, regs); + else + ret = setup_frame(ksig, oldset, regs); + + signal_setup_done(ret, ksig, 0); +} + +static int do_signal(struct pt_regs *regs, int in_syscall) +{ + struct ksignal ksig; + + /* FIXME - Do we still need to do this ? */ + current->thread.kregs = regs; + + if (get_signal(&ksig)) { + /* + * Are we from a system call? If so, check system call + * restarting. + */ + if (in_syscall) + handle_restart(regs, &ksig.ka, 1); + /* Whee! Actually deliver the signal. */ + handle_signal(&ksig, regs); + return 1; + } + + /* + * No signal to deliver to the process - restart the syscall. + */ + if (in_syscall) { + /* Did the syscall return an error code */ + if (regs->r7 == 1) { + if (regs->r2 == ERESTARTNOHAND || + regs->r2 == ERESTARTSYS || + regs->r2 == ERESTARTNOINTR) { + regs->r2 = regs->orig_r2; + regs->r7 = regs->orig_r7; + regs->ea -= 4; + } else if (regs->r2 == ERESTART_RESTARTBLOCK) { + regs->r2 = __NR_restart_syscall; + regs->ea -= 4; + } + } + } + + return 0; +} + +asmlinkage void do_notify_resume(struct pt_regs *regs, int in_syscall) +{ + /* + * We want the common case to go fast, which is why we may in certain + * cases get here from kernel mode. Just return without doing anything + * if so. + */ + if (!user_mode(regs)) + return; + + if (test_thread_flag(TIF_SIGPENDING)) + do_signal(regs, in_syscall); + + if (test_and_clear_thread_flag(TIF_NOTIFY_RESUME)) + tracehook_notify_resume(regs); +} diff --git a/arch/nios2/kernel/sys_nios2.c b/arch/nios2/kernel/sys_nios2.c new file mode 100644 index 0000000000000..ba5fd091c3b0c --- /dev/null +++ b/arch/nios2/kernel/sys_nios2.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011-2012 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include + +#include +#include + + +/* We don't want to use generic sys_clone because Nios II passes all arguments + * on stack. And we need to save all these registers, which means we need + * push all these registers on top of pt_regs. So, it is better to pass in + * pt_regs* and extract the arguments for do_fork() from here. + */ +asmlinkage int nios2_clone(struct pt_regs *regs) +{ + unsigned long flags; + unsigned long newsp; + int __user *parent_tidptr, *child_tidptr; + + flags = regs->r4; + newsp = regs->r5; + if (newsp == 0) + newsp = regs->sp; + + parent_tidptr = (int __user *) regs->r6; + child_tidptr = (int __user *) regs->r8; + + return do_fork(flags, newsp, 0, parent_tidptr, child_tidptr); +} + +asmlinkage long sys_mmap(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long offset) +{ + if (offset & ~PAGE_MASK) + return -EINVAL; + + return sys_mmap_pgoff(addr, len, prot, flags, fd, offset >> PAGE_SHIFT); +} + +/* sys_cacheflush -- flush the processor cache. */ +asmlinkage int sys_cacheflush(unsigned long addr, int scope, int cache, + unsigned long len) +{ + struct vm_area_struct *vma; + + if (len == 0) + return 0; + + /* Check for overflow */ + if (addr + len < addr) + return -EFAULT; + + /* + * Verify that the specified address region actually belongs + * to this process. + */ + vma = find_vma(current->mm, addr); + if (vma == NULL || addr < vma->vm_start || addr + len > vma->vm_end) + return -EFAULT; + + /* Ignore the scope and cache arguments. */ + flush_cache_range(vma, addr, addr + len); + + return 0; +} + +asmlinkage int sys_getpagesize(void) +{ + return PAGE_SIZE; +} + +#if defined(CONFIG_FB) || defined(CONFIG_FB_MODULE) +#include +unsigned long get_fb_unmapped_area(struct file *filp, unsigned long orig_addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + + struct fb_info *info = filp->private_data; + return (unsigned long)info->screen_base; +} +EXPORT_SYMBOL(get_fb_unmapped_area); +#endif /* CONFIG_FB */ diff --git a/arch/nios2/kernel/syscalltable.S b/arch/nios2/kernel/syscalltable.S new file mode 100644 index 0000000000000..e9c4a37fe8694 --- /dev/null +++ b/arch/nios2/kernel/syscalltable.S @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * Derived from m68knommu + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include + +.text +ALIGN +ENTRY(sys_call_table) + .long sys_restart_syscall /* 0 */ + .long sys_exit + .long __sys_fork + .long sys_read + .long sys_write + .long sys_open /* 5 */ + .long sys_close + .long sys_waitpid + .long sys_creat + .long sys_link + .long sys_unlink /* 10 */ + .long sys_execve + .long sys_chdir + .long sys_time + .long sys_mknod + .long sys_chmod /* 15 */ + .long sys_chown + .long sys_ni_syscall /* old sys_break */ + .long sys_ni_syscall /* old sys_stat */ + .long sys_lseek + .long sys_getpid /* 20 */ + .long sys_mount + .long sys_oldumount + .long sys_setuid + .long sys_getuid + .long sys_stime /* 25 */ + .long sys_ptrace + .long sys_alarm + .long sys_ni_syscall /* old sys_fstat */ + .long sys_pause + .long sys_utime /* 30 */ + .long sys_ni_syscall /* old sys_stty */ + .long sys_ni_syscall /* old sys_gtty */ + .long sys_access + .long sys_nice + .long sys_ni_syscall /* 35: old sys_ftime */ + .long sys_sync + .long sys_kill + .long sys_rename + .long sys_mkdir + .long sys_rmdir /* 40 */ + .long sys_dup + .long sys_pipe + .long sys_times + .long sys_ni_syscall /* old sys_prof */ + .long sys_brk /* 45 */ + .long sys_setgid + .long sys_getgid + .long sys_ni_syscall /* old sys_signal */ + .long sys_geteuid + .long sys_getegid /* 50 */ + .long sys_acct + .long sys_umount + .long sys_ni_syscall /* old sys_lock */ + .long sys_ioctl + .long sys_fcntl /* 55 */ + .long sys_ni_syscall /* old sys_mpx */ + .long sys_setpgid + .long sys_ni_syscall /* old sys_ulimit */ + .long sys_ni_syscall /* old sys_olduname */ + .long sys_umask /* 60 */ + .long sys_chroot + .long sys_ustat + .long sys_dup2 + .long sys_getppid + .long sys_getpgrp /* 65 */ + .long sys_setsid + .long sys_sigaction + .long sys_ni_syscall /* old sys_sgetmask */ + .long sys_ni_syscall /* old sys_ssetmask */ + .long sys_setreuid /* 70 */ + .long sys_setregid + .long sys_sigsuspend + .long sys_sigpending + .long sys_sethostname + .long sys_setrlimit /* 75 */ + .long sys_ni_syscall /* old sys_getrlimit */ + .long sys_getrusage + .long sys_gettimeofday + .long sys_settimeofday + .long sys_getgroups /* 80 */ + .long sys_setgroups + .long sys_old_select + .long sys_symlink + .long sys_ni_syscall /* old sys_lstat */ + .long sys_readlink /* 85 */ + .long sys_uselib + .long sys_swapon + .long sys_reboot + .long sys_ni_syscall /* old sys_readdir */ + .long sys_mmap /* 90 */ + .long sys_munmap + .long sys_truncate + .long sys_ftruncate + .long sys_fchmod + .long sys_fchown /* 95 */ + .long sys_getpriority + .long sys_setpriority + .long sys_ni_syscall /* old sys_profil */ + .long sys_statfs + .long sys_fstatfs /* 100 */ + .long sys_ni_syscall /* old sys_ioperm */ + .long sys_socketcall + .long sys_syslog + .long sys_setitimer + .long sys_getitimer /* 105 */ + .long sys_newstat + .long sys_newlstat + .long sys_newfstat + .long sys_ni_syscall /* old sys_uname */ + .long sys_ni_syscall /* old sys_iopl */ + .long sys_vhangup + .long sys_ni_syscall /* old sys_idle */ + .long sys_nios2cmpxchg /* nios2-specific compare and exchange syscall + for atomic operations */ + .long sys_wait4 + .long sys_swapoff /* 115 */ + .long sys_sysinfo + .long sys_ipc + .long sys_fsync + .long sys_sigreturn + .long __sys_clone /* 120 */ + .long sys_setdomainname + .long sys_newuname + .long sys_cacheflush /* modify_ldt for i386 */ + .long sys_adjtimex + .long sys_mprotect /* 125 */ + .long sys_sigprocmask + .long sys_ni_syscall /* old sys_create_module */ + .long sys_init_module + .long sys_delete_module + .long sys_ni_syscall /* 130: old sys_get_kernel_syms */ + .long sys_quotactl + .long sys_getpgid + .long sys_fchdir + .long sys_bdflush + .long sys_sysfs /* 135 */ + .long sys_personality + .long sys_ni_syscall /* old sys_afs_syscall */ + .long sys_setfsuid + .long sys_setfsgid + .long sys_llseek /* 140 */ + .long sys_getdents + .long sys_select + .long sys_flock + .long sys_msync + .long sys_readv /* 145 */ + .long sys_writev + .long sys_getsid + .long sys_fdatasync + .long sys_sysctl + .long sys_mlock /* 150 */ + .long sys_munlock + .long sys_mlockall + .long sys_munlockall + .long sys_sched_setparam + .long sys_sched_getparam /* 155 */ + .long sys_sched_setscheduler + .long sys_sched_getscheduler + .long sys_sched_yield + .long sys_sched_get_priority_max + .long sys_sched_get_priority_min /* 160 */ + .long sys_sched_rr_get_interval + .long sys_nanosleep + .long sys_mremap + .long sys_setresuid + .long sys_getresuid /* 165 */ + .long sys_getpagesize + .long sys_ni_syscall /* old sys_query_module */ + .long sys_poll + .long sys_ni_syscall /* old sys_nfsservctl */ + .long sys_setresgid /* 170 */ + .long sys_getresgid + .long sys_prctl + .long sys_rt_sigreturn + .long sys_rt_sigaction + .long sys_rt_sigprocmask /* 175 */ + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend + .long sys_pread64 /* 180 */ + .long sys_pwrite64 + .long sys_lchown + .long sys_getcwd + .long sys_capget + .long sys_capset /* 185 */ + .long sys_sigaltstack + .long sys_sendfile + .long sys_ni_syscall /* old sys_getpmsg (streams1) */ + .long sys_ni_syscall /* old sys_putpmsg (streams2) */ + .long __sys_vfork /* 190 */ + .long sys_getrlimit + .long sys_mmap_pgoff + .long sys_truncate64 + .long sys_ftruncate64 + .long sys_stat64 /* 195 */ + .long sys_lstat64 + .long sys_fstat64 + .long sys_chown + .long sys_getuid + .long sys_getgid /* 200 */ + .long sys_geteuid + .long sys_getegid + .long sys_setreuid + .long sys_setregid + .long sys_getgroups /* 205 */ + .long sys_setgroups + .long sys_fchown + .long sys_setresuid + .long sys_getresuid + .long sys_setresgid /* 210 */ + .long sys_getresgid + .long sys_lchown + .long sys_setuid + .long sys_setgid + .long sys_setfsuid /* 215 */ + .long sys_setfsgid + .long sys_pivot_root + .long sys_ni_syscall /* unused */ + .long sys_ni_syscall /* unused */ + .long sys_getdents64 /* 220 */ + .long sys_gettid + .long sys_tkill + .long sys_setxattr + .long sys_lsetxattr + .long sys_fsetxattr /* 225 */ + .long sys_getxattr + .long sys_lgetxattr + .long sys_fgetxattr + .long sys_listxattr + .long sys_llistxattr /* 230 */ + .long sys_flistxattr + .long sys_removexattr + .long sys_lremovexattr + .long sys_fremovexattr + .long sys_futex /* 235 */ + .long sys_sendfile64 + .long sys_mincore + .long sys_madvise + .long sys_fcntl64 + .long sys_readahead /* 240 */ + .long sys_io_setup + .long sys_io_destroy + .long sys_io_getevents + .long sys_io_submit + .long sys_io_cancel /* 245 */ + .long sys_fadvise64 + .long sys_exit_group + .long sys_lookup_dcookie + .long sys_epoll_create + .long sys_epoll_ctl /* 250 */ + .long sys_epoll_wait + .long sys_remap_file_pages + .long sys_set_tid_address + .long sys_timer_create + .long sys_timer_settime /* 255 */ + .long sys_timer_gettime + .long sys_timer_getoverrun + .long sys_timer_delete + .long sys_clock_settime + .long sys_clock_gettime /* 260 */ + .long sys_clock_getres + .long sys_clock_nanosleep + .long sys_statfs64 + .long sys_fstatfs64 + .long sys_tgkill /* 265 */ + .long sys_utimes + .long sys_fadvise64_64 + .long sys_mbind + .long sys_get_mempolicy + .long sys_set_mempolicy /* 270 */ + .long sys_mq_open + .long sys_mq_unlink + .long sys_mq_timedsend + .long sys_mq_timedreceive + .long sys_mq_notify /* 275 */ + .long sys_mq_getsetattr + .long sys_waitid + .long sys_ni_syscall /* old sys_setaltroot */ + .long sys_add_key + .long sys_request_key /* 280 */ + .long sys_keyctl + .long sys_ioprio_set + .long sys_ioprio_get + .long sys_inotify_init + .long sys_inotify_add_watch /* 285 */ + .long sys_inotify_rm_watch + .long sys_migrate_pages + .long sys_openat + .long sys_mkdirat + .long sys_mknodat /* 290 */ + .long sys_fchownat + .long sys_futimesat + .long sys_fstatat64 + .long sys_unlinkat + .long sys_renameat /* 295 */ + .long sys_linkat + .long sys_symlinkat + .long sys_readlinkat + .long sys_fchmodat + .long sys_faccessat /* 300 */ + .long sys_pselect6 + .long sys_ppoll + .long sys_unshare + .long sys_set_robust_list + .long sys_get_robust_list /* 305 */ + .long sys_splice + .long sys_sync_file_range + .long sys_tee + .long sys_vmsplice + .long sys_move_pages /* 310 */ + .long sys_sched_setaffinity + .long sys_sched_getaffinity + .long sys_kexec_load + .long sys_getcpu + .long sys_epoll_pwait /* 315 */ + .long sys_utimensat + .long sys_signalfd + .long sys_timerfd_create + .long sys_eventfd + .long sys_pread64 /* 320 */ + .long sys_pwrite64 + .long sys_fallocate + .long sys_timerfd_settime + .long sys_timerfd_gettime + .long sys_signalfd4 /* 325 */ + .long sys_eventfd2 + .long sys_epoll_create1 + .long sys_dup3 + .long sys_pipe2 + .long sys_inotify_init1 /* 330 */ + .long sys_preadv + .long sys_pwritev + .long sys_rt_sigqueueinfo + .long sys_perf_event_open + .long sys_recvmmsg /* 335 */ + .long sys_fanotify_init + .long sys_fanotify_mark + .long sys_prlimit64 + .long sys_name_to_handle_at + .long sys_open_by_handle_at /* 340 */ + .long sys_clock_adjtime + .long sys_syncfs + .long sys_sendmmsg + .long sys_setns + .long sys_process_vm_readv /* 345 */ + .long sys_process_vm_writev + .long sys_kcmp + .long sys_finit_module + .long sys_sched_setattr + .long sys_sched_getattr /* 350 */ + .long sys_renameat2 + + .rept NR_syscalls - 352 + .long sys_ni_syscall + .endr diff --git a/arch/nios2/kernel/time.c b/arch/nios2/kernel/time.c new file mode 100644 index 0000000000000..0677ebaa1dffd --- /dev/null +++ b/arch/nios2/kernel/time.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_SIZE (tick_nsec / 1000) +#define NIOS2_TIMER_PERIOD (timer_freq / HZ) + +#define ALTERA_TIMER_STATUS_REG 0 +#define ALTERA_TIMER_CONTROL_REG 4 +#define ALTERA_TIMER_PERIODL_REG 8 +#define ALTERA_TIMER_PERIODH_REG 12 +#define ALTERA_TIMER_SNAPL_REG 16 +#define ALTERA_TIMER_SNAPH_REG 20 + +#define ALTERA_TIMER_CONTROL_ITO_MSK (0x1) +#define ALTERA_TIMER_CONTROL_CONT_MSK (0x2) +#define ALTERA_TIMER_CONTROL_START_MSK (0x4) +#define ALTERA_TIMER_CONTROL_STOP_MSK (0x8) + +static u32 nios2_timer_count; +static void __iomem *timer_membase; +static u32 timer_freq; + +static inline unsigned long read_timersnapshot(void) +{ + unsigned long count; + + outw(0, timer_membase + ALTERA_TIMER_SNAPL_REG); + count = + inw(timer_membase + ALTERA_TIMER_SNAPH_REG) << 16 | + inw(timer_membase + ALTERA_TIMER_SNAPL_REG); + + return count; +} + +static inline void write_timerperiod(unsigned long period) +{ + outw(period, timer_membase + ALTERA_TIMER_PERIODL_REG); + outw(period >> 16, timer_membase + ALTERA_TIMER_PERIODH_REG); +} + +/* + * timer_interrupt() needs to keep up the real-time clock, + * as well as call the "xtime_update()" routine every clocktick + */ +irqreturn_t timer_interrupt(int irq, void *dummy) +{ + /* Clear the interrupt condition */ + outw(0, timer_membase + ALTERA_TIMER_STATUS_REG); + nios2_timer_count += NIOS2_TIMER_PERIOD; + + profile_tick(CPU_PROFILING); + + xtime_update(1); + + update_process_times(user_mode(get_irq_regs())); + + return IRQ_HANDLED; +} + +static cycle_t nios2_timer_read(struct clocksource *cs) +{ + unsigned long flags; + u32 cycles; + u32 tcn; + + local_irq_save(flags); + tcn = NIOS2_TIMER_PERIOD - 1 - read_timersnapshot(); + cycles = nios2_timer_count; + local_irq_restore(flags); + + return cycles + tcn; +} + +static struct clocksource nios2_timer = { + .name = "timer", + .rating = 250, + .read = nios2_timer_read, + .shift = 20, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static struct irqaction nios2_timer_irq = { + .name = "timer", + .flags = IRQF_TIMER, + .handler = timer_interrupt, +}; + +static void __init nios2_time_init(struct device_node *timer) +{ + int irq; + unsigned int ctrl; + + timer_membase = of_iomap(timer, 0); + if (!timer_membase) + panic("Unable to map timer resource\n"); + + if (of_property_read_u32(timer, "clock-frequency", &timer_freq)) + panic("Unable to get timer clock frequency\n"); + + irq = irq_of_parse_and_map(timer, 0); + if (irq < 0) + panic("Unable to parse timer irq\n"); + + if (setup_irq(irq, &nios2_timer_irq)) + panic("Unable to setup timer irq\n"); + + write_timerperiod(NIOS2_TIMER_PERIOD - 1); + + /* clocksource initialize */ + nios2_timer.mult = clocksource_hz2mult(timer_freq, nios2_timer.shift); + clocksource_register(&nios2_timer); + + /* interrupt enable + continuous + start */ + ctrl = ALTERA_TIMER_CONTROL_ITO_MSK | ALTERA_TIMER_CONTROL_CONT_MSK | + ALTERA_TIMER_CONTROL_START_MSK; + outw(ctrl, timer_membase + ALTERA_TIMER_CONTROL_REG); +} + +void read_persistent_clock(struct timespec *ts) +{ + ts->tv_sec = mktime(2007, 1, 1, 0, 0, 0); + ts->tv_nsec = 0; +} + +void __init time_init(void) +{ + clocksource_of_init(); +} + +CLOCKSOURCE_OF_DECLARE(nios2_timer, "ALTR,timer-1.0", nios2_time_init); diff --git a/arch/nios2/kernel/traps.c b/arch/nios2/kernel/traps.c new file mode 100644 index 0000000000000..af4c93a40fc83 --- /dev/null +++ b/arch/nios2/kernel/traps.c @@ -0,0 +1,188 @@ +/* + * Hardware exception handling + * + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd. + * Copyright (C) 2001 Vic Phillips + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + + +static DEFINE_SPINLOCK(die_lock); + +void die(const char *str, struct pt_regs *regs, long err) +{ + console_verbose(); + spin_lock_irq(&die_lock); + pr_warn("Oops: %s, sig: %ld\n", str, err); + show_regs(regs); + spin_unlock_irq(&die_lock); + /* + * do_exit() should take care of panic'ing from an interrupt + * context so we don't handle it here + */ + do_exit(err); +} + +void _exception(int signo, struct pt_regs *regs, int code, unsigned long addr) +{ + siginfo_t info; + + if (!user_mode(regs)) + die("Exception in kernel mode", regs, signo); + + info.si_signo = signo; + info.si_errno = 0; + info.si_code = code; + info.si_addr = (void __user *) addr; + force_sig_info(signo, &info, current); +} + +/* + * The show_stack is an external API which we do not use ourselves. + */ + +int kstack_depth_to_print = 48; + +void show_stack(struct task_struct *task, unsigned long *stack) +{ + unsigned long *endstack, addr; + int i; + + if (!stack) { + if (task) + stack = (unsigned long *)task->thread.ksp; + else + stack = (unsigned long *)&stack; + } + + addr = (unsigned long) stack; + endstack = (unsigned long *) PAGE_ALIGN(addr); + + pr_emerg("Stack from %08lx:", (unsigned long)stack); + for (i = 0; i < kstack_depth_to_print; i++) { + if (stack + 1 > endstack) + break; + if (i % 8 == 0) + pr_emerg("\n "); + pr_emerg(" %08lx", *stack++); + } + + pr_emerg("\nCall Trace:"); + i = 0; + while (stack + 1 <= endstack) { + addr = *stack++; + /* + * If the address is either in the text segment of the + * kernel, or in the region which contains vmalloc'ed + * memory, it *may* be the address of a calling + * routine; if so, print it so that someone tracing + * down the cause of the crash will be able to figure + * out the call path that was taken. + */ + if (((addr >= (unsigned long) _stext) && + (addr <= (unsigned long) _etext))) { + if (i % 4 == 0) + pr_emerg("\n "); + pr_emerg(" [<%08lx>]", addr); + i++; + } + } + pr_emerg("\n"); +} + +void __init trap_init(void) +{ + /* Nothing to do here */ +} + +/* Breakpoint handler */ +asmlinkage void breakpoint_c(struct pt_regs *fp) +{ + /* + * The breakpoint entry code has moved the PC on by 4 bytes, so we must + * move it back. This could be done on the host but we do it here + * because monitor.S of JTAG gdbserver does it too. + */ + fp->ea -= 4; + _exception(SIGTRAP, fp, TRAP_BRKPT, fp->ea); +} + +#ifndef CONFIG_ALIGNMENT_TRAP +/* Alignment exception handler */ +asmlinkage void handle_unaligned_c(struct pt_regs *fp, int cause) +{ + unsigned long addr = RDCTL(CTL_BADADDR); + + cause >>= 2; + fp->ea -= 4; + + if (fixup_exception(fp)) + return; + + if (!user_mode(fp)) { + pr_alert("Unaligned access from kernel mode, this might be a hardware\n"); + pr_alert("problem, dump registers and restart the instruction\n"); + pr_alert(" BADADDR 0x%08lx\n", addr); + pr_alert(" cause %d\n", cause); + pr_alert(" op-code 0x%08lx\n", *(unsigned long *)(fp->ea)); + show_regs(fp); + return; + } + + _exception(SIGBUS, fp, BUS_ADRALN, addr); +} +#endif /* CONFIG_ALIGNMENT_TRAP */ + +/* Illegal instruction handler */ +asmlinkage void handle_illegal_c(struct pt_regs *fp) +{ + fp->ea -= 4; + _exception(SIGILL, fp, ILL_ILLOPC, fp->ea); +} + +/* Supervisor instruction handler */ +asmlinkage void handle_supervisor_instr(struct pt_regs *fp) +{ + fp->ea -= 4; + _exception(SIGILL, fp, ILL_PRVOPC, fp->ea); +} + +/* Division error handler */ +asmlinkage void handle_diverror_c(struct pt_regs *fp) +{ + fp->ea -= 4; + _exception(SIGFPE, fp, FPE_INTDIV, fp->ea); +} + +/* Unhandled exception handler */ +asmlinkage void unhandled_exception(struct pt_regs *regs, int cause) +{ + unsigned long addr = RDCTL(CTL_BADADDR); + + cause /= 4; + + pr_emerg("Unhandled exception #%d in %s mode (badaddr=0x%08lx)\n", + cause, user_mode(regs) ? "user" : "kernel", addr); + + regs->ea -= 4; + show_regs(regs); + + pr_emerg("opcode: 0x%08lx\n", *(unsigned long *)(regs->ea)); + + /* TODO: What should we do here? WRS code was halting the ISS with + * WRCTL(6,1) and spinning forever afterwards. */ +} diff --git a/arch/nios2/kernel/vmlinux.lds.S b/arch/nios2/kernel/vmlinux.lds.S new file mode 100644 index 0000000000000..ec43c9f72b18a --- /dev/null +++ b/arch/nios2/kernel/vmlinux.lds.S @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +OUTPUT_FORMAT("elf32-littlenios2", "elf32-littlenios2", "elf32-littlenios2") + +OUTPUT_ARCH(nios) +ENTRY(_start) /* Defined in head.S */ + +jiffies = jiffies_64; + +SECTIONS +{ + . = CONFIG_MEM_BASE | CONFIG_KERNEL_REGION_BASE; + + _text = .; + _stext = .; + HEAD_TEXT_SECTION + .text : { + TEXT_TEXT + SCHED_TEXT + LOCK_TEXT + IRQENTRY_TEXT + KPROBES_TEXT + } =0 + _etext = .; + + .got : { + *(.got.plt) + *(.igot.plt) + *(.got) + *(.igot) + } + + EXCEPTION_TABLE(L1_CACHE_BYTES) + + . = ALIGN(PAGE_SIZE); + __init_begin = .; + INIT_TEXT_SECTION(PAGE_SIZE) + INIT_DATA_SECTION(PAGE_SIZE) + PERCPU_SECTION(L1_CACHE_BYTES) + __init_end = .; + + _sdata = .; + RO_DATA_SECTION(PAGE_SIZE) + RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE) + _edata = .; + + BSS_SECTION(0, 0, 0) + _end = .; + + STABS_DEBUG + DWARF_DEBUG + NOTES + + DISCARDS +} diff --git a/arch/nios2/lib/Makefile b/arch/nios2/lib/Makefile new file mode 100644 index 0000000000000..f9c5bc02ba357 --- /dev/null +++ b/arch/nios2/lib/Makefile @@ -0,0 +1 @@ +lib-y = memset.o memcpy.o memmove.o \ No newline at end of file diff --git a/arch/nios2/lib/memcpy.c b/arch/nios2/lib/memcpy.c new file mode 100644 index 0000000000000..ac7eec4c403ae --- /dev/null +++ b/arch/nios2/lib/memcpy.c @@ -0,0 +1,199 @@ +/* Extracted from GLIBC memcpy.c and memcopy.h, which is: + Copyright (C) 1991, 1992, 1993, 1997, 2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Torbjorn Granlund (tege@sics.se). + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +/* Type to use for aligned memory operations. + This should normally be the biggest type supported by a single load + and store. */ +#define op_t unsigned long int +#define OPSIZ (sizeof(op_t)) + +/* Optimal type for storing bytes in registers. */ +#define reg_char char + +#define MERGE(w0, sh_1, w1, sh_2) (((w0) >> (sh_1)) | ((w1) << (sh_2))) + +/* Copy exactly NBYTES bytes from SRC_BP to DST_BP, + without any assumptions about alignment of the pointers. */ +#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes) \ +do { \ + size_t __nbytes = (nbytes); \ + while (__nbytes > 0) { \ + unsigned char __x = ((unsigned char *) src_bp)[0]; \ + src_bp += 1; \ + __nbytes -= 1; \ + ((unsigned char *) dst_bp)[0] = __x; \ + dst_bp += 1; \ + } \ +} while (0) + +/* Copy *up to* NBYTES bytes from SRC_BP to DST_BP, with + the assumption that DST_BP is aligned on an OPSIZ multiple. If + not all bytes could be easily copied, store remaining number of bytes + in NBYTES_LEFT, otherwise store 0. */ +/* extern void _wordcopy_fwd_aligned __P ((long int, long int, size_t)); */ +/* extern void _wordcopy_fwd_dest_aligned __P ((long int, long int, size_t)); */ +#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) \ +do { \ + if (src_bp % OPSIZ == 0) \ + _wordcopy_fwd_aligned(dst_bp, src_bp, (nbytes) / OPSIZ);\ + else \ + _wordcopy_fwd_dest_aligned(dst_bp, src_bp, (nbytes) / OPSIZ);\ + src_bp += (nbytes) & -OPSIZ; \ + dst_bp += (nbytes) & -OPSIZ; \ + (nbytes_left) = (nbytes) % OPSIZ; \ +} while (0) + + +/* Threshold value for when to enter the unrolled loops. */ +#define OP_T_THRES 16 + +/* _wordcopy_fwd_aligned -- Copy block beginning at SRCP to + block beginning at DSTP with LEN `op_t' words (not LEN bytes!). + Both SRCP and DSTP should be aligned for memory operations on `op_t's. */ +/* stream-lined (read x8 + write x8) */ +static void _wordcopy_fwd_aligned(long int dstp, long int srcp, size_t len) +{ + while (len > 7) { + register op_t a0, a1, a2, a3, a4, a5, a6, a7; + a0 = ((op_t *) srcp)[0]; + a1 = ((op_t *) srcp)[1]; + a2 = ((op_t *) srcp)[2]; + a3 = ((op_t *) srcp)[3]; + a4 = ((op_t *) srcp)[4]; + a5 = ((op_t *) srcp)[5]; + a6 = ((op_t *) srcp)[6]; + a7 = ((op_t *) srcp)[7]; + ((op_t *) dstp)[0] = a0; + ((op_t *) dstp)[1] = a1; + ((op_t *) dstp)[2] = a2; + ((op_t *) dstp)[3] = a3; + ((op_t *) dstp)[4] = a4; + ((op_t *) dstp)[5] = a5; + ((op_t *) dstp)[6] = a6; + ((op_t *) dstp)[7] = a7; + + srcp += 8 * OPSIZ; + dstp += 8 * OPSIZ; + len -= 8; + } + while (len > 0) { + *(op_t *)dstp = *(op_t *)srcp; + + srcp += OPSIZ; + dstp += OPSIZ; + len -= 1; + } +} + +/* _wordcopy_fwd_dest_aligned -- Copy block beginning at SRCP to + block beginning at DSTP with LEN `op_t' words (not LEN bytes!). + DSTP should be aligned for memory operations on `op_t's, but SRCP must + *not* be aligned. */ +/* stream-lined (read x4 + write x4) */ +static void _wordcopy_fwd_dest_aligned(long int dstp, long int srcp, + size_t len) +{ + op_t ap; + int sh_1, sh_2; + + /* Calculate how to shift a word read at the memory operation + aligned srcp to make it aligned for copy. */ + + sh_1 = 8 * (srcp % OPSIZ); + sh_2 = 8 * OPSIZ - sh_1; + + /* Make SRCP aligned by rounding it down to the beginning of the `op_t' + it points in the middle of. */ + srcp &= -OPSIZ; + ap = ((op_t *) srcp)[0]; + srcp += OPSIZ; + + while (len > 3) { + op_t a0, a1, a2, a3; + a0 = ((op_t *) srcp)[0]; + a1 = ((op_t *) srcp)[1]; + a2 = ((op_t *) srcp)[2]; + a3 = ((op_t *) srcp)[3]; + ((op_t *) dstp)[0] = MERGE(ap, sh_1, a0, sh_2); + ((op_t *) dstp)[1] = MERGE(a0, sh_1, a1, sh_2); + ((op_t *) dstp)[2] = MERGE(a1, sh_1, a2, sh_2); + ((op_t *) dstp)[3] = MERGE(a2, sh_1, a3, sh_2); + + ap = a3; + srcp += 4 * OPSIZ; + dstp += 4 * OPSIZ; + len -= 4; + } + while (len > 0) { + register op_t a0; + a0 = ((op_t *) srcp)[0]; + ((op_t *) dstp)[0] = MERGE(ap, sh_1, a0, sh_2); + + ap = a0; + srcp += OPSIZ; + dstp += OPSIZ; + len -= 1; + } +} + +void *memcpy(void *dstpp, const void *srcpp, size_t len) +{ + unsigned long int dstp = (long int) dstpp; + unsigned long int srcp = (long int) srcpp; + + /* Copy from the beginning to the end. */ + + /* If there not too few bytes to copy, use word copy. */ + if (len >= OP_T_THRES) { + /* Copy just a few bytes to make DSTP aligned. */ + len -= (-dstp) % OPSIZ; + BYTE_COPY_FWD(dstp, srcp, (-dstp) % OPSIZ); + + /* Copy whole pages from SRCP to DSTP by virtual address + manipulation, as much as possible. */ + + /* PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); */ + + /* Copy from SRCP to DSTP taking advantage of the known + alignment of DSTP. Number of bytes remaining is put in the + third argument, i.e. in LEN. This number may vary from + machine to machine. */ + + WORD_COPY_FWD(dstp, srcp, len, len); + + /* Fall out and copy the tail. */ + } + + /* There are just a few bytes to copy. Use byte memory operations. */ + BYTE_COPY_FWD(dstp, srcp, len); + + return dstpp; +} + +void *memcpyb(void *dstpp, const void *srcpp, unsigned len) +{ + unsigned long int dstp = (long int) dstpp; + unsigned long int srcp = (long int) srcpp; + + BYTE_COPY_FWD(dstp, srcp, len); + + return dstpp; +} diff --git a/arch/nios2/lib/memmove.c b/arch/nios2/lib/memmove.c new file mode 100644 index 0000000000000..c65ef517eb80c --- /dev/null +++ b/arch/nios2/lib/memmove.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include + +#ifdef __HAVE_ARCH_MEMMOVE +void *memmove(void *d, const void *s, size_t count) +{ + unsigned long dst, src; + + if (!count) + return d; + + if (d < s) { + dst = (unsigned long) d; + src = (unsigned long) s; + + if ((count < 8) || ((dst ^ src) & 3)) + goto restup; + + if (dst & 1) { + *(char *)dst++ = *(char *)src++; + count--; + } + if (dst & 2) { + *(short *)dst = *(short *)src; + src += 2; + dst += 2; + count -= 2; + } + while (count > 3) { + *(long *)dst = *(long *)src; + src += 4; + dst += 4; + count -= 4; + } +restup: + while (count--) + *(char *)dst++ = *(char *)src++; + } else { + dst = (unsigned long) d + count; + src = (unsigned long) s + count; + + if ((count < 8) || ((dst ^ src) & 3)) + goto restdown; + + if (dst & 1) { + src--; + dst--; + count--; + *(char *)dst = *(char *)src; + } + if (dst & 2) { + src -= 2; + dst -= 2; + count -= 2; + *(short *)dst = *(short *)src; + } + while (count > 3) { + src -= 4; + dst -= 4; + count -= 4; + *(long *)dst = *(long *)src; + } +restdown: + while (count--) { + src--; + dst--; + *(char *)dst = *(char *)src; + } + } + + return d; +} +#endif /* __HAVE_ARCH_MEMMOVE */ diff --git a/arch/nios2/lib/memset.c b/arch/nios2/lib/memset.c new file mode 100644 index 0000000000000..65e97802f5cc8 --- /dev/null +++ b/arch/nios2/lib/memset.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include + +#ifdef __HAVE_ARCH_MEMSET +void *memset(void *s, int c, size_t count) +{ + int destptr, charcnt, dwordcnt, fill8reg, wrkrega; + + if (!count) + return s; + + c &= 0xFF; + + if (count <= 8) { + char *xs = (char *) s; + + while (count--) + *xs++ = c; + return s; + } + + __asm__ __volatile__ ( + /* fill8 %3, %5 (c & 0xff) */ + " slli %4, %5, 8\n" + " or %4, %4, %5\n" + " slli %3, %4, 16\n" + " or %3, %3, %4\n" + /* Word-align %0 (s) if necessary */ + " andi %4, %0, 0x01\n" + " beq %4, zero, 1f\n" + " addi %1, %1, -1\n" + " stb %3, 0(%0)\n" + " addi %0, %0, 1\n" + "1: mov %2, %1\n" + /* Dword-align %0 (s) if necessary */ + " andi %4, %0, 0x02\n" + " beq %4, zero, 2f\n" + " addi %1, %1, -2\n" + " sth %3, 0(%0)\n" + " addi %0, %0, 2\n" + " mov %2, %1\n" + /* %1 and %2 are how many more bytes to set */ + "2: srli %2, %2, 2\n" + /* %2 is how many dwords to set */ + "3: stw %3, 0(%0)\n" + " addi %0, %0, 4\n" + " addi %2, %2, -1\n" + " bne %2, zero, 3b\n" + /* store residual word and/or byte if necessary */ + " andi %4, %1, 0x02\n" + " beq %4, zero, 4f\n" + " sth %3, 0(%0)\n" + " addi %0, %0, 2\n" + /* store residual byte if necessary */ + "4: andi %4, %1, 0x01\n" + " beq %4, zero, 5f\n" + " stb %3, 0(%0)\n" + "5:\n" + : "=r" (destptr), /* %0 Output */ + "=r" (charcnt), /* %1 Output */ + "=r" (dwordcnt), /* %2 Output */ + "=r" (fill8reg), /* %3 Output */ + "=r" (wrkrega) /* %4 Output */ + : "r" (c), /* %5 Input */ + "0" (s), /* %0 Input/Output */ + "1" (count) /* %1 Input/Output */ + : "memory" /* clobbered */ + ); + + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ diff --git a/arch/nios2/mm/Makefile b/arch/nios2/mm/Makefile new file mode 100644 index 0000000000000..6ef15aae70ef0 --- /dev/null +++ b/arch/nios2/mm/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the Nios2-specific parts of the memory manager. +# + +obj-y := init.o + +obj-y += cacheflush.o + +obj-y += pgtable.o tlb.o uaccess.o fault.o +obj-y += ioremap.o extable.o mmu_context.o + +obj-y += dma-mapping.o \ No newline at end of file diff --git a/arch/nios2/mm/cacheflush.c b/arch/nios2/mm/cacheflush.c new file mode 100644 index 0000000000000..cf06a00c47a4d --- /dev/null +++ b/arch/nios2/mm/cacheflush.c @@ -0,0 +1,270 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + */ + +#include +#include +#include +#include + +#include +#include + +static void __flush_dcache(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(cpuinfo.dcache_line_size - 1); + end += (cpuinfo.dcache_line_size - 1); + end &= ~(cpuinfo.dcache_line_size - 1); + + if (end > start + cpuinfo.dcache_size) + end = start + cpuinfo.dcache_size; + + for (addr = start; addr < end; addr += cpuinfo.dcache_line_size) { + __asm__ __volatile__ (" flushda 0(%0)\n" + : /* Outputs */ + : /* Inputs */ "r"(addr) + /* : No clobber */); + } +} + +static void __flush_dcache_all(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(cpuinfo.dcache_line_size - 1); + end += (cpuinfo.dcache_line_size - 1); + end &= ~(cpuinfo.dcache_line_size - 1); + + if (end > start + cpuinfo.dcache_size) + end = start + cpuinfo.dcache_size; + + for (addr = start; addr < end; addr += cpuinfo.dcache_line_size) { + __asm__ __volatile__ (" flushd 0(%0)\n" + : /* Outputs */ + : /* Inputs */ "r"(addr) + /* : No clobber */); + } +} + +static void __invalidate_dcache(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(cpuinfo.dcache_line_size - 1); + end += (cpuinfo.dcache_line_size - 1); + end &= ~(cpuinfo.dcache_line_size - 1); + + if (end > start + cpuinfo.dcache_size) + end = start + cpuinfo.dcache_size; + + for (addr = start; addr < end; addr += cpuinfo.dcache_line_size) { + __asm__ __volatile__ (" initda 0(%0)\n" + : /* Outputs */ + : /* Inputs */ "r"(addr) + /* : No clobber */); + } +} + +static void __flush_icache(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(cpuinfo.icache_line_size - 1); + end += (cpuinfo.icache_line_size - 1); + end &= ~(cpuinfo.icache_line_size - 1); + + if (end > start + cpuinfo.icache_size) + end = start + cpuinfo.icache_size; + + for (addr = start; addr < end; addr += cpuinfo.icache_line_size) { + __asm__ __volatile__ (" flushi %0\n" + : /* Outputs */ + : /* Inputs */ "r"(addr) + /* : No clobber */); + } + __asm__ __volatile(" flushp\n"); +} + +static void flush_aliases(struct address_space *mapping, struct page *page) +{ + struct mm_struct *mm = current->active_mm; + struct vm_area_struct *mpnt; + pgoff_t pgoff; + + pgoff = page->index; + + flush_dcache_mmap_lock(mapping); + vma_interval_tree_foreach(mpnt, &mapping->i_mmap, pgoff, pgoff) { + unsigned long offset; + + if (mpnt->vm_mm != mm) + continue; + if (!(mpnt->vm_flags & VM_MAYSHARE)) + continue; + + offset = (pgoff - mpnt->vm_pgoff) << PAGE_SHIFT; + flush_cache_page(mpnt, mpnt->vm_start + offset, + page_to_pfn(page)); + } + flush_dcache_mmap_unlock(mapping); +} + +void flush_cache_all(void) +{ + __flush_dcache_all(0, cpuinfo.dcache_size); + __flush_icache(0, cpuinfo.icache_size); +} + +void flush_cache_mm(struct mm_struct *mm) +{ + flush_cache_all(); +} + +void flush_cache_dup_mm(struct mm_struct *mm) +{ + flush_cache_all(); +} + +void flush_icache_range(unsigned long start, unsigned long end) +{ + __flush_icache(start, end); +} + +void flush_dcache_range(unsigned long start, unsigned long end) +{ + __flush_dcache(start, end); +} +EXPORT_SYMBOL(flush_dcache_range); + +void invalidate_dcache_range(unsigned long start, unsigned long end) +{ + __invalidate_dcache(start, end); +} +EXPORT_SYMBOL(invalidate_dcache_range); + +void flush_cache_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end) +{ + __flush_dcache(start, end); + if (vma == NULL || (vma->vm_flags & VM_EXEC)) + __flush_icache(start, end); +} + +void flush_icache_page(struct vm_area_struct *vma, struct page *page) +{ + unsigned long start = (unsigned long) page_address(page); + unsigned long end = start + PAGE_SIZE; + + __flush_icache(start, end); +} + +void flush_cache_page(struct vm_area_struct *vma, unsigned long vmaddr, + unsigned long pfn) +{ + unsigned long start = vmaddr; + unsigned long end = start + PAGE_SIZE; + + __flush_dcache(start, end); + if (vma->vm_flags & VM_EXEC) + __flush_icache(start, end); +} + +void flush_dcache_page(struct page *page) +{ + struct address_space *mapping; + + /* + * The zero page is never written to, so never has any dirty + * cache lines, and therefore never needs to be flushed. + */ + if (page == ZERO_PAGE(0)) + return; + + mapping = page_mapping(page); + + /* Flush this page if there are aliases. */ + if (mapping && !mapping_mapped(mapping)) { + clear_bit(PG_dcache_clean, &page->flags); + } else { + unsigned long start = (unsigned long)page_address(page); + __flush_dcache_all(start, start + PAGE_SIZE); + if (mapping) + flush_aliases(mapping, page); + set_bit(PG_dcache_clean, &page->flags); + } +} +EXPORT_SYMBOL(flush_dcache_page); + +void update_mmu_cache(struct vm_area_struct *vma, + unsigned long address, pte_t *pte) +{ + unsigned long pfn = pte_pfn(*pte); + struct page *page; + + if (!pfn_valid(pfn)) + return; + + /* + * The zero page is never written to, so never has any dirty + * cache lines, and therefore never needs to be flushed. + */ + page = pfn_to_page(pfn); + if (page == ZERO_PAGE(0)) + return; + + if (!PageReserved(page) && + !test_and_set_bit(PG_dcache_clean, &page->flags)) { + unsigned long start = page_to_virt(page); + struct address_space *mapping; + + __flush_dcache(start, start + PAGE_SIZE); + + mapping = page_mapping(page); + if (mapping) + flush_aliases(mapping, page); + } +} + +void copy_user_page(void *vto, void *vfrom, unsigned long vaddr, + struct page *to) +{ + __flush_dcache(vaddr, vaddr + PAGE_SIZE); + copy_page(vto, vfrom); + __flush_dcache((unsigned long)vto, (unsigned long)vto + PAGE_SIZE); +} + +void clear_user_page(void *addr, unsigned long vaddr, struct page *page) +{ + __flush_dcache(vaddr, vaddr + PAGE_SIZE); + clear_page(addr); + __flush_dcache((unsigned long)addr, (unsigned long)addr + PAGE_SIZE); +} + +void copy_from_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, + void *dst, void *src, int len) +{ + flush_cache_page(vma, user_vaddr, page_to_pfn(page)); + memcpy(dst, src, len); + __flush_dcache((unsigned long)src, (unsigned long)src + len); + if (vma->vm_flags & VM_EXEC) + __flush_icache((unsigned long)src, (unsigned long)src + len); +} + +void copy_to_user_page(struct vm_area_struct *vma, struct page *page, + unsigned long user_vaddr, + void *dst, void *src, int len) +{ + flush_cache_page(vma, user_vaddr, page_to_pfn(page)); + memcpy(dst, src, len); + __flush_dcache((unsigned long)dst, (unsigned long)dst + len); + if (vma->vm_flags & VM_EXEC) + __flush_icache((unsigned long)dst, (unsigned long)dst + len); +} diff --git a/arch/nios2/mm/dma-mapping.c b/arch/nios2/mm/dma-mapping.c new file mode 100644 index 0000000000000..a9bafe9f5b5c6 --- /dev/null +++ b/arch/nios2/mm/dma-mapping.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * Based on DMA code from MIPS. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void *dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t gfp) +{ + void *ret; + + /* ignore region specifiers */ + gfp &= ~(__GFP_DMA | __GFP_HIGHMEM); + + /* optimized page clearing */ + gfp |= __GFP_ZERO; + + if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff)) + gfp |= GFP_DMA; + + ret = (void *) __get_free_pages(gfp, get_order(size)); + if (ret != NULL) { + *dma_handle = virt_to_phys(ret); + flush_dcache_range((unsigned long) ret, + (unsigned long) ret + size); + ret = UNCAC_ADDR(ret); + } + + return ret; +} +EXPORT_SYMBOL(dma_alloc_coherent); + +void dma_free_coherent(struct device *dev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + unsigned long addr = (unsigned long) CAC_ADDR((unsigned long) vaddr); + free_pages(addr, get_order(size)); +} +EXPORT_SYMBOL(dma_free_coherent); + +int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction direction) +{ + int i; + + BUG_ON(!valid_dma_direction(direction)); + + for_each_sg(sg, sg, nents, i) { + void *addr; + + addr = sg_virt(sg); + if (addr) { + __dma_sync(addr, sg->length, direction); + sg->dma_address = sg_phys(sg); + } + } + + return nents; +} +EXPORT_SYMBOL(dma_map_sg); + +dma_addr_t dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction direction) +{ + void *addr; + + BUG_ON(!valid_dma_direction(direction)); + + addr = page_address(page) + offset; + __dma_sync(addr, size, direction); + + return page_to_phys(page) + offset; +} +EXPORT_SYMBOL(dma_map_page); + +void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, + enum dma_data_direction direction) +{ + BUG_ON(!valid_dma_direction(direction)); + + if (direction != DMA_TO_DEVICE) + __dma_sync(phys_to_virt(dma_address), size, direction); +} +EXPORT_SYMBOL(dma_unmap_page); + +void dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nhwentries, + enum dma_data_direction direction) +{ + void *addr; + int i; + + BUG_ON(!valid_dma_direction(direction)); + + if (direction == DMA_TO_DEVICE) + return; + + for_each_sg(sg, sg, nhwentries, i) { + addr = sg_virt(sg); + if (addr) + __dma_sync(addr, sg->length, direction); + } +} +EXPORT_SYMBOL(dma_unmap_sg); + +void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, + size_t size, enum dma_data_direction direction) +{ + BUG_ON(!valid_dma_direction(direction)); + + __dma_sync(phys_to_virt(dma_handle), size, direction); +} +EXPORT_SYMBOL(dma_sync_single_for_cpu); + +void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, + size_t size, enum dma_data_direction direction) +{ + BUG_ON(!valid_dma_direction(direction)); + + __dma_sync(phys_to_virt(dma_handle), size, direction); +} +EXPORT_SYMBOL(dma_sync_single_for_device); + +void dma_sync_single_range_for_cpu(struct device *dev, dma_addr_t dma_handle, + unsigned long offset, size_t size, + enum dma_data_direction direction) +{ + BUG_ON(!valid_dma_direction(direction)); + + __dma_sync(phys_to_virt(dma_handle), size, direction); +} +EXPORT_SYMBOL(dma_sync_single_range_for_cpu); + +void dma_sync_single_range_for_device(struct device *dev, dma_addr_t dma_handle, + unsigned long offset, size_t size, + enum dma_data_direction direction) +{ + BUG_ON(!valid_dma_direction(direction)); + + __dma_sync(phys_to_virt(dma_handle), size, direction); +} +EXPORT_SYMBOL(dma_sync_single_range_for_device); + +void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems, + enum dma_data_direction direction) +{ + int i; + + BUG_ON(!valid_dma_direction(direction)); + + /* Make sure that gcc doesn't leave the empty loop body. */ + for_each_sg(sg, sg, nelems, i) + __dma_sync(sg_virt(sg), sg->length, direction); +} +EXPORT_SYMBOL(dma_sync_sg_for_cpu); + +void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, + int nelems, enum dma_data_direction direction) +{ + int i; + + BUG_ON(!valid_dma_direction(direction)); + + /* Make sure that gcc doesn't leave the empty loop body. */ + for_each_sg(sg, sg, nelems, i) + __dma_sync(sg_virt(sg), sg->length, direction); + +} +EXPORT_SYMBOL(dma_sync_sg_for_device); diff --git a/arch/nios2/mm/extable.c b/arch/nios2/mm/extable.c new file mode 100644 index 0000000000000..4d2fc5a589d09 --- /dev/null +++ b/arch/nios2/mm/extable.c @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010, Tobias Klauser + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include + +int fixup_exception(struct pt_regs *regs) +{ + const struct exception_table_entry *fixup; + + fixup = search_exception_tables(regs->ea); + if (fixup) { + regs->ea = fixup->fixup; + return 1; + } + + return 0; +} diff --git a/arch/nios2/mm/fault.c b/arch/nios2/mm/fault.c new file mode 100644 index 0000000000000..bddbb808fbcd1 --- /dev/null +++ b/arch/nios2/mm/fault.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * based on arch/mips/mm/fault.c which is: + * + * Copyright (C) 1995-2000 Ralf Baechle + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define EXC_SUPERV_INSN_ACCESS 9 /* Supervisor only instruction address */ +#define EXC_SUPERV_DATA_ACCESS 11 /* Supervisor only data address */ +#define EXC_X_PROTECTION_FAULT 13 /* TLB permission violation (x) */ +#define EXC_R_PROTECTION_FAULT 14 /* TLB permission violation (r) */ +#define EXC_W_PROTECTION_FAULT 15 /* TLB permission violation (w) */ + +/* + * This routine handles page faults. It determines the address, + * and the problem, and then passes it off to one of the appropriate + * routines. + */ +asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long cause, + unsigned long address) +{ + struct vm_area_struct *vma = NULL; + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; + int code = SEGV_MAPERR; + int fault; + unsigned int flags = 0; + + cause >>= 2; + + /* Restart the instruction */ + regs->ea -= 4; + + /* + * We fault-in kernel-space virtual memory on-demand. The + * 'reference' page table is init_mm.pgd. + * + * NOTE! We MUST NOT take any locks for this case. We may + * be in an interrupt or a critical region, and should + * only copy the information from the master page table, + * nothing more. + */ + if (unlikely(address >= VMALLOC_START && address <= VMALLOC_END)) { + if (user_mode(regs)) + goto bad_area_nosemaphore; + else + goto vmalloc_fault; + } + + if (unlikely(address >= TASK_SIZE)) + goto bad_area_nosemaphore; + + /* + * If we're in an interrupt or have no user + * context, we must not take the fault.. + */ + if (in_atomic() || !mm) + goto bad_area_nosemaphore; + + if (user_mode(regs)) + flags |= FAULT_FLAG_USER; + + down_read(&mm->mmap_sem); + vma = find_vma(mm, address); + if (!vma) + goto bad_area; + if (vma->vm_start <= address) + goto good_area; + if (!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; + if (expand_stack(vma, address)) + goto bad_area; +/* + * Ok, we have a good vm_area for this memory access, so + * we can handle it.. + */ +good_area: + code = SEGV_ACCERR; + + switch (cause) { + case EXC_SUPERV_INSN_ACCESS: + goto bad_area; + case EXC_SUPERV_DATA_ACCESS: + goto bad_area; + case EXC_X_PROTECTION_FAULT: + if (!(vma->vm_flags & VM_EXEC)) + goto bad_area; + break; + case EXC_R_PROTECTION_FAULT: + if (!(vma->vm_flags & VM_READ)) + goto bad_area; + break; + case EXC_W_PROTECTION_FAULT: + if (!(vma->vm_flags & VM_WRITE)) + goto bad_area; + flags = FAULT_FLAG_WRITE; + break; + } + +survive: + /* + * If for any reason at all we couldn't handle the fault, + * make sure we exit gracefully rather than endlessly redo + * the fault. + */ + fault = handle_mm_fault(mm, vma, address, flags); + if (unlikely(fault & VM_FAULT_ERROR)) { + if (fault & VM_FAULT_OOM) + goto out_of_memory; + else if (fault & VM_FAULT_SIGBUS) + goto do_sigbus; + BUG(); + } + if (fault & VM_FAULT_MAJOR) + tsk->maj_flt++; + else + tsk->min_flt++; + + up_read(&mm->mmap_sem); + return; + +/* + * Something tried to access memory that isn't in our memory map.. + * Fix it, but check if it's kernel or user first.. + */ +bad_area: + up_read(&mm->mmap_sem); + +bad_area_nosemaphore: + /* User mode accesses just cause a SIGSEGV */ + if (user_mode(regs)) { + _exception(SIGSEGV, regs, code, address); + return; + } + +no_context: + /* Are we prepared to handle this kernel fault? */ + if (fixup_exception(regs)) + return; + + /* + * Oops. The kernel tried to access some bad page. We'll have to + * terminate things with extreme prejudice. + */ + bust_spinlocks(1); + + pr_alert("Unable to handle kernel %s at virtual address %08lx", + address < PAGE_SIZE ? "NULL pointer dereference" : + "paging request", address); + pr_alert("ea = %08lx, ra = %08lx, cause = %ld\n", regs->ea, regs->ra, + cause); + panic("Oops"); + return; + +/* + * We ran out of memory, or some other thing happened to us that made + * us unable to handle the page fault gracefully. + */ +out_of_memory: + up_read(&mm->mmap_sem); + if (is_global_init(tsk)) { + yield(); + down_read(&mm->mmap_sem); + goto survive; + } + if (!user_mode(regs)) + goto no_context; + pagefault_out_of_memory(); + return; + +do_sigbus: + up_read(&mm->mmap_sem); + + /* Kernel mode? Handle exceptions or die */ + if (!user_mode(regs)) + goto no_context; + + _exception(SIGBUS, regs, BUS_ADRERR, address); + return; + +vmalloc_fault: + { + /* + * Synchronize this task's top level page-table + * with the 'reference' page table. + * + * Do _not_ use "tsk" here. We might be inside + * an interrupt in the middle of a task switch.. + */ + int offset = pgd_index(address); + pgd_t *pgd, *pgd_k; + pud_t *pud, *pud_k; + pmd_t *pmd, *pmd_k; + pte_t *pte_k; + +#if 1 + /* FIXME: Is this entirely correct ? */ + pgd = pgd_current + offset; +#else + pgd = ¤t->mm->pgd[offset]; +#endif + pgd_k = init_mm.pgd + offset; + + if (!pgd_present(*pgd_k)) + goto no_context; + set_pgd(pgd, *pgd_k); + + pud = pud_offset(pgd, address); + pud_k = pud_offset(pgd_k, address); + if (!pud_present(*pud_k)) + goto no_context; + pmd = pmd_offset(pud, address); + pmd_k = pmd_offset(pud_k, address); + if (!pmd_present(*pmd_k)) + goto no_context; + set_pmd(pmd, *pmd_k); + + pte_k = pte_offset_kernel(pmd_k, address); + if (!pte_present(*pte_k)) + goto no_context; + + flush_tlb_one(address); + return; + } +} diff --git a/arch/nios2/mm/init.c b/arch/nios2/mm/init.c new file mode 100644 index 0000000000000..f76aec410e2c3 --- /dev/null +++ b/arch/nios2/mm/init.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * Copyright (C) 2004 Microtronix Datacom Ltd + * + * based on arch/m68k/mm/init.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +pgd_t *pgd_current; + +/* + * paging_init() continues the virtual memory environment setup which + * was begun by the code in arch/head.S. + * The parameters are pointers to where to stick the starting and ending + * addresses of available kernel virtual memory. + */ +void __init paging_init(void) +{ + unsigned long zones_size[MAX_NR_ZONES]; + unsigned long start_mem, end_mem; + + memset(zones_size, 0, sizeof(zones_size)); + + /* + * Make sure start_mem is page aligned, otherwise bootmem and + * page_alloc get different views of the world. + */ + start_mem = PHYS_OFFSET; + end_mem = memory_end; + + pagetable_init(); + pgd_current = swapper_pg_dir; + + /* + * Set up SFC/DFC registers (user data space). + */ + zones_size[ZONE_DMA] = ((end_mem - start_mem) >> PAGE_SHIFT); + + /* pass the memory from the bootmem allocator to the main allocator */ + free_area_init(zones_size); + + flush_dcache_range((unsigned long)empty_zero_page, + (unsigned long)empty_zero_page + PAGE_SIZE); +} + +void __init mem_init(void) +{ + unsigned long end_mem = memory_end; /* this must not include + kernel stack at top */ + + pr_debug("mem_init: start=%lx, end=%lx\n", memory_start, memory_end); + + end_mem &= PAGE_MASK; + high_memory = __va(end_mem); + + max_mapnr = ((unsigned long)end_mem) >> PAGE_SHIFT; + + /* this will put all memory onto the freelists */ + free_all_bootmem(); + mem_init_print_info(NULL); +} + +void __init mmu_init(void) +{ + flush_tlb_all(); +} + +#ifdef CONFIG_BLK_DEV_INITRD +void __init free_initrd_mem(unsigned long start, unsigned long end) +{ + free_reserved_area((void*)start, (void*)end, -1, "initrd"); +} +#endif + +void __init_refok free_initmem(void) +{ + free_initmem_default(-1); +} + +#define __page_aligned(order) __aligned(PAGE_SIZE << (order)) +pgd_t swapper_pg_dir[PTRS_PER_PGD] __page_aligned(PGD_ORDER); +pte_t invalid_pte_table[PTRS_PER_PTE] __page_aligned(PTE_ORDER); diff --git a/arch/nios2/mm/ioremap.c b/arch/nios2/mm/ioremap.c new file mode 100644 index 0000000000000..72bec05f6a1d0 --- /dev/null +++ b/arch/nios2/mm/ioremap.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2010 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * Copyright (C) 2004 Microtronix Datacom Ltd. + * + * based on arch/m68knommu/mm/kmap.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static inline void remap_area_pte(pte_t *pte, unsigned long address, + unsigned long size, unsigned long phys_addr, + unsigned long flags) +{ + unsigned long end; + unsigned long pfn; + pgprot_t pgprot = __pgprot(_PAGE_GLOBAL | _PAGE_PRESENT | _PAGE_READ + | _PAGE_WRITE | flags); + + address &= ~PMD_MASK; + end = address + size; + if (end > PMD_SIZE) + end = PMD_SIZE; + if (address >= end) + BUG(); + pfn = PFN_DOWN(phys_addr); + do { + if (!pte_none(*pte)) { + pr_err("remap_area_pte: page already exists\n"); + BUG(); + } + set_pte(pte, pfn_pte(pfn, pgprot)); + address += PAGE_SIZE; + pfn++; + pte++; + } while (address && (address < end)); +} + +static inline int remap_area_pmd(pmd_t *pmd, unsigned long address, + unsigned long size, unsigned long phys_addr, + unsigned long flags) +{ + unsigned long end; + + address &= ~PGDIR_MASK; + end = address + size; + if (end > PGDIR_SIZE) + end = PGDIR_SIZE; + phys_addr -= address; + if (address >= end) + BUG(); + do { + pte_t *pte = pte_alloc_kernel(pmd, address); + if (!pte) + return -ENOMEM; + remap_area_pte(pte, address, end - address, address + phys_addr, + flags); + address = (address + PMD_SIZE) & PMD_MASK; + pmd++; + } while (address && (address < end)); + return 0; +} + +static int remap_area_pages(unsigned long address, unsigned long phys_addr, + unsigned long size, unsigned long flags) +{ + int error; + pgd_t *dir; + unsigned long end = address + size; + + phys_addr -= address; + dir = pgd_offset(&init_mm, address); + flush_cache_all(); + if (address >= end) + BUG(); + do { + pud_t *pud; + pmd_t *pmd; + + error = -ENOMEM; + pud = pud_alloc(&init_mm, dir, address); + if (!pud) + break; + pmd = pmd_alloc(&init_mm, pud, address); + if (!pmd) + break; + if (remap_area_pmd(pmd, address, end - address, + phys_addr + address, flags)) + break; + error = 0; + address = (address + PGDIR_SIZE) & PGDIR_MASK; + dir++; + } while (address && (address < end)); + flush_tlb_all(); + return error; +} + +#define IS_MAPPABLE_UNCACHEABLE(addr) (addr < 0x20000000UL) + +/* + * Map some physical address range into the kernel address space. + */ +void __iomem *__ioremap(unsigned long phys_addr, unsigned long size, + unsigned long cacheflag) +{ + struct vm_struct *area; + unsigned long offset; + unsigned long last_addr; + void *addr; + + /* Don't allow wraparound or zero size */ + last_addr = phys_addr + size - 1; + + if (!size || last_addr < phys_addr) + return NULL; + + /* Don't allow anybody to remap normal RAM that we're using */ + if (phys_addr > PHYS_OFFSET && phys_addr < virt_to_phys(high_memory)) { + char *t_addr, *t_end; + struct page *page; + + t_addr = __va(phys_addr); + t_end = t_addr + (size - 1); + for (page = virt_to_page(t_addr); + page <= virt_to_page(t_end); page++) + if (!PageReserved(page)) + return NULL; + } + + /* + * Map uncached objects in the low part of address space to + * CONFIG_IO_REGION_BASE + */ + if (IS_MAPPABLE_UNCACHEABLE(phys_addr) && + IS_MAPPABLE_UNCACHEABLE(last_addr) && + !(cacheflag & _PAGE_CACHED)) + return (void __iomem *)(CONFIG_IO_REGION_BASE + phys_addr); + + /* Mappings have to be page-aligned */ + offset = phys_addr & ~PAGE_MASK; + phys_addr &= PAGE_MASK; + size = PAGE_ALIGN(last_addr + 1) - phys_addr; + + /* Ok, go for it */ + area = get_vm_area(size, VM_IOREMAP); + if (!area) + return NULL; + addr = area->addr; + if (remap_area_pages((unsigned long) addr, phys_addr, size, + cacheflag)) { + vunmap(addr); + return NULL; + } + return (void __iomem *) (offset + (char *)addr); +} +EXPORT_SYMBOL(__ioremap); + +/* + * __iounmap unmaps nearly everything, so be careful + * it doesn't free currently pointer/page tables anymore but it + * wasn't used anyway and might be added later. + */ +void __iounmap(void __iomem *addr) +{ + struct vm_struct *p; + + if ((unsigned long) addr > CONFIG_IO_REGION_BASE) /* FIXME */ + return; + + p = remove_vm_area((void *) (PAGE_MASK & (unsigned long __force) addr)); + if (!p) + pr_err("iounmap: bad address %p\n", addr); + kfree(p); +} +EXPORT_SYMBOL(__iounmap); diff --git a/arch/nios2/mm/mmu_context.c b/arch/nios2/mm/mmu_context.c new file mode 100644 index 0000000000000..45d6b9c58d677 --- /dev/null +++ b/arch/nios2/mm/mmu_context.c @@ -0,0 +1,116 @@ +/* + * MMU context handling. + * + * Copyright (C) 2011 Tobias Klauser + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include + +#include +#include +#include + +/* The pids position and mask in context */ +#define PID_SHIFT 0 +#define PID_BITS (cpuinfo.tlb_pid_num_bits) +#define PID_MASK ((1UL << PID_BITS) - 1) + +/* The versions position and mask in context */ +#define VERSION_BITS (32 - PID_BITS) +#define VERSION_SHIFT (PID_SHIFT + PID_BITS) +#define VERSION_MASK ((1UL << VERSION_BITS) - 1) + +/* Return the version part of a context */ +#define CTX_VERSION(c) (((c) >> VERSION_SHIFT) & VERSION_MASK) + +/* Return the pid part of a context */ +#define CTX_PID(c) (((c) >> PID_SHIFT) & PID_MASK) + +/* Value of the first context (version 1, pid 0) */ +#define FIRST_CTX ((1UL << VERSION_SHIFT) | (0 << PID_SHIFT)) + +static mm_context_t next_mmu_context; + +/* + * Initialize MMU context management stuff. + */ +void __init mmu_context_init(void) +{ + /* We need to set this here because the value depends on runtime data + * from cpuinfo */ + next_mmu_context = FIRST_CTX; +} + +/* + * Set new context (pid), keep way + */ +static void set_context(mm_context_t context) +{ + set_mmu_pid(CTX_PID(context)); +} + +static mm_context_t get_new_context(void) +{ + /* Return the next pid */ + next_mmu_context += (1UL << PID_SHIFT); + + /* If the pid field wraps around we increase the version and + * flush the tlb */ + if (unlikely(CTX_PID(next_mmu_context) == 0)) { + /* Version is incremented since the pid increment above + * overflows info version */ + flush_cache_all(); + flush_tlb_all(); + } + + /* If the version wraps we start over with the first generation, we do + * not need to flush the tlb here since it's always done above */ + if (unlikely(CTX_VERSION(next_mmu_context) == 0)) + next_mmu_context = FIRST_CTX; + + return next_mmu_context; +} + +void switch_mm(struct mm_struct *prev, struct mm_struct *next, + struct task_struct *tsk) +{ + unsigned long flags; + + local_irq_save(flags); + + /* If the process context we are swapping in has a different context + * generation then we have it should get a new generation/pid */ + if (unlikely(CTX_VERSION(next->context) != + CTX_VERSION(next_mmu_context))) + next->context = get_new_context(); + + /* Save the current pgd so the fast tlb handler can find it */ + pgd_current = next->pgd; + + /* Set the current context */ + set_context(next->context); + + local_irq_restore(flags); +} + +/* + * After we have set current->mm to a new value, this activates + * the context for the new mm so we see the new mappings. + */ +void activate_mm(struct mm_struct *prev, struct mm_struct *next) +{ + next->context = get_new_context(); + set_context(next->context); + pgd_current = next->pgd; +} + +unsigned long get_pid_from_context(mm_context_t *context) +{ + return CTX_PID((*context)); +} diff --git a/arch/nios2/mm/pgtable.c b/arch/nios2/mm/pgtable.c new file mode 100644 index 0000000000000..21e865f1079ab --- /dev/null +++ b/arch/nios2/mm/pgtable.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include + +#include +#include + +/* pteaddr: + * ptbase | vpn* | zero + * 31-22 | 21-2 | 1-0 + * + * *vpn is preserved on double fault + * + * tlbacc: + * IG |*flags| pfn + * 31-25|24-20 | 19-0 + * + * *crwxg + * + * tlbmisc: + * resv |way |rd | we|pid |dbl|bad|perm|d + * 31-24 |23-20 |19 | 20|17-4|3 |2 |1 |0 + * + */ + +/* + * Initialize a new pgd / pmd table with invalid pointers. + */ +static void pgd_init(pgd_t *pgd) +{ + unsigned long *p = (unsigned long *) pgd; + int i; + + for (i = 0; i < USER_PTRS_PER_PGD; i += 8) { + p[i + 0] = (unsigned long) invalid_pte_table; + p[i + 1] = (unsigned long) invalid_pte_table; + p[i + 2] = (unsigned long) invalid_pte_table; + p[i + 3] = (unsigned long) invalid_pte_table; + p[i + 4] = (unsigned long) invalid_pte_table; + p[i + 5] = (unsigned long) invalid_pte_table; + p[i + 6] = (unsigned long) invalid_pte_table; + p[i + 7] = (unsigned long) invalid_pte_table; + } +} + +pgd_t *pgd_alloc(struct mm_struct *mm) +{ + pgd_t *ret, *init; + ret = (pgd_t *) __get_free_pages(GFP_KERNEL, PGD_ORDER); + if (ret) { + init = pgd_offset(&init_mm, 0UL); + pgd_init(ret); + memcpy(ret + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD, + (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t)); + } + + return ret; +} + +void __init pagetable_init(void) +{ + /* Initialize the entire pgd. */ + pgd_init(swapper_pg_dir); + pgd_init(swapper_pg_dir + USER_PTRS_PER_PGD); +} diff --git a/arch/nios2/mm/tlb.c b/arch/nios2/mm/tlb.c new file mode 100644 index 0000000000000..51201512190b7 --- /dev/null +++ b/arch/nios2/mm/tlb.c @@ -0,0 +1,280 @@ +/* + * Nios2 TLB handling + * + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TLB_INDEX_MASK \ + ((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \ + << PAGE_SHIFT) + +/* Used as illegal PHYS_ADDR for TLB mappings + */ +/* FIXME: ((1UL << DATA_ADDR_WIDTH) - 1) + */ +#define MAX_PHYS_ADDR 0 + +static void get_misc_and_pid(unsigned long *misc, unsigned long *pid) +{ + *misc = RDCTL(CTL_TLBMISC); + *misc &= (TLBMISC_PID | TLBMISC_WAY); + *pid = *misc & TLBMISC_PID; +} + +/* + * All entries common to a mm share an asid. To effectively flush these + * entries, we just bump the asid. + */ +void flush_tlb_mm(struct mm_struct *mm) +{ + if (current->mm == mm) + flush_tlb_all(); + else + memset(&mm->context, 0, sizeof(mm_context_t)); +} + +/* + * This one is only used for pages with the global bit set so we don't care + * much about the ASID. + * + * FIXME: This is proken, to prove it mmap a read/write-page, mprotect it to + * read only then write to it, the tlb-entry will not be flushed. + * + */ +void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid) +{ + unsigned int way; + unsigned long org_misc, pid_misc; + + pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); + + /* remember pid/way until we return. */ + get_misc_and_pid(&org_misc, &pid_misc); + + WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); + + for (way = 0; way < cpuinfo.tlb_num_ways; way++) { + unsigned long pteaddr; + unsigned long tlbmisc; + unsigned long pid; + + tlbmisc = pid_misc | TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_TLBMISC, tlbmisc); + pteaddr = RDCTL(CTL_PTEADDR); + tlbmisc = RDCTL(CTL_TLBMISC); + pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; + if (((((pteaddr >> 2) & 0xfffff)) == (addr >> PAGE_SHIFT)) && + pid == mmu_pid) { + unsigned long vaddr = CONFIG_IO_REGION_BASE + + ((PAGE_SIZE * cpuinfo.tlb_num_lines) * way) + + (addr & TLB_INDEX_MASK); + pr_debug("Flush entry by writing %#lx way=%dl pid=%ld\n", + vaddr, way, (pid_misc >> TLBMISC_PID_SHIFT)); + + WRCTL(CTL_PTEADDR, (vaddr >> 12) << 2); + tlbmisc = pid_misc | TLBMISC_WE | + (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_TLBMISC, tlbmisc); + WRCTL(CTL_TLBACC, (MAX_PHYS_ADDR >> PAGE_SHIFT)); + } + } + + WRCTL(CTL_TLBMISC, org_misc); +} + +void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, + unsigned long end) +{ + unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); + + while (start < end) { + flush_tlb_one_pid(start, mmu_pid); + start += PAGE_SIZE; + } +} + +void flush_tlb_kernel_range(unsigned long start, unsigned long end) +{ + while (start < end) { + flush_tlb_one(start); + start += PAGE_SIZE; + } +} + +/* + * This one is only used for pages with the global bit set so we don't care + * much about the ASID. + */ +void flush_tlb_one(unsigned long addr) +{ + unsigned int way; + unsigned long org_misc, pid_misc; + + pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); + + /* remember pid/way until we return. */ + get_misc_and_pid(&org_misc, &pid_misc); + + WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); + + for (way = 0; way < cpuinfo.tlb_num_ways; way++) { + unsigned long pteaddr; + unsigned long tlbmisc; + + tlbmisc = pid_misc | TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_TLBMISC, tlbmisc); + pteaddr = RDCTL(CTL_PTEADDR); + tlbmisc = RDCTL(CTL_TLBMISC); + + if ((((pteaddr >> 2) & 0xfffff)) == (addr >> PAGE_SHIFT)) { + unsigned long vaddr = CONFIG_IO_REGION_BASE + + ((PAGE_SIZE * cpuinfo.tlb_num_lines) * way) + + (addr & TLB_INDEX_MASK); + + pr_debug("Flush entry by writing %#lx way=%dl pid=%ld\n", + vaddr, way, (pid_misc >> TLBMISC_PID_SHIFT)); + + tlbmisc = pid_misc | TLBMISC_WE | + (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_PTEADDR, (vaddr >> 12) << 2); + WRCTL(CTL_TLBMISC, tlbmisc); + WRCTL(CTL_TLBACC, (MAX_PHYS_ADDR >> PAGE_SHIFT)); + } + } + + WRCTL(CTL_TLBMISC, org_misc); +} + +void dump_tlb_line(unsigned long line) +{ + unsigned int way; + unsigned long org_misc; + + pr_debug("dump tlb-entries for line=%#lx (addr %08lx)\n", line, + line << (PAGE_SHIFT + cpuinfo.tlb_num_ways_log2)); + + /* remember pid/way until we return */ + org_misc = (RDCTL(CTL_TLBMISC) & (TLBMISC_PID | TLBMISC_WAY)); + + WRCTL(CTL_PTEADDR, line << 2); + + for (way = 0; way < cpuinfo.tlb_num_ways; way++) { + unsigned long pteaddr; + unsigned long tlbmisc; + unsigned long tlbacc; + + WRCTL(CTL_TLBMISC, TLBMISC_RD | (way << TLBMISC_WAY_SHIFT)); + pteaddr = RDCTL(CTL_PTEADDR); + tlbmisc = RDCTL(CTL_TLBMISC); + tlbacc = RDCTL(CTL_TLBACC); + + if ((tlbacc << PAGE_SHIFT) != (MAX_PHYS_ADDR & PAGE_MASK)) { + pr_debug("-- way:%02x vpn:0x%08lx phys:0x%08lx pid:0x%02lx flags:%c%c%c%c%c\n", + way, + (pteaddr << (PAGE_SHIFT-2)), + (tlbacc << PAGE_SHIFT), + ((tlbmisc >> TLBMISC_PID_SHIFT) & + TLBMISC_PID_MASK), + (tlbacc & _PAGE_READ ? 'r' : '-'), + (tlbacc & _PAGE_WRITE ? 'w' : '-'), + (tlbacc & _PAGE_EXEC ? 'x' : '-'), + (tlbacc & _PAGE_GLOBAL ? 'g' : '-'), + (tlbacc & _PAGE_CACHED ? 'c' : '-')); + } + } + + WRCTL(CTL_TLBMISC, org_misc); +} + +void dump_tlb(void) +{ + unsigned int i; + for (i = 0; i < cpuinfo.tlb_num_lines; i++) + dump_tlb_line(i); +} + +void flush_tlb_pid(unsigned long pid) +{ + unsigned int line; + unsigned int way; + unsigned long org_misc, pid_misc; + + /* remember pid/way until we return */ + get_misc_and_pid(&org_misc, &pid_misc); + + for (line = 0; line < cpuinfo.tlb_num_lines; line++) { + WRCTL(CTL_PTEADDR, line << 2); + + for (way = 0; way < cpuinfo.tlb_num_ways; way++) { + unsigned long pteaddr; + unsigned long tlbmisc; + unsigned long tlbacc; + + tlbmisc = pid_misc | TLBMISC_RD | + (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_TLBMISC, tlbmisc); + pteaddr = RDCTL(CTL_PTEADDR); + tlbmisc = RDCTL(CTL_TLBMISC); + tlbacc = RDCTL(CTL_TLBACC); + + if (((tlbmisc>>TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK) + == pid) { + tlbmisc = pid_misc | TLBMISC_WE | + (way << TLBMISC_WAY_SHIFT); + WRCTL(CTL_TLBMISC, tlbmisc); + WRCTL(CTL_TLBACC, + (MAX_PHYS_ADDR >> PAGE_SHIFT)); + } + } + + WRCTL(CTL_TLBMISC, org_misc); + } +} + +void flush_tlb_all(void) +{ + int i; + unsigned long vaddr = CONFIG_IO_REGION_BASE; + unsigned int way; + unsigned long org_misc, pid_misc, tlbmisc; + + /* remember pid/way until we return */ + get_misc_and_pid(&org_misc, &pid_misc); + pid_misc |= TLBMISC_WE; + + /* Map each TLB entry to physcal address 0 with no-access and a + bad ptbase */ + for (way = 0; way < cpuinfo.tlb_num_ways; way++) { + tlbmisc = pid_misc | (way << TLBMISC_WAY_SHIFT); + for (i = 0; i < cpuinfo.tlb_num_lines; i++) { + WRCTL(CTL_PTEADDR, ((vaddr) >> PAGE_SHIFT) << 2); + WRCTL(CTL_TLBMISC, tlbmisc); + WRCTL(CTL_TLBACC, (MAX_PHYS_ADDR >> PAGE_SHIFT)); + vaddr += 1UL << 12; + } + } + + /* restore pid/way */ + WRCTL(CTL_TLBMISC, org_misc); +} + +void set_mmu_pid(unsigned long pid) +{ + WRCTL(CTL_TLBMISC, (RDCTL(CTL_TLBMISC) & TLBMISC_WAY) | + ((pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT)); +} diff --git a/arch/nios2/mm/uaccess.c b/arch/nios2/mm/uaccess.c new file mode 100644 index 0000000000000..4a38ad37c18b4 --- /dev/null +++ b/arch/nios2/mm/uaccess.c @@ -0,0 +1,162 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009, Wind River Systems Inc + * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com + */ + +#include +#include + +asm(".global __copy_from_user\n" + " .type __copy_from_user, @function\n" + "__copy_from_user:\n" + " movi r2,7\n" + " mov r3,r4\n" + " bge r2,r6,1f\n" + " xor r2,r4,r5\n" + " andi r2,r2,3\n" + " movi r7,3\n" + " beq r2,zero,4f\n" + "1: addi r6,r6,-1\n" + " movi r2,-1\n" + " beq r6,r2,3f\n" + " mov r7,r2\n" + "2: ldbu r2,0(r5)\n" + " addi r6,r6,-1\n" + " addi r5,r5,1\n" + " stb r2,0(r3)\n" + " addi r3,r3,1\n" + " bne r6,r7,2b\n" + "3:\n" + " addi r2,r6,1\n" + " ret\n" + "13:mov r2,r6\n" + " ret\n" + "4: andi r2,r4,1\n" + " cmpeq r2,r2,zero\n" + " beq r2,zero,7f\n" + "5: andi r2,r3,2\n" + " beq r2,zero,6f\n" + "9: ldhu r2,0(r5)\n" + " addi r6,r6,-2\n" + " addi r5,r5,2\n" + " sth r2,0(r3)\n" + " addi r3,r3,2\n" + "6: bge r7,r6,1b\n" + "10:ldw r2,0(r5)\n" + " addi r6,r6,-4\n" + " addi r5,r5,4\n" + " stw r2,0(r3)\n" + " addi r3,r3,4\n" + " br 6b\n" + "7: ldbu r2,0(r5)\n" + " addi r6,r6,-1\n" + " addi r5,r5,1\n" + " addi r3,r4,1\n" + " stb r2,0(r4)\n" + " br 5b\n" + ".section __ex_table,\"a\"\n" + ".word 2b,3b\n" + ".word 9b,13b\n" + ".word 10b,13b\n" + ".word 7b,13b\n" + ".previous\n" + ); +EXPORT_SYMBOL(__copy_from_user); + +asm( + " .global __copy_to_user\n" + " .type __copy_to_user, @function\n" + "__copy_to_user:\n" + " movi r2,7\n" + " mov r3,r4\n" + " bge r2,r6,1f\n" + " xor r2,r4,r5\n" + " andi r2,r2,3\n" + " movi r7,3\n" + " beq r2,zero,4f\n" + /* Bail if we try to copy zero bytes */ + "1: addi r6,r6,-1\n" + " movi r2,-1\n" + " beq r6,r2,3f\n" + /* Copy byte by byte for small copies and if src^dst != 0 */ + " mov r7,r2\n" + "2: ldbu r2,0(r5)\n" + " addi r5,r5,1\n" + "9: stb r2,0(r3)\n" + " addi r6,r6,-1\n" + " addi r3,r3,1\n" + " bne r6,r7,2b\n" + "3: addi r2,r6,1\n" + " ret\n" + "13:mov r2,r6\n" + " ret\n" + /* If 'to' is an odd address byte copy */ + "4: andi r2,r4,1\n" + " cmpeq r2,r2,zero\n" + " beq r2,zero,7f\n" + /* If 'to' is not divideable by four copy halfwords */ + "5: andi r2,r3,2\n" + " beq r2,zero,6f\n" + " ldhu r2,0(r5)\n" + " addi r5,r5,2\n" + "10:sth r2,0(r3)\n" + " addi r6,r6,-2\n" + " addi r3,r3,2\n" + /* Copy words */ + "6: bge r7,r6,1b\n" + " ldw r2,0(r5)\n" + " addi r5,r5,4\n" + "11:stw r2,0(r3)\n" + " addi r6,r6,-4\n" + " addi r3,r3,4\n" + " br 6b\n" + /* Copy remaining bytes */ + "7: ldbu r2,0(r5)\n" + " addi r5,r5,1\n" + " addi r3,r4,1\n" + "12: stb r2,0(r4)\n" + " addi r6,r6,-1\n" + " br 5b\n" + ".section __ex_table,\"a\"\n" + ".word 9b,3b\n" + ".word 10b,13b\n" + ".word 11b,13b\n" + ".word 12b,13b\n" + ".previous\n"); +EXPORT_SYMBOL(__copy_to_user); + +long strncpy_from_user(char *__to, const char __user *__from, long __len) +{ + int l = strnlen_user(__from, __len); + int is_zt = 1; + + if (l > __len) { + is_zt = 0; + l = __len; + } + + if (l == 0 || copy_from_user(__to, __from, l)) + return -EFAULT; + + if (is_zt) + l--; + return l; +} + +long strnlen_user(const char __user *s, long n) +{ + long i; + + for (i = 0; i < n; i++) { + char c; + if (get_user(c, s + i) == -EFAULT) + return 0; + if (c == 0) + return i + 1; + } + return n + 1; +} diff --git a/arch/nios2/oprofile/Makefile b/arch/nios2/oprofile/Makefile new file mode 100644 index 0000000000000..27b07c19e0000 --- /dev/null +++ b/arch/nios2/oprofile/Makefile @@ -0,0 +1,13 @@ +# +# arch/nios2/oprofile/Makefile +# + +obj-$(CONFIG_OPROFILE) += oprofile.o + +DRIVER_OBJS := $(addprefix ../../../drivers/oprofile/, \ + oprof.o cpu_buffer.o buffer_sync.o \ + event_buffer.o oprofile_files.o \ + oprofilefs.o oprofile_stats.o \ + timer_int.o ) + +oprofile-y := $(DRIVER_OBJS) nios2_oprofile.o diff --git a/arch/nios2/oprofile/nios2_oprofile.c b/arch/nios2/oprofile/nios2_oprofile.c new file mode 100644 index 0000000000000..29e9ab6fbaa52 --- /dev/null +++ b/arch/nios2/oprofile/nios2_oprofile.c @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 Thomas Chou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#include +#include + +int __init oprofile_arch_init(struct oprofile_operations *ops) +{ + return -1; +} + +void oprofile_arch_exit(void) +{ +} diff --git a/arch/nios2/platform/Kconfig.platform b/arch/nios2/platform/Kconfig.platform new file mode 100644 index 0000000000000..e767c51afd9f4 --- /dev/null +++ b/arch/nios2/platform/Kconfig.platform @@ -0,0 +1,129 @@ +menu "Platform options" + +comment "Memory settings" + +config MEM_BASE + hex "Memory base address" + default "0x00000000" + help + This is the physical address of the memory that the kernel will run + from. This address is used to link the kernel and setup initial memory + management. You should take the raw memory address without any MMU + or cache bits set. + Please not that this address is used directly so you have to manually + do address translation if it's connected to a bridge. + +comment "Device tree" + +config NIOS2_DTB_AT_PHYS_ADDR + bool "DTB at physical address" + default n + help + When enabled you can select a physical address to load the dtb from. + Normally this address is passed by a bootloader such as u-boot but + using this you can use a devicetree without a bootloader. + This way you can store a devicetree in NOR flash or an onchip rom. + Please note that this addres is used directly so you have to manually + do address translation if it's connected to a bridge. Also take into + account that when using an MMU you'd have to ad 0xC0000000 to your + address + +config NIOS2_DTB_PHYS_ADDR + hex "DTB Address" + depends on NIOS2_DTB_AT_PHYS_ADDR + default "0xC0000000" + help + Physical address of a dtb blob. + +config DTB_SOURCE_BOOL + bool "Compile and link device tree into kernel image" + default n + help + This allows you to specify a dts (device tree source) file + which will be compiled and linked into the kernel image. + +config DTB_SOURCE + string "Device tree source file" + depends on DTB_SOURCE_BOOL + default "" + help + Absolute path to the device tree source (dts) file describing your + system. + +comment "Nios II instructions" + +config NIOS2_HW_MUL_SUPPORT + bool "Enable MUL instruction" + default n + help + Set to true if you configured the Nios II to include the MUL + instruction. This will enable the -mhw-mul compiler flag. + +config NIOS2_HW_MULX_SUPPORT + bool "Enable MULX instruction" + default n + help + Set to true if you configured the Nios II to include the MULX + instruction. Enables the -mhw-mulx compiler flag. + +config NIOS2_HW_DIV_SUPPORT + bool "Enable DIV instruction" + default n + help + Set to true if you configured the Nios II to include the DIV + instruction. Enables the -mhw-div compiler flag. + +config NIOS2_FPU_SUPPORT + bool "Custom floating point instr support" + default n + help + Enables the -mcustom-fpu-cfg=60-1 compiler flag. + +config NIOS2_CI_SWAB_SUPPORT + bool "Byteswap custom instruction" + default n + help + Use the byteswap (endian convertor) Nios II custom instruction provided + by Altera and which can be enabled in SOPC builder. This accelerates + endian conversions in the kernel (e.g. ntohs). + +config NIOS2_CI_SWAB_NO + int "Byteswap custom instruction number" if NIOS2_CI_SWAB_SUPPORT + default 0 + help + Number of the instruction as configured in SOPC Builder. + +comment "Cache settings" + +config CUSTOM_CACHE_SETTINGS + bool "Custom cache settings" + help + This option allows you to tweak the cache settings used during early + boot (where the information from device tree is not yet available). + There should be no reason to change these values. Linux will work + perfectly fine, even if the Nios II is configured with smaller caches. + + Say N here unless you know what you are doing. + +config NIOS2_DCACHE_SIZE + hex "D-Cache size" if CUSTOM_CACHE_SETTINGS + range 0x200 0x10000 + default "0x800" + help + Maximum possible data cache size. + +config NIOS2_DCACHE_LINE_SIZE + hex "D-Cache line size" if CUSTOM_CACHE_SETTINGS + range 0x10 0x20 + default "0x20" + help + Minimum possible data cache line size. + +config NIOS2_ICACHE_SIZE + hex "I-Cache size" if CUSTOM_CACHE_SETTINGS + range 0x200 0x10000 + default "0x1000" + help + Maximum possible instruction cache size. + +endmenu diff --git a/arch/nios2/platform/Makefile b/arch/nios2/platform/Makefile new file mode 100644 index 0000000000000..46364f1d9352d --- /dev/null +++ b/arch/nios2/platform/Makefile @@ -0,0 +1 @@ +obj-y += platform.o diff --git a/arch/nios2/platform/platform.c b/arch/nios2/platform/platform.c new file mode 100644 index 0000000000000..2e5b8f624f4d5 --- /dev/null +++ b/arch/nios2/platform/platform.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright (C) 2011 Thomas Chou + * Copyright (C) 2011 Walter Goossens + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NIOS2_ID_DEFAULT (0x1) +#define NIOS2_REVISION_DEFAULT (0x1) + +static struct of_device_id altera_of_bus_ids[] __initdata = { + { .compatible = "simple-bus", }, + { .compatible = "altr,avalon", }, + {} +}; + +static void __init nios2_soc_device_init(void) +{ + struct soc_device *soc_dev; + struct soc_device_attribute *soc_dev_attr; + const char *machine; + + machine = of_flat_dt_get_machine_name(); + if (!machine) + return; + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + return; + + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%u", NIOS2_ID_DEFAULT); + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d", + NIOS2_REVISION_DEFAULT); + soc_dev_attr->machine = kasprintf(GFP_KERNEL, "%s", machine); + soc_dev_attr->family = "Nios II"; + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR_OR_NULL(soc_dev)) { + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->machine); + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr); + return; + } + + return; +} + +static int __init nios2_device_probe(void) +{ + nios2_soc_device_init(); + + of_platform_bus_probe(NULL, altera_of_bus_ids, NULL); + return 0; +} + +device_initcall(nios2_device_probe); diff --git a/drivers/Kconfig b/drivers/Kconfig index 0e87a34b6472e..b0cbbae2c7b8c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -34,6 +34,8 @@ source "drivers/message/fusion/Kconfig" source "drivers/firewire/Kconfig" +source "drivers/fpga/Kconfig" + source "drivers/message/i2o/Kconfig" source "drivers/macintosh/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index f98b50d8251d3..afdd2aa6b27c4 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_RESET_CONTROLLER) += reset/ # default. obj-y += tty/ obj-y += char/ +obj-$(CONFIG_FPGA) += fpga/ # gpu/ comes after char for AGP vs DRM startup obj-y += gpu/ diff --git a/drivers/clk/socfpga/clk-periph.c b/drivers/clk/socfpga/clk-periph.c index 46531c34ec9b5..9b64847bfdad9 100644 --- a/drivers/clk/socfpga/clk-periph.c +++ b/drivers/clk/socfpga/clk-periph.c @@ -45,8 +45,17 @@ static unsigned long clk_periclk_recalc_rate(struct clk_hw *hwclk, return parent_rate / div; } +static u8 clk_periclk_get_parent(struct clk_hw *hwclk) +{ + u32 clk_src; + + clk_src = readl(clk_mgr_base_addr + CLKMGR_DBCTRL); + return clk_src & 0x1; +} + static const struct clk_ops periclk_ops = { .recalc_rate = clk_periclk_recalc_rate, + .get_parent = clk_periclk_get_parent, }; static __init void __socfpga_periph_init(struct device_node *node, @@ -56,11 +65,12 @@ static __init void __socfpga_periph_init(struct device_node *node, struct clk *clk; struct socfpga_periph_clk *periph_clk; const char *clk_name = node->name; - const char *parent_name; + const char *parent_name[SOCFPGA_MAX_PARENTS]; struct clk_init_data init; int rc; u32 fixed_div; u32 div_reg[3]; + int i = 0; of_property_read_u32(node, "reg", ®); @@ -90,9 +100,12 @@ static __init void __socfpga_periph_init(struct device_node *node, init.name = clk_name; init.ops = ops; init.flags = 0; - parent_name = of_clk_get_parent_name(node, 0); - init.parent_names = &parent_name; - init.num_parents = 1; + while (i < SOCFPGA_MAX_PARENTS && (parent_name[i] = + of_clk_get_parent_name(node, i)) != NULL) + i++; + + init.parent_names = parent_name; + init.num_parents = i; periph_clk->hw.hw.init = &init; diff --git a/drivers/clk/socfpga/clk.h b/drivers/clk/socfpga/clk.h index d291f60c46e1a..d036de2ae58f1 100644 --- a/drivers/clk/socfpga/clk.h +++ b/drivers/clk/socfpga/clk.h @@ -23,6 +23,7 @@ /* Clock Manager offsets */ #define CLKMGR_CTRL 0x0 #define CLKMGR_BYPASS 0x4 +#define CLKMGR_DBCTRL 0x10 #define CLKMGR_L4SRC 0x70 #define CLKMGR_PERPLL_SRC 0xAC diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 73fa9b7a10ab3..83e1455b5297c 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -1264,10 +1264,11 @@ static inline int _ldst_devtomem(unsigned dry_run, u8 buf[], const struct _xfer_spec *pxs, int cyc) { int off = 0; + enum pl330_cond cond = (pxs->r->cfg->brst_len == 1) ? SINGLE : BURST; while (cyc--) { - off += _emit_WFP(dry_run, &buf[off], SINGLE, pxs->r->peri); - off += _emit_LDP(dry_run, &buf[off], SINGLE, pxs->r->peri); + off += _emit_WFP(dry_run, &buf[off], cond, pxs->r->peri); + off += _emit_LDP(dry_run, &buf[off], cond, pxs->r->peri); off += _emit_ST(dry_run, &buf[off], ALWAYS); off += _emit_FLUSHP(dry_run, &buf[off], pxs->r->peri); } @@ -1279,11 +1280,12 @@ static inline int _ldst_memtodev(unsigned dry_run, u8 buf[], const struct _xfer_spec *pxs, int cyc) { int off = 0; + enum pl330_cond cond = (pxs->r->cfg->brst_len == 1) ? SINGLE : BURST; while (cyc--) { - off += _emit_WFP(dry_run, &buf[off], SINGLE, pxs->r->peri); + off += _emit_WFP(dry_run, &buf[off], cond, pxs->r->peri); off += _emit_LD(dry_run, &buf[off], ALWAYS); - off += _emit_STP(dry_run, &buf[off], SINGLE, pxs->r->peri); + off += _emit_STP(dry_run, &buf[off], cond, pxs->r->peri); off += _emit_FLUSHP(dry_run, &buf[off], pxs->r->peri); } @@ -2850,7 +2852,7 @@ pl330_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, } desc->rqcfg.brst_size = pch->burst_sz; - desc->rqcfg.brst_len = 1; + desc->rqcfg.brst_len = pch->burst_len; } /* Return the last desc in the chain */ @@ -3007,6 +3009,13 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id) pd->device_issue_pending = pl330_issue_pending; pd->device_slave_caps = pl330_dma_device_slave_caps; + if (adev->dev.of_node) { + u32 val; + if (!of_property_read_u32(adev->dev.of_node, + "copy-align", &val)) + pd->copy_align = val; + } + ret = dma_async_device_register(pd); if (ret) { dev_err(&adev->dev, "unable to register DMAC\n"); diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 878f09005fad9..a61e0a6f65b87 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -368,4 +368,38 @@ config EDAC_OCTEON_PCI Support for error detection and correction on the Cavium Octeon family of SOCs. +config EDAC_ALTERA_MC + bool "Altera SDRAM Memory Controller EDAC" + depends on EDAC_MM_EDAC && ARCH_SOCFPGA + help + Support for error detection and correction on the + Altera SDRAM memory controller. Note that the + preloader must initialize the SDRAM before loading + the kernel. + +config EDAC_ALTERA_ECC_MGR + bool "Altera ECC Manager for EDAC" + depends on EDAC_MM_EDAC && ARCH_SOCFPGA + help + Support for error detection and correction on the + Altera memories. Submodules are individually + configurable. + +config EDAC_ALTERA_L2_ECC + bool "Altera L2 Cache EDAC" + depends on EDAC_ALTERA_ECC_MGR && CACHE_L2X0 + help + Support for error detection and correction on the + Altera L2 cache memory. + Ensure the preloader is configured to initialize + the L2 memory & enable the L2 ECC before loading + the kernel. + +config EDAC_ALTERA_OCRAM_ECC + bool "Altera On-Chip RAM EDAC" + depends on EDAC_ALTERA_ECC_MGR + help + Support for error detection and correction on the + Altera On-Chip RAM Memory. + endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index 4154ed6a02c67..c98fe600963cd 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -64,3 +64,9 @@ obj-$(CONFIG_EDAC_OCTEON_PC) += octeon_edac-pc.o obj-$(CONFIG_EDAC_OCTEON_L2C) += octeon_edac-l2c.o obj-$(CONFIG_EDAC_OCTEON_LMC) += octeon_edac-lmc.o obj-$(CONFIG_EDAC_OCTEON_PCI) += octeon_edac-pci.o + +obj-$(CONFIG_EDAC_ALTERA_MC) += altera_mc_edac.o +obj-$(CONFIG_EDAC_ALTERA_ECC_MGR) += altera_ecc_mgr_edac.o +obj-$(CONFIG_EDAC_ALTERA_L2_ECC) += altera_ecc_l2.o +obj-$(CONFIG_EDAC_ALTERA_OCRAM_ECC) += altera_ecc_ocram.o + diff --git a/drivers/edac/altera_ecc_l2.c b/drivers/edac/altera_ecc_l2.c new file mode 100644 index 0000000000000..2020438e77161 --- /dev/null +++ b/drivers/edac/altera_ecc_l2.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "altera_edac.h" +#include "edac_core.h" +#include "edac_module.h" + +/* MPU L2 Register Defines */ +#define ALTR_MPUL2_CONTROL_OFFSET 0x100 +#define ALTR_MPUL2_CTL_CACHE_EN_MASK 0x00000001 + +/* L2 ECC Management Group Defines */ +#define ALTR_MAN_GRP_L2_ECC_OFFSET 0x00 +#define ALTR_L2_ECC_EN_MASK 0x00000001 +#define ALTR_L2_ECC_INJS_MASK 0x00000002 +#define ALTR_L2_ECC_INJD_MASK 0x00000004 + +#ifdef CONFIG_EDAC_DEBUG +static void *l2_init_mem(size_t size, void **other) +{ + struct device *dev = *other; + void *ptemp = devm_kzalloc(dev, size, GFP_KERNEL); + if (!ptemp) + return NULL; + + wmb(); + flush_cache_all(); + + return ptemp; +} + +static void l2_free_mem(void *p, size_t size, void *other) +{ + struct device *dev = other; + if (dev && p) + devm_kfree(dev, p); +} + +static struct edac_dev_sysfs_attribute altr_l2_sysfs_attributes[] = { + { + .attr = { .name = "altr_l2_trigger", + .mode = (S_IRUGO | S_IWUSR) }, + .show = NULL, + .store = altr_ecc_mgr_trig + }, + { + .attr = {.name = NULL } + } +}; +#endif /* #ifdef CONFIG_EDAC_DEBUG */ + +/* + * altr_l2_dependencies() + * Test for L2 cache ECC dependencies upon entry because + * the preloader/UBoot should have initialized the L2 + * memory and enabled the ECC. + * Can't turn on ECC here because accessing un-initialized + * memory will cause CE/UE errors possibly causing an ABORT. + * Bail if ECC is not on. + * Test For 1) L2 ECC is enabled and 2) L2 Cache is enabled. + */ +static int altr_l2_dependencies(struct platform_device *pdev, + void __iomem *base) +{ + u32 control; + struct regmap *l2_vbase; + + control = readl(base) & ALTR_L2_ECC_EN_MASK; + if (!control) { + dev_err(&pdev->dev, "L2: No ECC present, or ECC disabled\n"); + return -ENODEV; + } + + l2_vbase = syscon_regmap_lookup_by_compatible("arm,pl310-cache"); + if (IS_ERR(l2_vbase)) { + dev_err(&pdev->dev, + "L2 ECC:regmap for arm,pl310-cache lookup failed.\n"); + return -ENODEV; + } + + regmap_read(l2_vbase, ALTR_MPUL2_CONTROL_OFFSET, &control); + if (!(control & ALTR_MPUL2_CTL_CACHE_EN_MASK)) { + dev_err(&pdev->dev, "L2: Cache disabled\n"); + return -ENODEV; + } + + return 0; +} + +const struct ecc_mgr_prv_data l2ecc_data = { + .setup = altr_l2_dependencies, + .ce_clear_mask = 0, + .ue_clear_mask = 0, +#ifdef CONFIG_EDAC_DEBUG + .eccmgr_sysfs_attr = altr_l2_sysfs_attributes, + .init_mem = l2_init_mem, + .free_mem = l2_free_mem, + .ecc_enable_mask = ALTR_L2_ECC_EN_MASK, + .ce_set_mask = (ALTR_L2_ECC_EN_MASK | ALTR_L2_ECC_INJS_MASK), + .ue_set_mask = (ALTR_L2_ECC_EN_MASK | ALTR_L2_ECC_INJD_MASK), + .trig_alloc_sz = 5000, +#endif +}; + diff --git a/drivers/edac/altera_ecc_mgr_edac.c b/drivers/edac/altera_ecc_mgr_edac.c new file mode 100644 index 0000000000000..5551691157058 --- /dev/null +++ b/drivers/edac/altera_ecc_mgr_edac.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright 2011-2012 Calxeda, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * Adapted from the highbank_l2_edac driver + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "altera_edac.h" +#include "edac_core.h" +#include "edac_module.h" + +static irqreturn_t altr_ecc_mgr_handler(int irq, void *dev_id) +{ + struct edac_device_ctl_info *dci = dev_id; + struct altr_ecc_mgr_dev *drvdata = dci->pvt_info; + const struct ecc_mgr_prv_data *priv = drvdata->data; + + if (irq == drvdata->sb_irq) { + if (priv->ce_clear_mask) + writel(priv->ce_clear_mask, drvdata->base); + edac_device_handle_ce(dci, 0, 0, drvdata->edac_dev_name); + } + if (irq == drvdata->db_irq) { + if (priv->ue_clear_mask) + writel(priv->ue_clear_mask, drvdata->base); + edac_device_handle_ue(dci, 0, 0, drvdata->edac_dev_name); + panic("\nEDAC:ECC_MGR[Uncorrectable errors]\n"); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_EDAC_DEBUG +ssize_t altr_ecc_mgr_trig(struct edac_device_ctl_info *edac_dci, + const char *buffer, size_t count) +{ + u32 *ptemp, i, error_mask; + int result = 0; + unsigned long flags; + struct altr_ecc_mgr_dev *drvdata = edac_dci->pvt_info; + const struct ecc_mgr_prv_data *priv = drvdata->data; + void *generic_ptr = edac_dci->dev; + + if (!priv->init_mem) + return -ENOMEM; + + /* Note that generic_ptr is initialized to the device * but in + * some init_functions, this is overridden and returns data */ + ptemp = priv->init_mem(priv->trig_alloc_sz, &generic_ptr); + if (!ptemp) { + dev_err(edac_dci->dev, + "**EDAC Error Inject: Buffer Allocation error\n"); + return -ENOMEM; + } + + if (count == 3) + error_mask = priv->ue_set_mask; + else + error_mask = priv->ce_set_mask; + + dev_alert(edac_dci->dev, "%s: Trigger Error Mask (0x%X)\n", + __func__, error_mask); + + local_irq_save(flags); + /* write data out which should be corrupted. */ + for (i = 0; i < 16; i++) { + /* Read data out so we're in the correct state */ + if (ptemp[i]) + result = -1; + rmb(); + /* Toggle Error bit (it is latched), leave ECC enabled */ + writel(error_mask, drvdata->base); + writel(priv->ecc_enable_mask, drvdata->base); + ptemp[i] = i; + } + wmb(); + local_irq_restore(flags); + + if (result) + dev_alert(edac_dci->dev, "%s: Mem Not Cleared (%d)\n", + __func__, result); + + /* Read out written data. ECC error caused here */ + for (i = 0; i < 16; i++) + if (ptemp[i] != i) + result = -1; + rmb(); + + if (priv->free_mem) + priv->free_mem(ptemp, priv->trig_alloc_sz, generic_ptr); + + if (result) + dev_alert(edac_dci->dev, "%s: Trigger Match Error (%d)\n", + __func__, result); + + return count; +} + +static void altr_set_sysfs_attr(struct edac_device_ctl_info *edac_dci, + const struct ecc_mgr_prv_data *priv) +{ + struct edac_dev_sysfs_attribute *ecc_attr = priv->eccmgr_sysfs_attr; + if (ecc_attr) + edac_dci->sysfs_attributes = ecc_attr; +} +#else +static void altr_set_sysfs_attr(struct edac_device_ctl_info *edac_dci, + const struct ecc_mgr_prv_data *priv) +{} +#endif /* #ifdef CONFIG_EDAC_DEBUG */ + +static const struct of_device_id altr_ecc_mgr_of_match[] = { +#ifdef CONFIG_EDAC_ALTERA_L2_ECC + { .compatible = "altr,l2-edac", .data = (void *)&l2ecc_data }, +#endif +#ifdef CONFIG_EDAC_ALTERA_OCRAM_ECC + { .compatible = "altr,ocram-edac", .data = (void *)&ocramecc_data }, +#endif + {}, +}; + +/* + * altr_ecc_mgr_probe() + * This is a generic EDAC device driver that will support + * various Altera memory devices such as the L2 cache ECC and + * OCRAM ECC as well as the memories for other peripherals. + * Module specific initialization is done by passing the + * function index in the device tree. + */ +static int altr_ecc_mgr_probe(struct platform_device *pdev) +{ + struct edac_device_ctl_info *dci; + struct altr_ecc_mgr_dev *drvdata; + const struct ecc_mgr_prv_data *priv; + struct resource *r; + int res = 0; + struct device_node *np = pdev->dev.of_node; + char *ecc_name = (char *)np->name; + static int dev_instance; + + dci = edac_device_alloc_ctl_info(sizeof(*drvdata), ecc_name, + 1, ecc_name, 1, 0, NULL, 0, dev_instance++); + + if (!dci) + return -ENOMEM; + + drvdata = dci->pvt_info; + dci->dev = &pdev->dev; + platform_set_drvdata(pdev, dci); + drvdata->edac_dev_name = ecc_name; + + if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "%s:Unable to get mem resource\n", + ecc_name); + res = -ENODEV; + goto err; + } + + if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), + dev_name(&pdev->dev))) { + dev_err(&pdev->dev, "%s:Error requesting mem region\n", + ecc_name); + res = -EBUSY; + goto err; + } + + drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!drvdata->base) { + dev_err(&pdev->dev, "%s:Unable to map regs\n", ecc_name); + res = -ENOMEM; + goto err; + } + + /* Check specific dependencies for the module */ + priv = drvdata->data = of_match_node(altr_ecc_mgr_of_match, np)->data; + if (priv->setup) { + res = priv->setup(pdev, drvdata->base); + if (res < 0) + goto err; + } + + drvdata->sb_irq = platform_get_irq(pdev, 0); + res = devm_request_irq(&pdev->dev, drvdata->sb_irq, + altr_ecc_mgr_handler, + 0, dev_name(&pdev->dev), dci); + if (res < 0) + goto err; + + drvdata->db_irq = platform_get_irq(pdev, 1); + res = devm_request_irq(&pdev->dev, drvdata->db_irq, + altr_ecc_mgr_handler, + 0, dev_name(&pdev->dev), dci); + if (res < 0) + goto err; + + dci->mod_name = "ECC_MGR"; + dci->dev_name = drvdata->edac_dev_name; + + altr_set_sysfs_attr(dci, priv); + + if (edac_device_add_device(dci)) + goto err; + + devres_close_group(&pdev->dev, NULL); + + return 0; +err: + devres_release_group(&pdev->dev, NULL); + edac_device_free_ctl_info(dci); + + return res; +} + +static int altr_ecc_mgr_remove(struct platform_device *pdev) +{ + struct edac_device_ctl_info *dci = platform_get_drvdata(pdev); + + edac_device_del_device(&pdev->dev); + edac_device_free_ctl_info(dci); + + return 0; +} + +static struct platform_driver altr_ecc_mgr_driver = { + .probe = altr_ecc_mgr_probe, + .remove = altr_ecc_mgr_remove, + .driver = { + .name = "altr_ecc_mgr", + .of_match_table = of_match_ptr(altr_ecc_mgr_of_match), + }, +}; + +MODULE_DEVICE_TABLE(of, altr_ecc_mgr_of_match); + +module_platform_driver(altr_ecc_mgr_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Altera Corporation"); +MODULE_DESCRIPTION("EDAC Driver for Altera SoC ECC Manager"); diff --git a/drivers/edac/altera_ecc_ocram.c b/drivers/edac/altera_ecc_ocram.c new file mode 100644 index 0000000000000..26e31e0bfc86a --- /dev/null +++ b/drivers/edac/altera_ecc_ocram.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include "altera_edac.h" + +/* OCRAM ECC Management Group Defines */ +#define ALTR_MAN_GRP_OCRAM_ECC_OFFSET 0x04 +#define ALTR_OCR_ECC_EN_MASK 0x00000001 +#define ALTR_OCR_ECC_INJS_MASK 0x00000002 +#define ALTR_OCR_ECC_INJD_MASK 0x00000004 +#define ALTR_OCR_ECC_SERR_MASK 0x00000008 +#define ALTR_OCR_ECC_DERR_MASK 0x00000010 + +#ifdef CONFIG_EDAC_DEBUG +static void *ocram_init_mem(size_t size, void **other) +{ + struct device_node *np; + struct gen_pool *gp; + void *sram_addr; + + np = of_find_compatible_node(NULL, NULL, "altr,ocram-edac"); + if (!np) + return NULL; + + gp = of_get_named_gen_pool(np, "iram", 0); + if (!gp) + return NULL; + *other = gp; + + sram_addr = (void *)gen_pool_alloc(gp, size); + if (!sram_addr) + return NULL; + + memset(sram_addr, 0, size); + wmb(); /* Ensure data is written out */ + + return sram_addr; +} + +static void ocram_free_mem(void *p, size_t size, void *other) +{ + gen_pool_free((struct gen_pool *)other, (u32)p, size); +} + +static struct edac_dev_sysfs_attribute altr_ocr_sysfs_attributes[] = { + { + .attr = { .name = "altr_ocram_trigger", + .mode = (S_IRUGO | S_IWUSR) }, + .show = NULL, + .store = altr_ecc_mgr_trig + }, + { + .attr = {.name = NULL } + } +}; +#endif /* #ifdef CONFIG_EDAC_DEBUG */ + +const struct ecc_mgr_prv_data ocramecc_data = { + .ce_clear_mask = (ALTR_OCR_ECC_EN_MASK | ALTR_OCR_ECC_SERR_MASK), + .ue_clear_mask = (ALTR_OCR_ECC_EN_MASK | ALTR_OCR_ECC_DERR_MASK), +#ifdef CONFIG_EDAC_DEBUG + .eccmgr_sysfs_attr = altr_ocr_sysfs_attributes, + .init_mem = ocram_init_mem, + .free_mem = ocram_free_mem, + .ecc_enable_mask = ALTR_OCR_ECC_EN_MASK, + .ce_set_mask = (ALTR_OCR_ECC_EN_MASK | ALTR_OCR_ECC_INJS_MASK), + .ue_set_mask = (ALTR_OCR_ECC_EN_MASK | ALTR_OCR_ECC_INJD_MASK), + .trig_alloc_sz = (32 * sizeof(u32)), +#endif +}; diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h new file mode 100644 index 0000000000000..48d726fbcb07a --- /dev/null +++ b/drivers/edac/altera_edac.h @@ -0,0 +1,55 @@ +/* + * + * Copyright (C) 2014 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef ALTERA_EDAC_H +#define ALTERA_EDAC_H + +#include +#include +#include +#include "edac_core.h" + +struct ecc_mgr_prv_data { + int (*setup)(struct platform_device *pdev, void __iomem *base); + int ce_clear_mask; + int ue_clear_mask; +#ifdef CONFIG_EDAC_DEBUG + struct edac_dev_sysfs_attribute *eccmgr_sysfs_attr; + void * (*init_mem)(size_t size, void **other); + void (*free_mem)(void *p, size_t size, void *other); + int ecc_enable_mask; + int ce_set_mask; + int ue_set_mask; + int trig_alloc_sz; +#endif +}; + +struct altr_ecc_mgr_dev { + void __iomem *base; + int sb_irq; + int db_irq; + const struct ecc_mgr_prv_data *data; + char *edac_dev_name; +}; + +extern const struct ecc_mgr_prv_data l2ecc_data; +extern const struct ecc_mgr_prv_data ocramecc_data; + +ssize_t altr_ecc_mgr_trig(struct edac_device_ctl_info *edac_dci, + const char *buffer, size_t count); + +#endif /* #ifndef ALTERA_EDAC_H */ diff --git a/drivers/edac/altera_mc_edac.c b/drivers/edac/altera_mc_edac.c new file mode 100644 index 0000000000000..db4b950263e00 --- /dev/null +++ b/drivers/edac/altera_mc_edac.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Copyright 2011-2012 Calxeda, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Adapted from the highbank_mc_edac driver + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edac_core.h" +#include "edac_module.h" + +#define ALTR_EDAC_MOD_STR "altera_edac" + +/* SDRAM Controller CtrlCfg Register */ +#define ALTR_SDR_CTLCFG 0x00 + +/* SDRAM Controller CtrlCfg Register Bit Masks */ +#define ALTR_SDR_CTLCFG_ECC_EN 0x400 +#define ALTR_SDR_CTLCFG_ECC_CORR_EN 0x800 +#define ALTR_SDR_CTLCFG_GEN_SB_ERR 0x2000 +#define ALTR_SDR_CTLCFG_GEN_DB_ERR 0x4000 + +#define ALTR_SDR_CTLCFG_ECC_AUTO_EN (ALTR_SDR_CTLCFG_ECC_EN | \ + ALTR_SDR_CTLCFG_ECC_CORR_EN) + +/* SDRAM Controller DRAM Status Register */ +#define ALTR_SDR_DRAMSTS 0x38 + +/* SDRAM Controller DRAM Status Register Bit Masks */ +#define ALTR_SDR_DRAMSTS_SBEERR 0x04 +#define ALTR_SDR_DRAMSTS_DBEERR 0x08 +#define ALTR_SDR_DRAMSTS_CORR_DROP 0x10 + +/* SDRAM Controller DRAM IRQ Register */ +#define ALTR_SDR_DRAMINTR 0x3C + +/* SDRAM Controller DRAM IRQ Register Bit Masks */ +#define ALTR_SDR_DRAMINTR_INTREN 0x01 +#define ALTR_SDR_DRAMINTR_SBEMASK 0x02 +#define ALTR_SDR_DRAMINTR_DBEMASK 0x04 +#define ALTR_SDR_DRAMINTR_CORRDROPMASK 0x08 +#define ALTR_SDR_DRAMINTR_INTRCLR 0x10 + +/* SDRAM Controller Single Bit Error Count Register */ +#define ALTR_SDR_SBECOUNT 0x40 + +/* SDRAM Controller Single Bit Error Count Register Bit Masks */ +#define ALTR_SDR_SBECOUNT_COUNT 0x0F + +/* SDRAM Controller Double Bit Error Count Register */ +#define ALTR_SDR_DBECOUNT 0x44 + +/* SDRAM Controller Double Bit Error Count Register Bit Masks */ +#define ALTR_SDR_DBECOUNT_COUNT 0x0F + +/* SDRAM Controller ECC Error Address Register */ +#define ALTR_SDR_ERRADDR 0x48 + +/* SDRAM Controller ECC Error Address Register Bit Masks */ +#define ALTR_SDR_ERRADDR_ADDR 0xFFFFFFFF + +/* SDRAM Controller ECC Autocorrect Drop Count Register */ +#define ALTR_SDR_DROPCOUNT 0x4C + +/* SDRAM Controller ECC Autocorrect Drop Count Register Bit Masks */ +#define ALTR_SDR_DROPCOUNT_CORR 0x0F + +/* SDRAM Controller ECC AutoCorrect Address Register */ +#define ALTR_SDR_DROPADDR 0x50 + +/* SDRAM Controller ECC AutoCorrect Error Address Register Bit Masks */ +#define ALTR_SDR_DROPADDR_ADDR 0xFFFFFFFF + +/* Altera SDRAM Memory Controller data */ +struct altr_sdram_mc_data { + struct regmap *mc_vbase; +}; + +static irqreturn_t altr_sdram_mc_err_handler(int irq, void *dev_id) +{ + struct mem_ctl_info *mci = dev_id; + struct altr_sdram_mc_data *drvdata = mci->pvt_info; + u32 status = 0, err_count = 0, err_addr = 0; + + /* Error Address is the same for both SBE & DBE */ + regmap_read(drvdata->mc_vbase, ALTR_SDR_ERRADDR, &err_addr); + + regmap_read(drvdata->mc_vbase, ALTR_SDR_DRAMSTS, &status); + + if (status & ALTR_SDR_DRAMSTS_DBEERR) { + regmap_read(drvdata->mc_vbase, ALTR_SDR_DBECOUNT, &err_count); + panic("\nEDAC: [%d Uncorrectable errors @ 0x%08X]\n", + err_count, err_addr); + } + if (status & ALTR_SDR_DRAMSTS_SBEERR) { + regmap_read(drvdata->mc_vbase, ALTR_SDR_SBECOUNT, &err_count); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, err_count, + err_addr >> PAGE_SHIFT, + err_addr & ~PAGE_MASK, 0, + 0, 0, -1, mci->ctl_name, ""); + } + + regmap_write(drvdata->mc_vbase, ALTR_SDR_DRAMINTR, + (ALTR_SDR_DRAMINTR_INTRCLR | ALTR_SDR_DRAMINTR_INTREN)); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_EDAC_DEBUG +static ssize_t altr_sdr_mc_err_inject_write(struct file *file, + const char __user *data, + size_t count, loff_t *ppos) +{ + struct mem_ctl_info *mci = file->private_data; + struct altr_sdram_mc_data *drvdata = mci->pvt_info; + u32 *ptemp; + dma_addr_t dma_handle; + u32 reg, read_reg = 0; + + ptemp = dma_alloc_coherent(mci->pdev, 16, &dma_handle, GFP_KERNEL); + if (IS_ERR(ptemp)) { + dma_free_coherent(mci->pdev, 16, ptemp, dma_handle); + dev_err(mci->pdev, "**EDAC Inject: Buffer Allocation error\n"); + return -ENOMEM; + } + + regmap_read(drvdata->mc_vbase, ALTR_SDR_CTLCFG, &read_reg); + read_reg &= ~(ALTR_SDR_CTLCFG_GEN_SB_ERR | ALTR_SDR_CTLCFG_GEN_DB_ERR); + + if (count == 3) { + dev_alert(mci->pdev, "** EDAC Inject Double bit error\n"); + regmap_write(drvdata->mc_vbase, ALTR_SDR_CTLCFG, + (read_reg | ALTR_SDR_CTLCFG_GEN_DB_ERR)); + } else { + dev_alert(mci->pdev, "** EDAC Inject Single bit error\n"); + regmap_write(drvdata->mc_vbase, ALTR_SDR_CTLCFG, + (read_reg | ALTR_SDR_CTLCFG_GEN_SB_ERR)); + } + + ptemp[0] = 0x5A5A5A5A; + ptemp[1] = 0xA5A5A5A5; + regmap_write(drvdata->mc_vbase, ALTR_SDR_CTLCFG, read_reg); + wmb(); + + reg = ptemp[0]; + read_reg = ptemp[1]; + + dma_free_coherent(mci->pdev, 16, ptemp, dma_handle); + + return count; +} + +static const struct file_operations altr_sdr_mc_debug_inject_fops = { + .open = simple_open, + .write = altr_sdr_mc_err_inject_write, + .llseek = generic_file_llseek, +}; + +static void altr_sdr_mc_create_debugfs_nodes(struct mem_ctl_info *mci) +{ + if (mci->debugfs) + debugfs_create_file("inject_ctrl", S_IWUSR, mci->debugfs, mci, + &altr_sdr_mc_debug_inject_fops); +} +#else +static void altr_sdr_mc_create_debugfs_nodes(struct mem_ctl_info *mci) +{} +#endif + +/* Get total memory size from Open Firmware DTB */ +static u32 altr_sdram_get_total_mem_size(void) +{ + struct device_node *np; + u32 retcode, reg_array[2]; + + np = of_find_node_by_type(NULL, "memory"); + if (!np) + return 0; + + retcode = of_property_read_u32_array(np, "reg", + reg_array, ARRAY_SIZE(reg_array)); + + of_node_put(np); + + if (retcode) + return 0; + + return reg_array[1]; +} + +static int altr_sdram_mc_probe(struct platform_device *pdev) +{ + struct edac_mc_layer layers[2]; + struct mem_ctl_info *mci; + struct altr_sdram_mc_data *drvdata; + struct dimm_info *dimm; + u32 read_reg = 0, mem_size; + int irq; + int res = 0, retcode; + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = 1; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = 1; + layers[1].is_virt_csrow = false; + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + sizeof(struct altr_sdram_mc_data)); + if (!mci) + return -ENOMEM; + + mci->pdev = &pdev->dev; + drvdata = mci->pvt_info; + platform_set_drvdata(pdev, mci); + + if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) { + edac_mc_free(mci); + return -ENOMEM; + } + + /* Grab the register values from the sdr-ctl in device tree */ + drvdata->mc_vbase = syscon_regmap_lookup_by_compatible("altr,sdr-ctl"); + if (IS_ERR(drvdata->mc_vbase)) { + dev_err(&pdev->dev, + "regmap for altr,sdr-ctl lookup failed.\n"); + res = -ENODEV; + goto err; + } + + retcode = regmap_read(drvdata->mc_vbase, ALTR_SDR_CTLCFG, &read_reg); + if (retcode || ((read_reg & ALTR_SDR_CTLCFG_ECC_AUTO_EN) != + ALTR_SDR_CTLCFG_ECC_AUTO_EN)) { + dev_err(&pdev->dev, "No ECC present / ECC disabled - 0x%08X\n", + read_reg); + res = -ENODEV; + goto err; + } + + mci->mtype_cap = MEM_FLAG_DDR3; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->edac_cap = EDAC_FLAG_SECDED; + mci->mod_name = ALTR_EDAC_MOD_STR; + mci->mod_ver = "1"; + mci->ctl_name = dev_name(&pdev->dev); + mci->scrub_mode = SCRUB_SW_SRC; + mci->dev_name = dev_name(&pdev->dev); + + /* Grab memory size from device tree. */ + mem_size = altr_sdram_get_total_mem_size(); + dev_dbg(&pdev->dev, "EDAC Memory Size = 0x%08x\n", mem_size); + dimm = *mci->dimms; + if (mem_size <= 0) { + dev_err(&pdev->dev, "Unable to find memory size (dts)\n"); + res = -ENODEV; + goto err; + } + dimm->nr_pages = ((mem_size - 1) >> PAGE_SHIFT) + 1; + dimm->grain = 8; + dimm->dtype = DEV_X8; + dimm->mtype = MEM_DDR3; + dimm->edac_mode = EDAC_SECDED; + + res = edac_mc_add_mc(mci); + if (res < 0) + goto err; + + retcode = regmap_write(drvdata->mc_vbase, ALTR_SDR_DRAMINTR, + ALTR_SDR_DRAMINTR_INTRCLR); + if (retcode) { + dev_err(&pdev->dev, "Error clearning SDRAM ECC IRQ\n"); + res = -ENODEV; + goto err; + } + + irq = platform_get_irq(pdev, 0); + res = devm_request_irq(&pdev->dev, irq, altr_sdram_mc_err_handler, + 0, dev_name(&pdev->dev), mci); + if (res < 0) { + dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + res = -ENODEV; + goto err; + } + + retcode = regmap_write(drvdata->mc_vbase, ALTR_SDR_DRAMINTR, + (ALTR_SDR_DRAMINTR_INTRCLR | ALTR_SDR_DRAMINTR_INTREN)); + if (retcode) { + dev_err(&pdev->dev, "Error enabling SDRAM ECC IRQ\n"); + res = -ENODEV; + goto err; + } + + altr_sdr_mc_create_debugfs_nodes(mci); + + devres_close_group(&pdev->dev, NULL); + + return 0; + +err: + dev_err(&pdev->dev, "EDAC Probe Failed; Error %d\n", res); + devres_release_group(&pdev->dev, NULL); + edac_mc_free(mci); + + return res; +} + +static int altr_sdram_mc_remove(struct platform_device *pdev) +{ + struct mem_ctl_info *mci = platform_get_drvdata(pdev); + + edac_mc_del_mc(&pdev->dev); + edac_mc_free(mci); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id altr_sdram_ctrl_of_match[] = { + { .compatible = "altr,sdram-edac", }, + {}, +}; +MODULE_DEVICE_TABLE(of, altr_sdram_ctrl_of_match); + +static struct platform_driver altr_sdram_mc_edac_driver = { + .probe = altr_sdram_mc_probe, + .remove = altr_sdram_mc_remove, + .driver = { + .name = "altr_sdram_mc_edac", + .of_match_table = of_match_ptr(altr_sdram_ctrl_of_match), + }, +}; + +module_platform_driver(altr_sdram_mc_edac_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Altera Corporation"); +MODULE_DESCRIPTION("EDAC Driver for Altera SDRAM Controller"); diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig new file mode 100644 index 0000000000000..3af1dc5064812 --- /dev/null +++ b/drivers/fpga/Kconfig @@ -0,0 +1,20 @@ +# +# FPGA framework configuration +# + +menu "FPGA devices" + +config FPGA + tristate "FPGA Framework" + help + Say Y here if you want support for configuring FPGAs from the + kernel. The FPGA framework adds a FPGA manager class and FPGA + manager drivers. + +config FPGA_MGR_ALTERA + tristate "Altera" + depends on FPGA + help + FPGA manager driver support for configuring Altera FPGAs. +endmenu + diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile new file mode 100644 index 0000000000000..fd3cfb49469f8 --- /dev/null +++ b/drivers/fpga/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the fpga framework and fpga manager drivers. +# + +fpga-mgr-core-y += fpga-mgr.o fpga-mgr-fops.o fpga-transport.o fpga-transport-mmio.o + +obj-$(CONFIG_FPGA) += fpga-mgr-core.o +obj-y += fpga-mgrs/ diff --git a/drivers/fpga/fpga-mgr-fops.c b/drivers/fpga/fpga-mgr-fops.c new file mode 100644 index 0000000000000..b0096cd8ee3f4 --- /dev/null +++ b/drivers/fpga/fpga-mgr-fops.c @@ -0,0 +1,124 @@ +/* + * FPGA Framework file operations + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include "fpga-mgr.h" + +static ssize_t fpga_mgr_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + char *tmp; + int ret = -EFAULT; + struct fpga_manager *mgr = file->private_data; + + tmp = vmalloc(count); + if (tmp == NULL) + return -ENOMEM; + + ret = mgr->mops->read(mgr, tmp, count); + if ((ret > 0) && copy_to_user(buf, tmp, ret)) + ret = -EFAULT; + + vfree(tmp); + return ret; +} + +static ssize_t fpga_mgr_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct fpga_manager *mgr = file->private_data; + char *kern_buf; + int ret; + + kern_buf = memdup_user(buf, count); + if (IS_ERR(kern_buf)) + return PTR_ERR(kern_buf); + + ret = mgr->mops->write(mgr, kern_buf, count); + kfree(kern_buf); + return ret; +} + +static int fpga_mgr_open(struct inode *inode, struct file *file) +{ + struct fpga_manager *mgr; + struct fpga_manager_ops *mops; + bool fmode_wr = (file->f_mode & FMODE_WRITE) != 0; + bool fmode_rd = (file->f_mode & FMODE_READ) != 0; + int ret = 0; + + mgr = container_of(inode->i_cdev, struct fpga_manager, cdev); + if (!mgr) + return -ENODEV; + + mops = mgr->mops; + + /* Don't allow read or write if we don't have read/write fns. */ + if ((fmode_wr && !mops->write) || (fmode_rd && !mops->read)) + return -EPERM; + + /* Need to know if we are going to read or write. Can't be both. */ + if (fmode_wr && fmode_rd) + return -EPERM; + + if (test_and_set_bit_lock(FPGA_MGR_DEV_BUSY, &mgr->flags)) + return -EBUSY; + + file->private_data = mgr; + + if (fmode_wr && mops->write_init) + ret = mops->write_init(mgr); + else if (fmode_rd && mops->read_init) + ret = mops->read_init(mgr); + + return ret; +} + +static int fpga_mgr_release(struct inode *inode, struct file *file) +{ + struct fpga_manager *mgr = file->private_data; + struct fpga_manager_ops *mops = mgr->mops; + bool fmode_wr = (file->f_mode & FMODE_WRITE) != 0; + bool fmode_rd = (file->f_mode & FMODE_READ) != 0; + int ret = 0; + + if (fmode_wr && mops->write_complete) + ret = mops->write_complete(mgr); + else if (fmode_rd && mops->read_complete) + ret = mops->read_complete(mgr); + + file->private_data = NULL; + clear_bit_unlock(FPGA_MGR_DEV_BUSY, &mgr->flags); + + return ret; +} + +const struct file_operations fpga_mgr_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = fpga_mgr_read, + .write = fpga_mgr_write, + .open = fpga_mgr_open, + .release = fpga_mgr_release, +}; diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c new file mode 100644 index 0000000000000..0a72e72f9c58a --- /dev/null +++ b/drivers/fpga/fpga-mgr.c @@ -0,0 +1,258 @@ +/* + * FPGA Framework + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "fpga-mgr.h" + +static DEFINE_IDA(fpga_mgr_ida); +static int fpga_mgr_major; +static struct class *fpga_mgr_class; + +#define FPGA_MAX_MINORS 256 + +/* + * class attributes + */ +static ssize_t fpga_mgr_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + + if (!mgr) + return -ENODEV; + + return snprintf(buf, sizeof(mgr->name), "%s\n", mgr->name); +} + +static ssize_t fpga_mgr_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + + if (!mgr || !mgr->mops || !mgr->mops->status) + return -ENODEV; + + return mgr->mops->status(mgr, buf); +} + +static DEVICE_ATTR(name, S_IRUGO, fpga_mgr_name_show, NULL); +static DEVICE_ATTR(status, S_IRUGO, fpga_mgr_status_show, NULL); + +static struct attribute *fpga_mgr_attrs[] = { + &dev_attr_name.attr, + &dev_attr_status.attr, + NULL, +}; + +static const struct attribute_group fpga_mgr_group = { + .attrs = fpga_mgr_attrs, +}; + +const struct attribute_group *fpga_mgr_groups[] = { + &fpga_mgr_group, + NULL, +}; + +static int fpga_mgr_get_new_minor(struct fpga_manager *mgr, int request_nr) +{ + int nr, start; + + /* check specified minor number */ + if (request_nr >= FPGA_MAX_MINORS) { + dev_err(mgr->parent, "Out of device minors (%d)\n", request_nr); + return -ENODEV; + } + + /* + * If request_nr == -1, dynamically allocate number. + * If request_nr >= 0, attempt to get specific number. + */ + if (request_nr == -1) + start = 0; + else + start = request_nr; + + nr = ida_simple_get(&fpga_mgr_ida, start, FPGA_MAX_MINORS, GFP_KERNEL); + + /* return error code */ + if (nr < 0) + return nr; + + if ((request_nr != -1) && (request_nr != nr)) { + dev_err(mgr->parent, + "Could not get requested device minor (%d)\n", nr); + ida_simple_remove(&fpga_mgr_ida, nr); + return -ENODEV; + } + + mgr->nr = nr; + + return 0; +} + +static void fpga_mgr_free_minor(int nr) +{ + ida_simple_remove(&fpga_mgr_ida, nr); +} + +int register_fpga_manager(struct platform_device *pdev, + struct fpga_manager_ops *mops, char *name, void *priv) +{ + struct fpga_manager *mgr; + int ret; + + if (!mops) { + dev_err(&pdev->dev, + "Attempt to register with no fpga_manager_ops\n"); + return -EINVAL; + } + if (!name || (name[0] == '\0')) { + dev_err(&pdev->dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + mgr = kzalloc(sizeof(struct fpga_manager), GFP_KERNEL); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + mgr->mops = mops; + mgr->np = pdev->dev.of_node; + mgr->parent = get_device(&pdev->dev); + mgr->priv = priv; + strlcpy(mgr->name, name, sizeof(mgr->name)); + init_completion(&mgr->status_complete); + + ret = fpga_mgr_get_new_minor(mgr, pdev->id); + if (ret) + goto error_kfree; + + ret = fpga_mgr_attach_transport(mgr); + if (ret) + goto error_attach; + + if (mops->isr) { + mgr->irq = irq_of_parse_and_map(mgr->np, 0); + if (mgr->irq == NO_IRQ) { + dev_err(mgr->parent, "failed to map interrupt\n"); + goto error_irq_map; + } + + ret = request_irq(mgr->irq, mops->isr, 0, "fpga-mgr", mgr); + if (ret < 0) { + dev_err(mgr->parent, "error requesting interrupt\n"); + goto error_irq_req; + } + } + + cdev_init(&mgr->cdev, &fpga_mgr_fops); + ret = cdev_add(&mgr->cdev, MKDEV(fpga_mgr_major, mgr->nr), 1); + if (ret) + goto error_cdev; + + mgr->dev = device_create(fpga_mgr_class, mgr->parent, + MKDEV(fpga_mgr_major, mgr->nr), mgr, + "fpga%d", mgr->nr); + if (IS_ERR(mgr->dev)) { + ret = PTR_ERR(mgr->dev); + goto error_device; + } + + dev_info(mgr->parent, "fpga manager [%s] registered as minor %d\n", + mgr->name, mgr->nr); + + return 0; + +error_device: + cdev_del(&mgr->cdev); +error_cdev: + free_irq(mgr->irq, mgr); +error_irq_req: + irq_dispose_mapping(mgr->irq); +error_irq_map: + fpga_mgr_detach_transport(mgr); +error_attach: + fpga_mgr_free_minor(mgr->nr); +error_kfree: + put_device(mgr->parent); + kfree(mgr); + return ret; +} +EXPORT_SYMBOL_GPL(register_fpga_manager); + +void remove_fpga_manager(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + if (mgr && mgr->mops && mgr->mops->fpga_remove) + mgr->mops->fpga_remove(mgr); + + device_destroy(fpga_mgr_class, MKDEV(fpga_mgr_major, mgr->nr)); + cdev_del(&mgr->cdev); + free_irq(mgr->irq, mgr); + irq_dispose_mapping(mgr->irq); + fpga_mgr_detach_transport(mgr); + fpga_mgr_free_minor(mgr->nr); + put_device(mgr->parent); + kfree(mgr); +} +EXPORT_SYMBOL_GPL(remove_fpga_manager); + +static int __init fpga_mgr_dev_init(void) +{ + dev_t fpga_mgr_dev; + int ret; + + pr_info("FPGA Mangager framework driver\n"); + + fpga_mgr_class = class_create(THIS_MODULE, "fpga"); + if (IS_ERR(fpga_mgr_class)) + return PTR_ERR(fpga_mgr_class); + + fpga_mgr_class->dev_groups = fpga_mgr_groups; + + ret = alloc_chrdev_region(&fpga_mgr_dev, 0, FPGA_MAX_MINORS, "fpga"); + if (ret) { + class_destroy(fpga_mgr_class); + return ret; + } + + fpga_mgr_major = MAJOR(fpga_mgr_dev); + + return 0; +} + +static void __exit fpga_mgr_dev_exit(void) +{ + unregister_chrdev_region(MKDEV(fpga_mgr_major, 0), FPGA_MAX_MINORS); + class_destroy(fpga_mgr_class); + ida_destroy(&fpga_mgr_ida); +} + +MODULE_DESCRIPTION("FPGA Manager framework driver"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_mgr_dev_init); +module_exit(fpga_mgr_dev_exit); diff --git a/drivers/fpga/fpga-mgr.h b/drivers/fpga/fpga-mgr.h new file mode 100644 index 0000000000000..2c72da342c2ab --- /dev/null +++ b/drivers/fpga/fpga-mgr.h @@ -0,0 +1,24 @@ +/* + * FPGA Framework + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __FPGA_CORE_H +#define __FPGA_CORE_H + +extern const struct file_operations fpga_mgr_fops; + +#endif /* __FPGA_CORE_H */ diff --git a/drivers/fpga/fpga-mgrs/Makefile b/drivers/fpga/fpga-mgrs/Makefile new file mode 100644 index 0000000000000..3b4d59014234d --- /dev/null +++ b/drivers/fpga/fpga-mgrs/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for fpga manager drivers of various fpga families +# + +obj-$(CONFIG_FPGA_MGR_ALTERA) += altera.o diff --git a/drivers/fpga/fpga-mgrs/altera.c b/drivers/fpga/fpga-mgrs/altera.c new file mode 100644 index 0000000000000..3d47505029464 --- /dev/null +++ b/drivers/fpga/fpga-mgrs/altera.c @@ -0,0 +1,576 @@ +/* + * FPGA Manager Driver for Altera FPGAs + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Controls whether to use the Configuration with DCLK steps */ +#ifndef _ALT_FPGA_USE_DCLK +#define _ALT_FPGA_USE_DCLK 0 +#endif + +/* Register offsets */ +#define ALT_FPGAMGR_STAT_OFST 0x0 +#define ALT_FPGAMGR_CTL_OFST 0x4 +#define ALT_FPGAMGR_DCLKCNT_OFST 0x8 +#define ALT_FPGAMGR_DCLKSTAT_OFST 0xc +#define ALT_FPGAMGR_MON_GPIO_INTEN_OFST 0x830 +#define ALT_FPGAMGR_MON_GPIO_INTMSK_OFST 0x834 +#define ALT_FPGAMGR_MON_GPIO_INTTYPE_LEVEL_OFST 0x838 +#define ALT_FPGAMGR_MON_GPIO_INT_POL_OFST 0x83c +#define ALT_FPGAMGR_MON_GPIO_INTSTAT_OFST 0x840 +#define ALT_FPGAMGR_MON_GPIO_RAW_INTSTAT_OFST 0x844 +#define ALT_FPGAMGR_MON_GPIO_PORTA_EOI_OFST 0x84c +#define ALT_FPGAMGR_MON_GPIO_EXT_PORTA_OFST 0x850 + +/* Register bit defines */ +/* ALT_FPGAMGR_STAT register */ +#define ALT_FPGAMGR_STAT_POWER_UP 0x0 +#define ALT_FPGAMGR_STAT_RESET 0x1 +#define ALT_FPGAMGR_STAT_CFG 0x2 +#define ALT_FPGAMGR_STAT_INIT 0x3 +#define ALT_FPGAMGR_STAT_USER_MODE 0x4 +#define ALT_FPGAMGR_STAT_UNKNOWN 0x5 +#define ALT_FPGAMGR_STAT_STATE_MASK 0x7 +/* This is a flag value that doesn't really happen in this register field */ +#define ALT_FPGAMGR_STAT_POWER_OFF 0xf + +#define MSEL_PP16_FAST_NOAES_NODC 0x0 +#define MSEL_PP16_FAST_AES_NODC 0x1 +#define MSEL_PP16_FAST_AESOPT_DC 0x2 +#define MSEL_PP16_SLOW_NOAES_NODC 0x4 +#define MSEL_PP16_SLOW_AES_NODC 0x5 +#define MSEL_PP16_SLOW_AESOPT_DC 0x6 +#define MSEL_PP32_FAST_NOAES_NODC 0x8 +#define MSEL_PP32_FAST_AES_NODC 0x9 +#define MSEL_PP32_FAST_AESOPT_DC 0xa +#define MSEL_PP32_SLOW_NOAES_NODC 0xc +#define MSEL_PP32_SLOW_AES_NODC 0xd +#define MSEL_PP32_SLOW_AESOPT_DC 0xe +#define ALT_FPGAMGR_STAT_MSEL_MASK 0x000000f8 +#define ALT_FPGAMGR_STAT_MSEL_SHIFT 3 + +/* ALT_FPGAMGR_CTL register */ +#define ALT_FPGAMGR_CTL_EN 0x00000001 +#define ALT_FPGAMGR_CTL_NCE 0x00000002 +#define ALT_FPGAMGR_CTL_NCFGPULL 0x00000004 + +#define CDRATIO_X1 0x00000000 +#define CDRATIO_X2 0x00000040 +#define CDRATIO_X4 0x00000080 +#define CDRATIO_X8 0x000000c0 +#define ALT_FPGAMGR_CTL_CDRATIO_MASK 0x000000c0 + +#define ALT_FPGAMGR_CTL_AXICFGEN 0x00000100 + +#define CFGWDTH_16 0x00000000 +#define CFGWDTH_32 0x00000200 +#define ALT_FPGAMGR_CTL_CFGWDTH_MASK 0x00000200 + +/* ALT_FPGAMGR_DCLKSTAT register */ +#define ALT_FPGAMGR_DCLKSTAT_DCNTDONE_E_DONE 0x1 + +/* ALT_FPGAMGR_MON_GPIO_* registers share the same bit positions */ +#define ALT_FPGAMGR_MON_NSTATUS 0x0001 +#define ALT_FPGAMGR_MON_CONF_DONE 0x0002 +#define ALT_FPGAMGR_MON_INIT_DONE 0x0004 +#define ALT_FPGAMGR_MON_CRC_ERROR 0x0008 +#define ALT_FPGAMGR_MON_CVP_CONF_DONE 0x0010 +#define ALT_FPGAMGR_MON_PR_READY 0x0020 +#define ALT_FPGAMGR_MON_PR_ERROR 0x0040 +#define ALT_FPGAMGR_MON_PR_DONE 0x0080 +#define ALT_FPGAMGR_MON_NCONFIG_PIN 0x0100 +#define ALT_FPGAMGR_MON_NSTATUS_PIN 0x0200 +#define ALT_FPGAMGR_MON_CONF_DONE_PIN 0x0400 +#define ALT_FPGAMGR_MON_FPGA_POWER_ON 0x0800 +#define ALT_FPGAMGR_MON_STATUS_MASK 0x0fff + +struct cfgmgr_mode { + /* Values to set in the CTRL register */ + u32 ctrl; + + /* flag that this table entry is a valid mode */ + bool valid; +}; + +/* For ALT_FPGAMGR_STAT_MSEL field */ +static struct cfgmgr_mode cfgmgr_modes[] = { + [MSEL_PP16_FAST_NOAES_NODC] = { CFGWDTH_16 | CDRATIO_X1, 1 }, + [MSEL_PP16_FAST_AES_NODC] = { CFGWDTH_16 | CDRATIO_X2, 1 }, + [MSEL_PP16_FAST_AESOPT_DC] = { CFGWDTH_16 | CDRATIO_X4, 1 }, + [MSEL_PP16_SLOW_NOAES_NODC] = { CFGWDTH_16 | CDRATIO_X1, 1 }, + [MSEL_PP16_SLOW_AES_NODC] = { CFGWDTH_16 | CDRATIO_X2, 1 }, + [MSEL_PP16_SLOW_AESOPT_DC] = { CFGWDTH_16 | CDRATIO_X4, 1 }, + [MSEL_PP32_FAST_NOAES_NODC] = { CFGWDTH_32 | CDRATIO_X1, 1 }, + [MSEL_PP32_FAST_AES_NODC] = { CFGWDTH_32 | CDRATIO_X4, 1 }, + [MSEL_PP32_FAST_AESOPT_DC] = { CFGWDTH_32 | CDRATIO_X8, 1 }, + [MSEL_PP32_SLOW_NOAES_NODC] = { CFGWDTH_32 | CDRATIO_X1, 1 }, + [MSEL_PP32_SLOW_AES_NODC] = { CFGWDTH_32 | CDRATIO_X4, 1 }, + [MSEL_PP32_SLOW_AESOPT_DC] = { CFGWDTH_32 | CDRATIO_X8, 1 }, +}; + +static int alt_fpga_mon_status_get(struct fpga_manager *mgr) +{ + return fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_MON_GPIO_EXT_PORTA_OFST) & + ALT_FPGAMGR_MON_STATUS_MASK; +} + +static int alt_fpga_state_get(struct fpga_manager *mgr) +{ + if ((alt_fpga_mon_status_get(mgr) & ALT_FPGAMGR_MON_FPGA_POWER_ON) == 0) + return ALT_FPGAMGR_STAT_POWER_OFF; + + return fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_STAT_OFST) & + ALT_FPGAMGR_STAT_STATE_MASK; +} + +/* + * Set the DCLKCNT, wait for DCLKSTAT to report the count completed, and clear + * the complete status. + */ +static int alt_fpga_dclk_set_and_wait_clear(struct fpga_manager *mgr, u32 count) +{ + int timeout = 2; + u32 done; + + /* Clear any existing DONE status. */ + if (fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_DCLKSTAT_OFST)) + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_DCLKSTAT_OFST, + ALT_FPGAMGR_DCLKSTAT_DCNTDONE_E_DONE); + + /* Issue the DCLK count. */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_DCLKCNT_OFST, count); + + /* Poll DCLKSTAT to see if it completed in the timeout period. */ + do { + done = fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_DCLKSTAT_OFST); + if (done == ALT_FPGAMGR_DCLKSTAT_DCNTDONE_E_DONE) { + /* clear the DONE status. */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_DCLKSTAT_OFST, + ALT_FPGAMGR_DCLKSTAT_DCNTDONE_E_DONE); + return 0; + } + if (count <= 4) + udelay(1); + else + msleep(20); + } while (timeout--); + + return -ETIMEDOUT; +} + +static int alt_fpga_wait_for_state(struct fpga_manager *mgr, u32 state) +{ + int timeout = 2; + + /* + * HW doesn't support an interrupt for changes in state, so poll to see + * if it matches the requested state within the timeout period. + */ + do { + if ((alt_fpga_state_get(mgr) & state) != 0) + return 0; + msleep(20); + } while (timeout--); + + return -ETIMEDOUT; +} + +static void alt_fpga_enable_irqs(struct fpga_manager *mgr, u32 irqs) +{ + /* set irqs to level sensitive */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_INTTYPE_LEVEL_OFST, 0); + + /* set interrupt polarity */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_INT_POL_OFST, irqs); + + /* clear irqs */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_PORTA_EOI_OFST, irqs); + + /* unmask interrupts */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_INTMSK_OFST, 0); + + /* enable interrupts */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_INTEN_OFST, irqs); +} + +static void alt_fpga_disable_irqs(struct fpga_manager *mgr) +{ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_INTEN_OFST, 0); +} + +static irqreturn_t alt_fpga_isr(int irq, void *dev_id) +{ + struct fpga_manager *mgr = dev_id; + u32 irqs, st; + bool conf_done, nstatus; + + /* clear irqs */ + irqs = fpga_mgr_reg_raw_readl(mgr, ALT_FPGAMGR_MON_GPIO_INTSTAT_OFST); + fpga_mgr_reg_raw_writel(mgr, ALT_FPGAMGR_MON_GPIO_PORTA_EOI_OFST, irqs); + + st = fpga_mgr_reg_raw_readl(mgr, ALT_FPGAMGR_MON_GPIO_EXT_PORTA_OFST); + conf_done = (st & ALT_FPGAMGR_MON_CONF_DONE) != 0; + nstatus = (st & ALT_FPGAMGR_MON_NSTATUS) != 0; + + /* success */ + if (conf_done && nstatus) { + /* disable irqs */ + fpga_mgr_reg_raw_writel(mgr, ALT_FPGAMGR_MON_GPIO_INTEN_OFST, 0); + complete(&mgr->status_complete); + } + + return IRQ_HANDLED; +} + +static int alt_fpga_wait_for_config_done(struct fpga_manager *mgr) +{ + int timeout, ret = 0; + + alt_fpga_disable_irqs(mgr); + reinit_completion(&mgr->status_complete); + alt_fpga_enable_irqs(mgr, ALT_FPGAMGR_MON_CONF_DONE); + + timeout = wait_for_completion_interruptible_timeout( + &mgr->status_complete, + msecs_to_jiffies(10)); + if (timeout == 0) { + dev_err(mgr->parent, "timeout\n"); + ret = -ETIMEDOUT; + } + + alt_fpga_disable_irqs(mgr); + return ret; +} + +static int alt_fpga_cfg_mode_get(struct fpga_manager *mgr) +{ + u32 msel; + + msel = fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_STAT_OFST); + msel &= ALT_FPGAMGR_STAT_MSEL_MASK; + msel >>= ALT_FPGAMGR_STAT_MSEL_SHIFT; + + /* Check that this MSEL setting is supported */ + if ((msel >= sizeof(cfgmgr_modes)/sizeof(struct cfgmgr_mode)) || + !cfgmgr_modes[msel].valid) { + dev_warn(mgr->parent, "Invalid MSEL setting"); + return -1; + } + + return msel; +} + +static int alt_fpga_cfg_mode_set(struct fpga_manager *mgr) +{ + u32 ctrl_reg, mode; + + /* get value from MSEL pins */ + mode = alt_fpga_cfg_mode_get(mgr); + if (mode < 0) + return -EINVAL; + + /* Adjust CTRL for the CDRATIO */ + ctrl_reg = fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_CTL_OFST); + ctrl_reg &= ~ALT_FPGAMGR_CTL_CDRATIO_MASK; + ctrl_reg &= ~ALT_FPGAMGR_CTL_CFGWDTH_MASK; + ctrl_reg |= cfgmgr_modes[mode].ctrl; + + /* Set NCE to 0. */ + ctrl_reg &= ~ALT_FPGAMGR_CTL_NCE; + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_CTL_OFST, ctrl_reg); + + return 0; +} + +static int alt_fpga_reset(struct fpga_manager *mgr) +{ + u32 ctrl_reg, status; + + /* + * Step 3: Set CTRL.NCONFIGPULL to 1 to put FPGA in reset + */ + ctrl_reg = fpga_mgr_reg_readl(mgr, ALT_FPGAMGR_CTL_OFST); + ctrl_reg |= ALT_FPGAMGR_CTL_NCFGPULL; + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_CTL_OFST, ctrl_reg); + + /* + * Step 4: Wait for STATUS.MODE to report FPGA is in reset phase + */ + status = alt_fpga_wait_for_state(mgr, ALT_FPGAMGR_STAT_RESET); + + /* + * Step 5: Set CONTROL.NCONFIGPULL to 0 to release FPGA from reset + */ + ctrl_reg &= ~ALT_FPGAMGR_CTL_NCFGPULL; + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_CTL_OFST, ctrl_reg); + + if (status) { + /* This is a failure from Step 4. */ + dev_err(mgr->parent, + "Error in step 4: Wait for RESET timeout.\n"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Prepare the FPGA to receive the configuration data. + */ +static int alt_fpga_configure_init(struct fpga_manager *mgr) +{ + int ret; + + /* + * Step 1: + * - Set CTRL.CFGWDTH, CTRL.CDRATIO to match cfg mode + * - Set CTRL.NCE to 0 + */ + ret = alt_fpga_cfg_mode_set(mgr); + if (ret) + return ret; + + /* Step 2: Set CTRL.EN to 1 */ + fpga_mgr_reg_set_bitsl(mgr, ALT_FPGAMGR_CTL_OFST, ALT_FPGAMGR_CTL_EN); + + /* Steps 3 - 5: Reset the FPGA */ + ret = alt_fpga_reset(mgr); + if (ret) + return ret; + + /* Step 6: Wait for FPGA to enter configuration phase */ + if (alt_fpga_wait_for_state(mgr, ALT_FPGAMGR_STAT_CFG)) { + dev_err(mgr->parent, + "Error in step 6: Wait for CFG timeout.\n"); + return -ETIMEDOUT; + } + + /* Step 7: Clear nSTATUS interrupt */ + fpga_mgr_reg_writel(mgr, ALT_FPGAMGR_MON_GPIO_PORTA_EOI_OFST, + ALT_FPGAMGR_MON_NSTATUS); + + /* Step 8: Set CTRL.AXICFGEN to 1 to enable transfer of config data */ + fpga_mgr_reg_set_bitsl(mgr, ALT_FPGAMGR_CTL_OFST, + ALT_FPGAMGR_CTL_AXICFGEN); + + return 0; +} + +/* + * Step 9: write data to the FPGA data register + */ +static ssize_t alt_fpga_configure_write(struct fpga_manager *mgr, + char *buf, size_t count) +{ + u32 *buffer_32 = (u32 *)buf; + size_t total = count; + size_t i = 0; + + if (count <= 0) + return 0; + + /* Write out the complete 32-bit chunks. */ + while (count >= sizeof(u32)) { + fpga_mgr_data_writel(mgr, buffer_32[i++]); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks. */ + switch (count) { + case 3: + fpga_mgr_data_writel(mgr, buffer_32[i++] & 0x00ffffff); + break; + case 2: + fpga_mgr_data_writel(mgr, buffer_32[i++] & 0x0000ffff); + break; + case 1: + fpga_mgr_data_writel(mgr, buffer_32[i++] & 0x000000ff); + break; + default: + /* This will never happen. */ + break; + } + + return total; +} + +static int alt_fpga_configure_complete(struct fpga_manager *mgr) +{ + u32 status; + + /* + * Step 10: + * - Observe CONF_DONE and nSTATUS (active low) + * - if CONF_DONE = 1 and nSTATUS = 1, configuration was successful + * - if CONF_DONE = 0 and nSTATUS = 0, configuration failed + */ + status = alt_fpga_wait_for_config_done(mgr); + if (status) + return status; + + /* Step 11: Clear CTRL.AXICFGEN to disable transfer of config data */ + fpga_mgr_reg_clr_bitsl(mgr, ALT_FPGAMGR_CTL_OFST, + ALT_FPGAMGR_CTL_AXICFGEN); + + /* + * Step 12: + * - Write 4 to DCLKCNT + * - Wait for STATUS.DCNTDONE = 1 + * - Clear W1C bit in STATUS.DCNTDONE + */ + if (alt_fpga_dclk_set_and_wait_clear(mgr, 4)) { + dev_err(mgr->parent, "Error: Wait for dclk(4) timeout.\n"); + return -ETIMEDOUT; + } + +#if _ALT_FPGA_USE_DCLK + /* Step 13: Wait for STATUS.MODE to report INIT or USER MODE */ + if (alt_fpga_wait_for_state(mgr, ALT_FPGAMGR_STAT_INIT | + ALT_FPGAMGR_STAT_USER_MODE)) { + dev_err(mgr->parent, + "Error in step 13: Wait for USER_MODE timeout.\n"); + return -ETIMEDOUT; + } + + /* + * Extra steps for Configuration with DCLK for Initialization Phase + * Step 14 (using 4.2.1.2 steps), 15 (using 4.2.1.2 steps) + * - Write 0x5000 to DCLKCNT == the number of clocks needed to exit + * the Initialization Phase. + * - Poll until STATUS.DCNTDONE = 1, write 1 to clear + */ + if (alt_fpga_dclk_set_and_wait_clear(mgr, 0x5000)) { + dev_err(mgr->parent, + "Error in step 15: Wait for dclk(0x5000) timeout.\n"); + return -ETIMEDOUT; + } +#endif + + /* Step 13: Wait for STATUS.MODE to report USER MODE */ + if (alt_fpga_wait_for_state(mgr, ALT_FPGAMGR_STAT_USER_MODE)) { + dev_err(mgr->parent, + "Error in step 13: Wait for USER_MODE timeout.\n"); + return -ETIMEDOUT; + } + + /* Step 14: Set CTRL.EN to 0 */ + fpga_mgr_reg_clr_bitsl(mgr, ALT_FPGAMGR_CTL_OFST, ALT_FPGAMGR_CTL_EN); + + return 0; +} + +static const char *const altera_fpga_state_str[] = { + [ALT_FPGAMGR_STAT_POWER_UP] = "power up phase", + [ALT_FPGAMGR_STAT_RESET] = "reset phase", + [ALT_FPGAMGR_STAT_CFG] = "configuration phase", + [ALT_FPGAMGR_STAT_INIT] = "initialization phase", + [ALT_FPGAMGR_STAT_USER_MODE] = "user mode", + [ALT_FPGAMGR_STAT_UNKNOWN] = "undetermined", + [ALT_FPGAMGR_STAT_POWER_OFF] = "powered off", +}; + +static int alt_fpga_status(struct fpga_manager *mgr, char *buf) +{ + u32 state; + const char *state_str = NULL; + int ret; + + state = alt_fpga_state_get(mgr); + + if (state < sizeof(altera_fpga_state_str)/sizeof(char *)) + state_str = altera_fpga_state_str[state]; + + if (state_str) + ret = sprintf(buf, "%s\n", state_str); + else + ret = sprintf(buf, "%s\n", "unknown state"); + + return ret; +} + +struct fpga_manager_ops altera_fpga_mgr_ops = { + .status = alt_fpga_status, + .write_init = alt_fpga_configure_init, + .write = alt_fpga_configure_write, + .write_complete = alt_fpga_configure_complete, + .isr = alt_fpga_isr, +}; + +static int alt_fpga_probe(struct platform_device *pdev) +{ + return register_fpga_manager(pdev, &altera_fpga_mgr_ops, + "Altera FPGA Manager", NULL); +} + +static int alt_fpga_remove(struct platform_device *pdev) +{ + remove_fpga_manager(pdev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,fpga-mgr", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); +#endif + +static struct platform_driver altera_fpga_driver = { + .remove = alt_fpga_remove, + .driver = { + .name = "altera_fpga_manager", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +static int __init alt_fpga_init(void) +{ + return platform_driver_probe(&altera_fpga_driver, alt_fpga_probe); +} + +static void __exit alt_fpga_exit(void) +{ + platform_driver_unregister(&altera_fpga_driver); +} + +module_init(alt_fpga_init); +module_exit(alt_fpga_exit); + +MODULE_AUTHOR("Alan Tull "); +MODULE_DESCRIPTION("Altera FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/fpga-transport-mmio.c b/drivers/fpga/fpga-transport-mmio.c new file mode 100644 index 0000000000000..5a373cd48c45c --- /dev/null +++ b/drivers/fpga/fpga-transport-mmio.c @@ -0,0 +1,120 @@ +/* + * FPGA Framework Transport + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include + +static u32 fpga_reg_readl(struct fpga_mgr_transport *transp, u32 reg_offset) +{ + return readl(transp->fpga_base_addr + reg_offset); +} + +static void fpga_reg_writel(struct fpga_mgr_transport *transp, u32 reg_offset, + u32 value) +{ + writel(value, transp->fpga_base_addr + reg_offset); +} + +static u32 fpga_reg_raw_readl(struct fpga_mgr_transport *transp, u32 reg_offset) +{ + return __raw_readl(transp->fpga_base_addr + reg_offset); +} + +static void fpga_reg_raw_writel(struct fpga_mgr_transport *transp, u32 reg_offset, + u32 value) +{ + __raw_writel(value, transp->fpga_base_addr + reg_offset); +} + +static u32 fpga_data_readl(struct fpga_mgr_transport *transp) +{ + return readl(transp->fpga_data_addr); +} + +static void fpga_data_writel(struct fpga_mgr_transport *transp, u32 value) +{ + writel(value, transp->fpga_data_addr); +} + +int fpga_attach_mmio_transport(struct fpga_manager *mgr) +{ + struct device_node *np = mgr->np; + struct fpga_mgr_transport *transp; + static void __iomem *fpga_base_addr; + static void __iomem *fpga_data_addr; + int ret = 0; + + fpga_base_addr = of_iomap(np, 0); + if (!fpga_base_addr) { + dev_err(mgr->parent, + "Need to specify fpga manager register address.\n"); + return -EINVAL; + } + + fpga_data_addr = of_iomap(np, 1); + if (!fpga_data_addr) { + dev_err(mgr->parent, + "Need to specify fpga manager data address.\n"); + ret = -ENOMEM; + goto err_baddr; + } + + transp = kzalloc(sizeof(struct fpga_mgr_transport), GFP_KERNEL); + if (!transp) { + dev_err(mgr->parent, "Could not allocate transport\n"); + ret = -ENOMEM; + goto err_daddr; + } + + transp->fpga_base_addr = fpga_base_addr; + transp->fpga_data_addr = fpga_data_addr; + + transp->reg_readl = fpga_reg_readl; + transp->reg_writel = fpga_reg_writel; + transp->reg_raw_readl = fpga_reg_raw_readl; + transp->reg_raw_writel = fpga_reg_raw_writel; + transp->data_writel = fpga_data_writel; + transp->data_readl = fpga_data_readl; + strlcpy(transp->type, "mmio", sizeof(transp->type)); + + transp->mgr = mgr; + mgr->transp = transp; + + return 0; + +err_daddr: + iounmap(fpga_data_addr); +err_baddr: + iounmap(fpga_base_addr); + return ret; +} +EXPORT_SYMBOL(fpga_attach_mmio_transport); + +void fpga_detach_mmio_transport(struct fpga_manager *mgr) +{ + struct fpga_mgr_transport *transp = mgr->transp; + + iounmap(transp->fpga_base_addr); + iounmap(transp->fpga_data_addr); + kfree(transp); +} +EXPORT_SYMBOL(fpga_detach_mmio_transport); diff --git a/drivers/fpga/fpga-transport.c b/drivers/fpga/fpga-transport.c new file mode 100644 index 0000000000000..7f68ad4ec5220 --- /dev/null +++ b/drivers/fpga/fpga-transport.c @@ -0,0 +1,63 @@ +/* + * FPGA Framework Transport + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include + +int fpga_mgr_attach_transport(struct fpga_manager *mgr) +{ + struct device_node *np = mgr->np; + const char *string; + int ret; + + ret = of_property_read_string(np, "transport", &string); + if (ret) { + dev_err(mgr->parent, + "Transport not specified for fpga manager %s\n", + mgr->name); + return -ENODEV; + } + + if (strcmp(string, "mmio") == 0) + ret = fpga_attach_mmio_transport(mgr); + else { + dev_err(mgr->parent, + "Invalid transport specified for fpga manager %s (%s)\n", + mgr->name, string); + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(fpga_mgr_attach_transport); + +void fpga_mgr_detach_transport(struct fpga_manager *mgr) +{ + struct fpga_mgr_transport *transp = mgr->transp; + const char *type = transp->type; + + if (!strncmp(type, "mmio", strlen(type))) + fpga_detach_mmio_transport(mgr); + else + BUG(); +} +EXPORT_SYMBOL(fpga_mgr_detach_transport); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 4a1b5113e5277..8bd7acfab2737 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -141,6 +141,12 @@ config GPIO_DWAPB Say Y or M here to build support for the Synopsys DesignWare APB GPIO block. +config GPIO_ALTERA + tristate "Altera GPIO" + depends on OF_GPIO + help + Say yes here to support the Altera PIO device. + config GPIO_IT8761E tristate "IT8761E GPIO support" depends on X86 # unconditional access to IO space. diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index d10f6a9d875a2..77fb72fa028fb 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o obj-$(CONFIG_GPIO_ADP5588) += gpio-adp5588.o +obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o diff --git a/drivers/gpio/gpio-altera.c b/drivers/gpio/gpio-altera.c new file mode 100644 index 0000000000000..79d7a78953eaa --- /dev/null +++ b/drivers/gpio/gpio-altera.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2013 Altera Corporation + * Based on gpio-mpc8xxx.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ALTERA_GPIO_DATA 0x0 +#define ALTERA_GPIO_DIR 0x4 +#define ALTERA_GPIO_IRQ_MASK 0x8 +#define ALTERA_GPIO_EDGE_CAP 0xc +#define ALTERA_GPIO_OUTSET 0x10 +#define ALTERA_GPIO_OUTCLEAR 0x14 + +/** +* struct altera_gpio_chip +* @mmchip : memory mapped chip structure. +* @irq : irq domain that this driver is registered to. +* @gpio_lock : synchronization lock so that new irq/set/get requests + will be blocked until the current one completes. +* @interrupt_trigger : specifies the hardware configured IRQ trigger type + (rising, falling, both, high) +* @mapped_irq : kernel mapped irq number. +*/ +struct altera_gpio_chip { + struct of_mm_gpio_chip mmchip; + struct irq_domain *domain; + spinlock_t gpio_lock; + int interrupt_trigger; + int edge_type; + int mapped_irq; +}; + +static void altera_gpio_irq_unmask(struct irq_data *d) +{ + struct altera_gpio_chip *altera_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm_gc = &altera_gc->mmchip; + unsigned long flags; + unsigned int intmask; + + spin_lock_irqsave(&altera_gc->gpio_lock, flags); + intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK); + /* Set ALTERA_GPIO_IRQ_MASK bit to unmask */ + intmask |= BIT(irqd_to_hwirq(d)); + writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK); + spin_unlock_irqrestore(&altera_gc->gpio_lock, flags); +} + +static void altera_gpio_irq_mask(struct irq_data *d) +{ + struct altera_gpio_chip *altera_gc = irq_data_get_irq_chip_data(d); + struct of_mm_gpio_chip *mm_gc = &altera_gc->mmchip; + unsigned long flags; + unsigned int intmask; + + spin_lock_irqsave(&altera_gc->gpio_lock, flags); + intmask = readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK); + /* Clear ALTERA_GPIO_IRQ_MASK bit to mask */ + intmask &= ~BIT(irqd_to_hwirq(d)); + writel(intmask, mm_gc->regs + ALTERA_GPIO_IRQ_MASK); + spin_unlock_irqrestore(&altera_gc->gpio_lock, flags); +} + +static int altera_gpio_irq_set_type(struct irq_data *d, + unsigned int type) +{ + struct altera_gpio_chip *altera_gc = irq_data_get_irq_chip_data(d); + + if (type == IRQ_TYPE_NONE) + return 0; + + if (type == IRQ_TYPE_LEVEL_HIGH && + altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH) { + return 0; + } else { + if (type == IRQ_TYPE_EDGE_RISING && + altera_gc->interrupt_trigger == IRQ_TYPE_EDGE_RISING) + return 0; + else if (type == IRQ_TYPE_EDGE_FALLING && + altera_gc->interrupt_trigger == IRQ_TYPE_EDGE_FALLING) + return 0; + else if (type == IRQ_TYPE_EDGE_BOTH && + altera_gc->interrupt_trigger == IRQ_TYPE_EDGE_BOTH) + return 0; + } + + return -EINVAL; +} + +static unsigned int altera_gpio_irq_startup(struct irq_data *d) +{ + altera_gpio_irq_unmask(d); + + return 0; +} + +static void altera_gpio_irq_shutdown(struct irq_data *d) +{ + altera_gpio_irq_unmask(d); +} + +static struct irq_chip altera_irq_chip = { + .name = "altera-gpio", + .irq_mask = altera_gpio_irq_mask, + .irq_unmask = altera_gpio_irq_unmask, + .irq_set_type = altera_gpio_irq_set_type, + .irq_startup = altera_gpio_irq_startup, + .irq_shutdown = altera_gpio_irq_shutdown, +}; + +static int altera_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + + return !!(readl(mm_gc->regs + ALTERA_GPIO_DATA) >> offset); +} + +static void altera_gpio_set(struct gpio_chip *gc, unsigned offset, int value) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct altera_gpio_chip *chip = container_of(mm_gc, + struct altera_gpio_chip, mmchip); + unsigned long flags; + unsigned int data_reg; + + spin_lock_irqsave(&chip->gpio_lock, flags); + data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA); + if (value) + data_reg |= BIT(offset); + else + data_reg &= ~BIT(offset); + writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA); + spin_unlock_irqrestore(&chip->gpio_lock, flags); +} + +static int altera_gpio_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct altera_gpio_chip *chip = container_of(mm_gc, + struct altera_gpio_chip, mmchip); + unsigned long flags; + unsigned int gpio_ddr; + + spin_lock_irqsave(&chip->gpio_lock, flags); + /* Set pin as input, assumes software controlled IP */ + gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR); + gpio_ddr &= ~BIT(offset); + writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR); + spin_unlock_irqrestore(&chip->gpio_lock, flags); + + return 0; +} + +static int altera_gpio_direction_output(struct gpio_chip *gc, + unsigned offset, int value) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct altera_gpio_chip *chip = container_of(mm_gc, + struct altera_gpio_chip, mmchip); + unsigned long flags; + unsigned int data_reg, gpio_ddr; + + spin_lock_irqsave(&chip->gpio_lock, flags); + /* Sets the GPIO value */ + data_reg = readl(mm_gc->regs + ALTERA_GPIO_DATA); + if (value) + data_reg |= BIT(offset); + else + data_reg &= ~BIT(offset); + writel(data_reg, mm_gc->regs + ALTERA_GPIO_DATA); + + /* Set pin as output, assumes software controlled IP */ + gpio_ddr = readl(mm_gc->regs + ALTERA_GPIO_DIR); + gpio_ddr |= BIT(offset); + writel(gpio_ddr, mm_gc->regs + ALTERA_GPIO_DIR); + spin_unlock_irqrestore(&chip->gpio_lock, flags); + + return 0; +} + +static int altera_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct altera_gpio_chip *altera_gc = container_of(mm_gc, + struct altera_gpio_chip, mmchip); + + if (!altera_gc->domain) + return -ENXIO; + if (offset < altera_gc->mmchip.gc.ngpio) + return irq_find_mapping(altera_gc->domain, offset); + else + return -ENXIO; +} + +static void altera_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct altera_gpio_chip *altera_gc = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct of_mm_gpio_chip *mm_gc = &altera_gc->mmchip; + unsigned long status; + + int i; + + chained_irq_enter(chip, desc); + /* Handling for level trigger and edge trigger is different */ + if (altera_gc->interrupt_trigger == IRQ_TYPE_LEVEL_HIGH) { + status = readl(mm_gc->regs + ALTERA_GPIO_DATA); + status &= readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK); + + for (i = 0; i < mm_gc->gc.ngpio; i++) { + if (status & BIT(i)) { + generic_handle_irq(irq_find_mapping( + altera_gc->domain, i)); + } + } + } else { + while ((status = + (readl(mm_gc->regs + ALTERA_GPIO_EDGE_CAP) & + readl(mm_gc->regs + ALTERA_GPIO_IRQ_MASK)))) { + writel(status, mm_gc->regs + ALTERA_GPIO_EDGE_CAP); + for (i = 0; i < mm_gc->gc.ngpio; i++) { + if (status & BIT(i)) { + generic_handle_irq(irq_find_mapping( + altera_gc->domain, i)); + } + } + } + } + + chained_irq_exit(chip, desc); +} + +static int altera_gpio_irq_map(struct irq_domain *h, unsigned int irq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_data(irq, h->host_data); + irq_set_chip_and_handler(irq, &altera_irq_chip, handle_level_irq); + irq_set_irq_type(irq, IRQ_TYPE_NONE); + + return 0; +} + +static struct irq_domain_ops altera_gpio_irq_ops = { + .map = altera_gpio_irq_map, + .xlate = irq_domain_xlate_onecell, +}; + +int altera_gpio_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + int i, id, reg, ret; + struct altera_gpio_chip *altera_gc = devm_kzalloc(&pdev->dev, + sizeof(*altera_gc), GFP_KERNEL); + if (altera_gc == NULL) { + pr_err("%s: out of memory\n", node->full_name); + return -ENOMEM; + } + altera_gc->domain = 0; + + spin_lock_init(&altera_gc->gpio_lock); + + id = pdev->id; + + if (of_property_read_u32(node, "altr,gpio-bank-width", ®)) + /*By default assume full GPIO controller*/ + altera_gc->mmchip.gc.ngpio = 32; + else + altera_gc->mmchip.gc.ngpio = reg; + + if (altera_gc->mmchip.gc.ngpio > 32) { + dev_warn(&pdev->dev, + "ngpio is greater than 32, defaulting to 32\n"); + altera_gc->mmchip.gc.ngpio = 32; + } + + altera_gc->mmchip.gc.direction_input = altera_gpio_direction_input; + altera_gc->mmchip.gc.direction_output = altera_gpio_direction_output; + altera_gc->mmchip.gc.get = altera_gpio_get; + altera_gc->mmchip.gc.set = altera_gpio_set; + altera_gc->mmchip.gc.to_irq = altera_gpio_to_irq; + altera_gc->mmchip.gc.owner = THIS_MODULE; + + ret = of_mm_gpiochip_add(node, &altera_gc->mmchip); + if (ret) { + dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); + return ret; + } + + platform_set_drvdata(pdev, altera_gc); + + altera_gc->mapped_irq = irq_of_parse_and_map(node, 0); + + if (!altera_gc->mapped_irq) + goto skip_irq; + + altera_gc->domain = irq_domain_add_linear(node, + altera_gc->mmchip.gc.ngpio, &altera_gpio_irq_ops, altera_gc); + + if (!altera_gc->domain) { + ret = -ENODEV; + goto dispose_irq; + } + + for (i = 0; i < altera_gc->mmchip.gc.ngpio; i++) + irq_create_mapping(altera_gc->domain, i); + + if (of_property_read_u32(node, "altr,interrupt_type", ®)) { + ret = -EINVAL; + dev_err(&pdev->dev, + "altr,interrupt_type value not set in device tree\n"); + goto teardown; + } + altera_gc->interrupt_trigger = reg; + + irq_set_handler_data(altera_gc->mapped_irq, altera_gc); + irq_set_chained_handler(altera_gc->mapped_irq, altera_gpio_irq_handler); + + return 0; + +teardown: + irq_domain_remove(altera_gc->domain); +dispose_irq: + irq_dispose_mapping(altera_gc->mapped_irq); + WARN_ON(gpiochip_remove(&altera_gc->mmchip.gc) < 0); + + pr_err("%s: registration failed with status %d\n", + node->full_name, ret); + + return ret; +skip_irq: + return 0; +} + +static int altera_gpio_remove(struct platform_device *pdev) +{ + unsigned int irq, i; + int status; + struct altera_gpio_chip *altera_gc = platform_get_drvdata(pdev); + + status = gpiochip_remove(&altera_gc->mmchip.gc); + + if (status < 0) + return status; + + if (altera_gc->domain) { + irq_dispose_mapping(altera_gc->mapped_irq); + + for (i = 0; i < altera_gc->mmchip.gc.ngpio; i++) { + irq = irq_find_mapping(altera_gc->domain, i); + if (irq > 0) + irq_dispose_mapping(irq); + } + + irq_domain_remove(altera_gc->domain); + } + + irq_set_handler_data(altera_gc->mapped_irq, NULL); + irq_set_chained_handler(altera_gc->mapped_irq, NULL); + return -EIO; +} + +static struct of_device_id altera_gpio_of_match[] = { + { .compatible = "altr,pio-1.0", }, + {}, +}; +MODULE_DEVICE_TABLE(of, altera_gpio_of_match); + +static struct platform_driver altera_gpio_driver = { + .driver = { + .name = "altera_gpio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_gpio_of_match), + }, + .probe = altera_gpio_probe, + .remove = altera_gpio_remove, +}; + +static int __init altera_gpio_init(void) +{ + return platform_driver_register(&altera_gpio_driver); +} +subsys_initcall(altera_gpio_init); + +static void __exit altera_gpio_exit(void) +{ + platform_driver_unregister(&altera_gpio_driver); +} +module_exit(altera_gpio_exit); + +MODULE_AUTHOR("Tien Hock Loh "); +MODULE_DESCRIPTION("Altera GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/gpio-dw.c b/drivers/gpio/gpio-dw.c new file mode 100644 index 0000000000000..802bd1491973f --- /dev/null +++ b/drivers/gpio/gpio-dw.c @@ -0,0 +1,217 @@ +/* + * Designware GPIO support functions + * + * Copyright (C) 2012 Altera + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_INT_EN_REG_OFFSET (0x30) +#define GPIO_INT_MASK_REG_OFFSET (0x34) +#define GPIO_INT_TYPE_LEVEL_REG_OFFSET (0x38) +#define GPIO_INT_POLARITY_REG_OFFSET (0x3c) +#define GPIO_INT_STATUS_REG_OFFSET (0x40) +#define GPIO_PORT_A_EOI_REG_OFFSET (0x4c) + +#define GPIO_DDR_OFFSET_PORT (0x4) +#define DW_GPIO_EXT (0x50) +#define DW_GPIO_DR (0x0) +#define DRV_NAME "dw gpio" + +struct dw_gpio_instance { + struct of_mm_gpio_chip mmchip; + u32 gpio_state; /* GPIO state shadow register */ + u32 gpio_dir; /* GPIO direction shadow register */ + int irq; /* GPIO controller IRQ number */ + int irq_base; /* base number for the "virtual" GPIO IRQs */ + u32 irq_mask; /* IRQ mask */ + spinlock_t gpio_lock; /* Lock used for synchronization */ +}; + +static int dw_gpio_get(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + + return (__raw_readl(mm_gc->regs + DW_GPIO_EXT) >> offset) & 1; +} + +static void dw_gpio_set(struct gpio_chip *gc, unsigned offset, int value) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct dw_gpio_instance *chip = container_of(mm_gc, struct dw_gpio_instance, mmchip); + unsigned long flags; + u32 data_reg; + + spin_lock_irqsave(&chip->gpio_lock, flags); + data_reg = __raw_readl(mm_gc->regs + DW_GPIO_DR); + data_reg = (data_reg & ~(1<regs + DW_GPIO_DR); + spin_unlock_irqrestore(&chip->gpio_lock, flags); +} + +static int dw_gpio_direction_input(struct gpio_chip *gc, unsigned offset) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct dw_gpio_instance *chip = container_of(mm_gc, struct dw_gpio_instance, mmchip); + unsigned long flags; + u32 gpio_ddr; + + spin_lock_irqsave(&chip->gpio_lock, flags); + /* Set pin as input, assumes software controlled IP */ + gpio_ddr = __raw_readl(mm_gc->regs + GPIO_DDR_OFFSET_PORT); + gpio_ddr &= ~(1 << offset); + __raw_writel(gpio_ddr, mm_gc->regs + GPIO_DDR_OFFSET_PORT); + spin_unlock_irqrestore(&chip->gpio_lock, flags); + + return 0; +} + +static int dw_gpio_direction_output(struct gpio_chip *gc, + unsigned offset, int value) +{ + struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); + struct dw_gpio_instance *chip = container_of(mm_gc, struct dw_gpio_instance, mmchip); + unsigned long flags; + u32 gpio_ddr; + + dw_gpio_set(gc, offset, value); + + spin_lock_irqsave(&chip->gpio_lock, flags); + /* Set pin as output, assumes software controlled IP */ + gpio_ddr = __raw_readl(mm_gc->regs + GPIO_DDR_OFFSET_PORT); + gpio_ddr |= (1 << offset); + __raw_writel(gpio_ddr, mm_gc->regs + GPIO_DDR_OFFSET_PORT); + spin_unlock_irqrestore(&chip->gpio_lock, flags); + return 0; +} + +/* + * dw_gpio_probe - Probe method for the GPIO device. + * @np: pointer to device tree node + * + * This function probes the GPIO device in the device tree. It initializes the + * driver data structure. It returns 0, if the driver is bound to the GPIO + * device, or a negative value if there is an error. + */ +static int __devinit dw_gpio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct dw_gpio_instance *chip; + int status = 0; + u32 reg; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + { + printk(KERN_ERR "%s 2 ERROR allocating memory", __func__); + return -ENOMEM; + } + + /* Update GPIO state shadow register with default value */ + if (of_property_read_u32(np, "resetvalue", ®) == 0) + chip->gpio_state = reg; + + /* Update GPIO direction shadow register with default value */ + chip->gpio_dir = 0; /* By default, all pins are inputs */ + + /* Check device node for device width */ + if (of_property_read_u32(np, "width", ®) == 0) + chip->mmchip.gc.ngpio = reg; + else + chip->mmchip.gc.ngpio = 32; /* By default assume full GPIO controller */ + + spin_lock_init(&chip->gpio_lock); + + chip->mmchip.gc.direction_input = dw_gpio_direction_input; + chip->mmchip.gc.direction_output = dw_gpio_direction_output; + chip->mmchip.gc.get = dw_gpio_get; + chip->mmchip.gc.set = dw_gpio_set; + + /* Call the OF gpio helper to setup and register the GPIO device */ + status = of_mm_gpiochip_add(np, &chip->mmchip); + if (status) { + kfree(chip); + pr_err("%s: error in probe function with status %d\n", + np->full_name, status); + return status; + } + + platform_set_drvdata(pdev, chip); + return 0; +} + +static int dw_gpio_remove(struct platform_device *pdev) +{ + /* todo check this and see that we don't have a memory leak */ + int status; + + struct dw_gpio_instance *chip = platform_get_drvdata(pdev); + status = gpiochip_remove(&chip->mmchip.gc); + if (status < 0) + return status; + + kfree(chip); + return -EIO; +} + +#ifdef CONFIG_OF +static const struct of_device_id dwgpio_match[] = { + {.compatible = DW_GPIO_COMPATIBLE,}, + {} +}; +MODULE_DEVICE_TABLE(of, dwgpio_match); +#else +#define dwgpio_match NULL +#endif + +static struct platform_driver dwgpio_driver = { + .driver = { + .name = "dw_gpio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(dwgpio_match), + }, + .probe = dw_gpio_probe, + .remove = dw_gpio_remove, +}; + +static int __init dwgpio_init(void) +{ + return platform_driver_register(&dwgpio_driver); +} +subsys_initcall(dwgpio_init); + +static void __exit dwgpio_exit(void) +{ + platform_driver_unregister(&dwgpio_driver); +} +module_exit(dwgpio_exit); + + +MODULE_DESCRIPTION("Altera GPIO driver"); +MODULE_AUTHOR("Thomas Chou "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 402ec3970feda..17452efefdbe7 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -119,9 +119,11 @@ static inline int dw_i2c_acpi_configure(struct platform_device *pdev) static int dw_i2c_probe(struct platform_device *pdev) { struct dw_i2c_dev *dev; + struct device_node *np = pdev->dev.of_node; struct i2c_adapter *adap; struct resource *mem; int irq, r; + int speed, speed_prop, ret; irq = platform_get_irq(pdev, 0); if (irq < 0) { @@ -175,8 +177,16 @@ static int dw_i2c_probe(struct platform_device *pdev) I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; + + /* Get speed from device tree. Default to fast speed. */ + speed = DW_IC_CON_SPEED_FAST; + if (np) { + ret = of_property_read_u32(np, "speed-mode", &speed_prop); + if (!ret && (speed_prop == 0)) + speed = DW_IC_CON_SPEED_STD; + } dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | - DW_IC_CON_RESTART_EN | DW_IC_CON_SPEED_FAST; + DW_IC_CON_RESTART_EN | speed; /* Try first if we can configure the device from ACPI */ r = dw_i2c_acpi_configure(pdev); diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index c8b5c13bcd05e..88544dcf5545f 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -50,4 +50,11 @@ config OMAP_MBOX_KFIFO_SIZE Specify the default size of mailbox's kfifo buffers (bytes). This can also be changed at runtime (via the mbox_kfifo_size module parameter). + +config ALTERA_MBOX + tristate "Altera Mailbox" + help + An implementation of the Altera Mailbox (simple) soft core. Is is used + to send message between processors. Say Y here if you want to use the + Altera mailbox support. endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index e0facb34084a4..c8f70394a4f5a 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -1,3 +1,7 @@ +# Generic MAILBOX API + +obj-$(CONFIG_MAILBOX) += mailbox.o + obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o obj-$(CONFIG_OMAP_MBOX) += omap-mailbox.o @@ -5,3 +9,4 @@ obj-$(CONFIG_OMAP1_MBOX) += mailbox_omap1.o mailbox_omap1-objs := mailbox-omap1.o obj-$(CONFIG_OMAP2PLUS_MBOX) += mailbox_omap2.o mailbox_omap2-objs := mailbox-omap2.o +obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o diff --git a/drivers/mailbox/mailbox-altera.c b/drivers/mailbox/mailbox-altera.c new file mode 100644 index 0000000000000..5a055c1163edb --- /dev/null +++ b/drivers/mailbox/mailbox-altera.c @@ -0,0 +1,416 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAILBOX_CMD_REG 0x00 +#define MAILBOX_PTR_REG 0x04 +#define MAILBOX_STS_REG 0x08 +#define MAILBOX_INTMASK_REG 0x0C + +#define INT_PENDING_MSK 0x1 +#define INT_SPACE_MSK 0x2 + +#define STS_PENDING_MSK 0x1 +#define STS_FULL_MSK 0x2 +#define STS_FULL_OFT 0x1 + +#define MBOX_PENDING(status) (((status) & STS_PENDING_MSK)) +#define MBOX_FULL(status) (((status) & STS_FULL_MSK) >> STS_FULL_OFT) + +enum altera_mbox_msg { + MBOX_CMD = 0, + MBOX_PTR, +}; + +#define MBOX_POLLING_MS 1 /* polling interval 1ms */ + +struct altera_mbox { + bool is_sender; /* 1-sender, 0-receiver */ + bool intr_mode; + int irq; + int use_count; + void __iomem *mbox_base; + struct device *dev; + struct ipc_link link; + struct ipc_controller ipc_con; + struct mutex lock; + /* If the controller supports only RX polling mode */ + struct timer_list rxpoll_timer; +}; + +static inline struct altera_mbox *to_altera_mbox(struct ipc_link *link) +{ + if (!link) + return NULL; + + return container_of(link, struct altera_mbox, link); +} + +static inline int altera_mbox_full(struct altera_mbox *mbox) +{ + u32 status; + status = __raw_readl(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_FULL(status); +} + +static inline int altera_mbox_pending(struct altera_mbox *mbox) +{ + u32 status; + status = __raw_readl(mbox->mbox_base + MAILBOX_STS_REG); + return MBOX_PENDING(status); +} + +static void altera_mbox_rx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + mask = __raw_readl(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_PENDING_MSK; + else + mask &= ~INT_PENDING_MSK; + __raw_writel(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static void altera_mbox_tx_intmask(struct altera_mbox *mbox, bool enable) +{ + u32 mask; + mask = __raw_readl(mbox->mbox_base + MAILBOX_INTMASK_REG); + if (enable) + mask |= INT_SPACE_MSK; + else + mask &= ~INT_SPACE_MSK; + __raw_writel(mask, mbox->mbox_base + MAILBOX_INTMASK_REG); +} + +static bool altera_mbox_is_sender(struct altera_mbox *mbox) +{ + u32 reg; + /* Write a magic number to PTR register and read back this register. + * This register is read-write if it is a sender. + */ + #define MBOX_MAGIC 0xA5A5AA55 + __raw_writel(MBOX_MAGIC, mbox->mbox_base + MAILBOX_PTR_REG); + reg = __raw_readl(mbox->mbox_base + MAILBOX_PTR_REG); + if (reg == MBOX_MAGIC) { + /* Clear to 0 */ + __raw_writel(0, mbox->mbox_base + MAILBOX_PTR_REG); + return true; + } + return false; +} + +static void altera_mbox_rx_data(struct ipc_link *link) +{ + struct altera_mbox *mbox = to_altera_mbox(link); + u32 data[2]; + + if (altera_mbox_pending(mbox)) { + data[MBOX_PTR] = __raw_readl(mbox->mbox_base + MAILBOX_PTR_REG); + data[MBOX_CMD] = __raw_readl(mbox->mbox_base + MAILBOX_CMD_REG); + ipc_link_received_data(link, (void *)data); + } + + return; +} + +static void altera_mbox_poll_rx(unsigned long data) +{ + struct ipc_link *link = (struct ipc_link *)data; + struct altera_mbox *mbox = to_altera_mbox(link); + + altera_mbox_rx_data(link); + + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); +} + +static irqreturn_t altera_mbox_tx_interrupt(int irq, void *p) +{ + struct ipc_link *link = (struct ipc_link *)p; + struct altera_mbox *mbox = to_altera_mbox(link); + + altera_mbox_tx_intmask(mbox, false); + ipc_link_txdone(link, XFER_OK); + + return IRQ_HANDLED; +} + +static irqreturn_t altera_mbox_rx_interrupt(int irq, void *p) +{ + struct ipc_link *link = (struct ipc_link *)p; + altera_mbox_rx_data(link); + return IRQ_HANDLED; +} + +static int altera_mbox_startup_sender(struct ipc_link *link) +{ + int ret; + struct altera_mbox *mbox = to_altera_mbox(link); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_tx_interrupt, 0, + mbox->ipc_con.controller_name, link); + if (ret) { + dev_err(mbox->dev, + "failed to register mailbox interrupt:%d\n", + ret); + return ret; + } + } + + return 0; +} + +static int altera_mbox_startup_receiver(struct ipc_link *link) +{ + int ret; + struct altera_mbox *mbox = to_altera_mbox(link); + + if (mbox->intr_mode) { + ret = request_irq(mbox->irq, altera_mbox_rx_interrupt, 0, + mbox->ipc_con.controller_name, link); + if (ret) { + dev_err(mbox->dev, + "failed to register mailbox interrupt:%d\n", + ret); + return ret; + } + altera_mbox_rx_intmask(mbox, true); + } else { + /* Setup polling timer */ + setup_timer(&mbox->rxpoll_timer, altera_mbox_poll_rx, + (unsigned long)link); + mod_timer(&mbox->rxpoll_timer, + jiffies + msecs_to_jiffies(MBOX_POLLING_MS)); + } + + return 0; +} + +static int altera_mbox_send_data(struct ipc_link *link, void *data) +{ + struct altera_mbox *mbox = to_altera_mbox(link); + u32 *udata = (u32 *)data; + + if (!mbox || !data) + return -EINVAL; + if (!mbox->is_sender) { + dev_warn(mbox->dev, + "failed to send. This is receiver mailbox.\n"); + return -EINVAL; + } + + if (altera_mbox_full(mbox)) + return -EBUSY; + + /* Enable interrupt before send */ + altera_mbox_tx_intmask(mbox, true); + + /* Pointer register must write before command register */ + __raw_writel(udata[MBOX_PTR], mbox->mbox_base + MAILBOX_PTR_REG); + __raw_writel(udata[MBOX_CMD], mbox->mbox_base + MAILBOX_CMD_REG); + + return 0; +} + +static bool altera_mbox_is_ready(struct ipc_link *link) +{ + struct altera_mbox *mbox = to_altera_mbox(link); + + if (WARN_ON(!mbox)) + return false; + + /* Return false if mailbox is full */ + return altera_mbox_full(mbox) ? false : true; +} + +static int altera_mbox_startup(struct ipc_link *link, void *ignored) +{ + struct altera_mbox *mbox = to_altera_mbox(link); + int ret = 0; + + if (!mbox) + return -EINVAL; + + mutex_lock(&mbox->lock); + if (!mbox->use_count) { + if (mbox->is_sender) + ret = altera_mbox_startup_sender(link); + else + ret = altera_mbox_startup_receiver(link); + + if (!ret) + mbox->use_count++; + } + mutex_unlock(&mbox->lock); + return ret; +} + +static void altera_mbox_shutdown(struct ipc_link *link) +{ + struct altera_mbox *mbox = to_altera_mbox(link); + + if (WARN_ON(!mbox)) + return; + + mutex_lock(&mbox->lock); + if (!--mbox->use_count) { + if (mbox->intr_mode) { + /* Unmask all interrupt masks */ + __raw_writel(~0, mbox->mbox_base + MAILBOX_INTMASK_REG); + free_irq(mbox->irq, link); + } else if (!mbox->is_sender) + del_timer_sync(&mbox->rxpoll_timer); + } + mutex_unlock(&mbox->lock); +} + +static struct ipc_link_ops altera_mbox_ops = { + .send_data = altera_mbox_send_data, + .startup = altera_mbox_startup, + .shutdown = altera_mbox_shutdown, + .is_ready = altera_mbox_is_ready, +}; + +static int altera_mbox_probe(struct platform_device *pdev) +{ + struct altera_mbox *mbox; + struct ipc_link *links[2] = {NULL, NULL}; + struct resource *regs; + struct device_node *np = pdev->dev.of_node; + int ret; + const char *mbox_name = NULL; + + mbox = devm_kzalloc(&pdev->dev, sizeof(struct altera_mbox), + GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + mbox->mbox_base = devm_request_and_ioremap(&pdev->dev, regs); + if (!mbox->mbox_base) + return -EADDRNOTAVAIL; + + mbox->dev = &pdev->dev; + mutex_init(&mbox->lock); + + /* Check is it a sender or receiver? */ + mbox->is_sender = altera_mbox_is_sender(mbox); + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq >= 0) + mbox->intr_mode = true; + + /* Hardware supports only one channel, link_name always set to "0". */ + snprintf(mbox->link.link_name, sizeof(mbox->link.link_name), "0"); + links[0] = &mbox->link; + mbox->ipc_con.links = links; + mbox->ipc_con.ops = &altera_mbox_ops; + + if ((strlen(np->name) + 1) > sizeof(mbox->ipc_con.controller_name)) + dev_warn(&pdev->dev, "Length of mailbox controller name is greater than %zu\n", + sizeof(mbox->ipc_con.controller_name)); + + ret = of_property_read_string(np, "linux,mailbox-name", &mbox_name); + if (ret) { + dev_err(&pdev->dev, "Missing linux,mailbox-name property in device tree\n"); + goto err; + } + + snprintf(mbox->ipc_con.controller_name, + sizeof(mbox->ipc_con.controller_name), "%s", mbox_name); + + dev_info(&pdev->dev, "Mailbox controller name is %s\n", + mbox->ipc_con.controller_name); + + if (mbox->is_sender) { + if (mbox->intr_mode) + mbox->ipc_con.txdone_irq = true; + else { + mbox->ipc_con.txdone_poll = true; + mbox->ipc_con.txpoll_period = MBOX_POLLING_MS; + } + } + + ret = ipc_links_register(&mbox->ipc_con); + if (ret) { + dev_err(&pdev->dev, "Register mailbox failed\n"); + goto err; + } + + platform_set_drvdata(pdev, mbox); + return 0; +err: + return ret; +} + +static int altera_mbox_remove(struct platform_device *pdev) +{ + struct altera_mbox *mbox = platform_get_drvdata(pdev); + if (!mbox) + return -EINVAL; + + ipc_links_unregister(&mbox->ipc_con); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_mbox_match[] = { + { .compatible = "altr,mailbox-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mbox_match); + +static struct platform_driver altera_mbox_driver = { + .probe = altera_mbox_probe, + .remove = altera_mbox_remove, + .driver = { + .name = "altera-mailbox", + .owner = THIS_MODULE, + .of_match_table = altera_mbox_match, + }, +}; + +static int altera_mbox_init(void) +{ + return platform_driver_register(&altera_mbox_driver); +} + +static void altera_mbox_exit(void) +{ + platform_driver_unregister(&altera_mbox_driver); +} + +module_init(altera_mbox_init); +module_exit(altera_mbox_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera mailbox specific functions"); +MODULE_AUTHOR("Ley Foon Tan "); +MODULE_ALIAS("platform:altera-mailbox"); diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c new file mode 100644 index 0000000000000..0888be5b664cf --- /dev/null +++ b/drivers/mailbox/mailbox.c @@ -0,0 +1,477 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mailbox_internal.h" + +static LIST_HEAD(ipc_cons); +static DEFINE_MUTEX(con_mutex); + +static request_token_t _add_to_rbuf(struct ipc_chan *chan, void *mssg) +{ + request_token_t idx; + unsigned long flags; + + spin_lock_irqsave(&chan->lock, flags); + + /* See if there is any space left */ + if (chan->msg_count == MBOX_TX_QUEUE_LEN) { + spin_unlock_irqrestore(&chan->lock, flags); + return 0; + } + + idx = chan->msg_free; + chan->msg_data[idx] = mssg; + chan->msg_count++; + + if (idx == MBOX_TX_QUEUE_LEN - 1) + chan->msg_free = 0; + else + chan->msg_free++; + + spin_unlock_irqrestore(&chan->lock, flags); + + /* To aid debugging, we return 'idx+1' instead of 1 */ + return idx + 1; +} + +static void _msg_submit(struct ipc_chan *chan) +{ + struct ipc_link *link = chan->link; + unsigned count, idx; + unsigned long flags; + void *data; + int err; + + spin_lock_irqsave(&chan->lock, flags); + + if (!chan->msg_count || chan->active_req) { + spin_unlock_irqrestore(&chan->lock, flags); + return; + } + + count = chan->msg_count; + idx = chan->msg_free; + if (idx >= count) + idx -= count; + else + idx += MBOX_TX_QUEUE_LEN - count; + + data = chan->msg_data[idx]; + + /* Try to submit a message to the IPC controller */ + err = chan->link_ops->send_data(link, data); + if (!err) { + chan->active_req = data; + chan->msg_count--; + } + + spin_unlock_irqrestore(&chan->lock, flags); +} + +static void tx_tick(struct ipc_chan *chan, enum xfer_result r) +{ + unsigned long flags; + void *mssg; + + spin_lock_irqsave(&chan->lock, flags); + mssg = chan->active_req; + chan->active_req = NULL; + spin_unlock_irqrestore(&chan->lock, flags); + + /* Submit next message */ + _msg_submit(chan); + + /* Notify the client */ + if (chan->tx_block) + complete(&chan->tx_complete); + else if (mssg && chan->txcb) + chan->txcb(chan->cl_id, mssg, r); +} + +static void poll_txdone(unsigned long data) +{ + struct ipc_con *con = (struct ipc_con *)data; + bool txdone, resched = false; + struct ipc_chan *chan; + + list_for_each_entry(chan, &con->channels, node) { + if (chan->active_req && chan->assigned) { + resched = true; + txdone = chan->link_ops->is_ready(chan->link); + if (txdone) + tx_tick(chan, XFER_OK); + } + } + + if (resched) + mod_timer(&con->poll, + jiffies + msecs_to_jiffies(con->period)); +} + +/* + * After 'startup' and before 'shutdown', the IPC controller driver + * notifies the API of data received over the link. + * The controller driver should make sure the 'RTR' is de-asserted since + * reception of the packet and until after this call returns. + * This call could be made from atomic context. + */ +void ipc_link_received_data(struct ipc_link *link, void *mssg) +{ + struct ipc_chan *chan = (struct ipc_chan *)link->api_priv; + + /* No buffering the received data */ + if (chan->rxcb) + chan->rxcb(chan->cl_id, mssg); +} +EXPORT_SYMBOL(ipc_link_received_data); + +/* + * The IPC controller driver notifies the API that the remote has + * asserted RTR and it could now send another message on the link. + */ +void ipc_link_txdone(struct ipc_link *link, enum xfer_result r) +{ + struct ipc_chan *chan = (struct ipc_chan *)link->api_priv; + + if (unlikely(!(chan->txdone_method & TXDONE_BY_IRQ))) { + pr_err("Controller can't run the TX ticker\n"); + return; + } + + tx_tick(chan, r); +} +EXPORT_SYMBOL(ipc_link_txdone); + +/* + * The client/protocol had received some 'ACK' packet and it notifies + * the API that the last packet was sent successfully. This only works + * if the controller doesn't get IRQ for TX done. + */ +void ipc_client_txdone(void *channel, enum xfer_result r) +{ + struct ipc_chan *chan = (struct ipc_chan *)channel; + bool txdone = true; + + if (unlikely(!(chan->txdone_method & TXDONE_BY_ACK))) { + pr_err("Client can't run the TX ticker\n"); + return; + } + + if (chan->txdone_method & TXDONE_BY_POLL) + txdone = chan->link_ops->is_ready(chan->link); + + if (txdone) + tx_tick(chan, r); +} +EXPORT_SYMBOL(ipc_client_txdone); + +/* + * Called by a client to "put data on the h/w channel" so that if + * everything else is fine we don't need to do anything more locally + * for the remote to receive the data intact. + * In reality, the remote may receive it intact, corrupted or not at all. + * This could be called from atomic context as it simply + * queues the data and returns a token (request_token_t) + * against the request. + * The client is later notified of successful transmission of + * data over the channel via the 'txcb'. The client could in + * turn queue more messages from txcb. + */ +request_token_t ipc_send_message(void *channel, void *mssg) +{ + struct ipc_chan *chan = (struct ipc_chan *)channel; + request_token_t t; + + if (!chan || !chan->assigned) + return 0; + + if (chan->tx_block) + init_completion(&chan->tx_complete); + + t = _add_to_rbuf(chan, mssg); + if (!t) + pr_err("Try increasing MBOX_TX_QUEUE_LEN\n"); + + _msg_submit(chan); + + if (chan->txdone_method == TXDONE_BY_POLL) + poll_txdone((unsigned long)chan->con); + + if (chan->tx_block && chan->active_req) { + int ret; + ret = wait_for_completion_timeout(&chan->tx_complete, + chan->tx_tout); + if (ret == 0) { + t = 0; + tx_tick(chan, XFER_ERR); + } + } + + return t; +} +EXPORT_SYMBOL(ipc_send_message); + +/* + * A client driver asks for exclusive use of a channel/mailbox. + * If assigned, the channel has to be 'freed' before it could + * be assigned to some other client. + * After assignment, any packet received on this channel will be + * handed over to the client via the 'rxcb' callback. + * The 'txcb' callback is used to notify client upon sending the + * packet over the channel, which may or may not have been yet + * read by the remote processor. + */ +void *ipc_request_channel(struct ipc_client *cl) +{ + struct ipc_chan *chan; + struct ipc_con *con; + unsigned long flags; + char *con_name; + int len, ret; + + con_name = cl->chan_name; + len = strcspn(cl->chan_name, ":"); + + ret = 0; + mutex_lock(&con_mutex); + list_for_each_entry(con, &ipc_cons, node) + if (!strncmp(con->name, con_name, len)) { + ret = 1; + break; + } + mutex_unlock(&con_mutex); + + if (!ret) { + pr_err("Controller(%s) not found!\n", cl->chan_name); + return NULL; + } + + ret = 0; + list_for_each_entry(chan, &con->channels, node) { + if (!strcmp(con_name + len + 1, chan->name) + && !chan->assigned) { + spin_lock_irqsave(&chan->lock, flags); + chan->msg_free = 0; + chan->msg_count = 0; + chan->active_req = NULL; + chan->rxcb = cl->rxcb; + chan->txcb = cl->txcb; + chan->cl_id = cl->cl_id; + chan->assigned = true; + chan->tx_block = cl->tx_block; + if (!cl->tx_tout) + chan->tx_tout = ~0; + else + chan->tx_tout = msecs_to_jiffies(cl->tx_tout); + if (chan->txdone_method == TXDONE_BY_POLL + && cl->knows_txdone) + chan->txdone_method |= TXDONE_BY_ACK; + spin_unlock_irqrestore(&chan->lock, flags); + ret = 1; + break; + } + } + + if (!ret) { + pr_err("Unable to assign mailbox(%s)\n", cl->chan_name); + return NULL; + } + + ret = chan->link_ops->startup(chan->link, cl->link_data); + if (ret) { + pr_err("Unable to startup the link\n"); + ipc_free_channel((void *)chan); + return NULL; + } + + return (void *)chan; +} +EXPORT_SYMBOL(ipc_request_channel); + +/* Drop any messages queued and release the channel */ +void ipc_free_channel(void *ch) +{ + struct ipc_chan *chan = (struct ipc_chan *)ch; + unsigned long flags; + + if (!chan || !chan->assigned) + return; + + chan->link_ops->shutdown(chan->link); + + /* The queued TX requests are simply aborted, no callbacks are made */ + spin_lock_irqsave(&chan->lock, flags); + chan->assigned = false; + chan->active_req = NULL; + if (chan->txdone_method == (TXDONE_BY_POLL | TXDONE_BY_ACK)) + chan->txdone_method = TXDONE_BY_POLL; + spin_unlock_irqrestore(&chan->lock, flags); + + blocking_notifier_call_chain(&chan->avail, 0, NULL); +} +EXPORT_SYMBOL(ipc_free_channel); + +static struct ipc_chan *name_to_chan(const char *name) +{ + struct ipc_chan *chan = NULL; + struct ipc_con *con; + int len, found = 0; + + len = strcspn(name, ":"); + + mutex_lock(&con_mutex); + + list_for_each_entry(con, &ipc_cons, node) { + if (!strncmp(con->name, name, len)) { + list_for_each_entry(chan, &con->channels, node) { + if (!strcmp(name + len + 1, chan->name)) { + found = 1; + goto done; + } + } + } + } +done: + mutex_unlock(&con_mutex); + + if (!found) + return NULL; + + return chan; +} + +int ipc_notify_chan_register(const char *name, struct notifier_block *nb) +{ + struct ipc_chan *chan = name_to_chan(name); + + if (chan && nb) + return blocking_notifier_chain_register(&chan->avail, nb); + + return -EINVAL; +} +EXPORT_SYMBOL(ipc_notify_chan_register); + +void ipc_notify_chan_unregister(const char *name, struct notifier_block *nb) +{ + struct ipc_chan *chan = name_to_chan(name); + + if (chan && nb) + blocking_notifier_chain_unregister(&chan->avail, nb); +} +EXPORT_SYMBOL(ipc_notify_chan_unregister); + +/* + * Call for IPC controller drivers to register a controller, adding + * its channels/mailboxes to the global pool. + */ +int ipc_links_register(struct ipc_controller *ipc) +{ + int i, num_links, txdone; + struct ipc_chan *chan; + struct ipc_con *con; + + /* Sanity check */ + if (!ipc || !ipc->ops) + return -EINVAL; + + for (i = 0; ipc->links[i]; i++) + ; + if (!i) + return -EINVAL; + num_links = i; + + mutex_lock(&con_mutex); + /* Check if already populated */ + list_for_each_entry(con, &ipc_cons, node) + if (!strcmp(ipc->controller_name, con->name)) { + mutex_unlock(&con_mutex); + return -EINVAL; + } + mutex_unlock(&con_mutex); + + con = kzalloc(sizeof(*con) + sizeof(*chan) * num_links, GFP_KERNEL); + if (!con) + return -ENOMEM; + + INIT_LIST_HEAD(&con->channels); + snprintf(con->name, 16, "%s", ipc->controller_name); + + if (ipc->txdone_irq) + txdone = TXDONE_BY_IRQ; + else if (ipc->txdone_poll) + txdone = TXDONE_BY_POLL; + else /* It has to be ACK then */ + txdone = TXDONE_BY_ACK; + + if (txdone == TXDONE_BY_POLL) { + con->period = ipc->txpoll_period; + con->poll.function = &poll_txdone; + con->poll.data = (unsigned long)con; + init_timer(&con->poll); + } + + chan = (void *)con + sizeof(*con); + for (i = 0; i < num_links; i++) { + chan[i].con = con; + chan[i].assigned = false; + chan[i].link_ops = ipc->ops; + chan[i].link = ipc->links[i]; + chan[i].txdone_method = txdone; + chan[i].link->api_priv = &chan[i]; + spin_lock_init(&chan[i].lock); + BLOCKING_INIT_NOTIFIER_HEAD(&chan[i].avail); + list_add_tail(&chan[i].node, &con->channels); + snprintf(chan[i].name, 16, "%s", ipc->links[i]->link_name); + } + + mutex_lock(&con_mutex); + list_add_tail(&con->node, &ipc_cons); + mutex_unlock(&con_mutex); + + return 0; +} +EXPORT_SYMBOL(ipc_links_register); + +void ipc_links_unregister(struct ipc_controller *ipc) +{ + struct ipc_con *t, *con = NULL; + struct ipc_chan *chan; + + mutex_lock(&con_mutex); + + list_for_each_entry(t, &ipc_cons, node) + if (!strcmp(ipc->controller_name, t->name)) { + con = t; + break; + } + + if (con) + list_del(&con->node); + + mutex_unlock(&con_mutex); + + if (!con) + return; + + list_for_each_entry(chan, &con->channels, node) + ipc_free_channel((void *)chan); + + if (ipc->txdone_poll) + del_timer_sync(&con->poll); + + kfree(con); +} +EXPORT_SYMBOL(ipc_links_unregister); diff --git a/drivers/mailbox/mailbox_internal.h b/drivers/mailbox/mailbox_internal.h new file mode 100644 index 0000000000000..a39dcb7f4952f --- /dev/null +++ b/drivers/mailbox/mailbox_internal.h @@ -0,0 +1,77 @@ +/* + * mailbox: interprocessor communication module + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MAILBOX_INTERNAL_H +#define MAILBOX_INTERNAL_H + +#include +#include + +/* + * The length of circular buffer for queuing messages from a client. + * 'msg_count' tracks the number of buffered messages while 'msg_free' + * is the index where the next message would be buffered. + * We shouldn't need it too big because every transferr is interrupt + * triggered and if we have lots of data to transfer, the interrupt + * latencies are going to be the bottleneck, not the buffer length. + * Besides, ipc_send_message could be called from atomic context and + * the client could also queue another message from the notifier 'txcb' + * of the last transfer done. + * REVIST: If too many platforms see the "Try increasing MBOX_TX_QUEUE_LEN" + * print, it needs to be taken from config option or somesuch. + */ +#define MBOX_TX_QUEUE_LEN 20 + +#define TXDONE_BY_IRQ (1 << 0) /* controller has remote RTR irq */ +#define TXDONE_BY_POLL (1 << 1) /* controller can read status of last TX */ +#define TXDONE_BY_ACK (1 << 2) /* S/W ACK recevied by Client ticks the TX */ + +struct ipc_chan { + char name[16]; /* link_name */ + struct ipc_con *con; /* Parent Controller */ + unsigned txdone_method; + + /* Cached values from controller */ + struct ipc_link *link; + struct ipc_link_ops *link_ops; + + /* Cached values from client */ + void *cl_id; + void (*rxcb)(void *cl_id, void *mssg); + void (*txcb)(void *cl_id, void *mssg, enum xfer_result r); + bool tx_block; + unsigned long tx_tout; + struct completion tx_complete; + + void *active_req; + unsigned msg_count, msg_free; + void *msg_data[MBOX_TX_QUEUE_LEN]; + bool assigned; + /* Serialize access to the channel */ + spinlock_t lock; + /* Hook to add to the controller's list of channels */ + struct list_head node; + /* Notifier to all clients waiting on aquiring this channel */ + struct blocking_notifier_head avail; +}; + +/* Internal representation of a controller */ +struct ipc_con { + char name[16]; /* controller_name */ + struct list_head channels; + /* + * If the controller supports only TXDONE_BY_POLL, + * this timer polls all the links for txdone. + */ + struct timer_list poll; + unsigned period; + /* Hook to add to the global controller list */ + struct list_head node; +}; + +#endif /* MAILBOX_INTERNAL_H */ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ee9402324a23a..20363457eb413 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -516,14 +516,22 @@ config SRAM the genalloc API. It is supposed to be used for small on-chip SRAM areas found on many SoCs. -config VEXPRESS_SYSCFG - bool "Versatile Express System Configuration driver" - depends on VEXPRESS_CONFIG - default y +config ALTERA_HWMUTEX + tristate "Altera Hardware Mutex" + help + This option enables device driver support for Altera Hardware Mutex. + Say Y here if you want to use the Altera hardware mutex support. + +config ALTERA_SYSID + tristate "Altera System ID" help - ARM Ltd. Versatile Express uses specialised platform configuration - bus. System Configuration interface is one of the possible means - of generating transactions on this bus. + This enables Altera System ID soft core driver. + +config ALTERA_ILC + tristate "Altera Interrupt Latency Counter driver" + help + This enables the Interrupt Latency Counter driver for the Altera + SOCFPGA platform. source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" @@ -537,4 +545,6 @@ source "drivers/misc/vmw_vmci/Kconfig" source "drivers/misc/mic/Kconfig" source "drivers/misc/genwqe/Kconfig" source "drivers/misc/echo/Kconfig" +source "drivers/misc/fpga-bridge/Kconfig" + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d59ce1261b384..f3fbc6f7f4962 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_C2PORT) += c2port/ obj-$(CONFIG_HMC6352) += hmc6352.o obj-y += eeprom/ obj-y += cb710/ +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge/ obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o @@ -47,7 +48,10 @@ obj-y += ti-st/ obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o -obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ +obj-$(CONFIG_ALTERA_STAPL) += altera-stapl/ +obj-$(CONFIG_ALTERA_HWMUTEX) += altera_hwmutex.o +obj-$(CONFIG_ALTERA_ILC) += altera_ilc.o +obj-$(CONFIG_ALTERA_SYSID) += altera_sysid.o obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o diff --git a/drivers/misc/altera_hwmutex.c b/drivers/misc/altera_hwmutex.c new file mode 100644 index 0000000000000..3171db55c8979 --- /dev/null +++ b/drivers/misc/altera_hwmutex.c @@ -0,0 +1,321 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "altera_hwmutex" + + +static DEFINE_SPINLOCK(list_lock); /* protect mutex_list */ +static LIST_HEAD(mutex_list); + +/* Mutex Registers */ +#define MUTEX_REG 0x0 + +#define MUTEX_REG_VALUE_MASK 0xFFFF +#define MUTEX_REG_OWNER_OFFSET 16 +#define MUTEX_REG_OWNER_MASK 0xFFFF +#define MUTEX_GET_OWNER(reg) \ + ((reg >> MUTEX_REG_OWNER_OFFSET) & MUTEX_REG_OWNER_MASK) + +/** + * altera_mutex_request - Retrieves a pointer to an acquired mutex device + * structure + * @mutex_np: The pointer to mutex device node + * + * Returns a pointer to the mutex device structure associated with the + * supplied device node, or NULL if no corresponding mutex device was + * found. + */ +struct altera_mutex *altera_mutex_request(struct device_node *mutex_np) +{ + struct altera_mutex *mutex; + + spin_lock(&list_lock); + list_for_each_entry(mutex, &mutex_list, list) { + if (mutex_np == mutex->pdev->dev.of_node) { + if (!mutex->requested) { + mutex->requested = true; + spin_unlock(&list_lock); + return mutex; + } else { + pr_info("Mutex device is in use.\n"); + spin_unlock(&list_lock); + return NULL; + } + } + } + spin_unlock(&list_lock); + pr_info("Mutex device not found!\n"); + return NULL; +} +EXPORT_SYMBOL(altera_mutex_request); + +/** + * altera_mutex_free - Free the mutex + * @mutex: the mutex + * + * Return 0 if success. Otherwise, returns non-zero. + */ +int altera_mutex_free(struct altera_mutex *mutex) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + spin_lock(&list_lock); + mutex->requested = false; + spin_unlock(&list_lock); + + return 0; +} +EXPORT_SYMBOL(altera_mutex_free); + +static int __mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + u32 read; + int ret = 0; + u32 data = (owner << MUTEX_REG_OWNER_OFFSET) | value; + + mutex_lock(&mutex->lock); + __raw_writel(data, mutex->regs + MUTEX_REG); + read = __raw_readl(mutex->regs + MUTEX_REG); + if (read != data) + ret = -1; + + mutex_unlock(&mutex->lock); + return ret; +} + +/** + * altera_mutex_lock - Acquires a hardware mutex, wait until it can get it. + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_lock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + while (__mutex_trylock(mutex, owner, value) != 0) + ; + + return 0; +} +EXPORT_SYMBOL(altera_mutex_lock); + +/** + * altera_mutex_trylock - Tries once to lock the hardware mutex and returns + * immediately + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + return __mutex_trylock(mutex, owner, value); +} +EXPORT_SYMBOL(altera_mutex_trylock); + +/** + * altera_mutex_unlock - Unlock a mutex that has been locked by this owner + * previously that was locked on the + * altera_mutex_lock. Upon release, the value stored + * in the mutex is set to zero. + * @mutex: the mutex to be released + * @owner: Owner ID + * + * Returns 0 if mutex was successfully unlocked. Otherwise, returns + * non-zero. + * + * This function is not ISR callable. + */ +int altera_mutex_unlock(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + + if (!mutex || !mutex->requested) + return -EINVAL; + + mutex_lock(&mutex->lock); + + __raw_writel(owner << MUTEX_REG_OWNER_OFFSET, + mutex->regs + MUTEX_REG); + + reg = __raw_readl(mutex->regs + MUTEX_REG); + if (reg & MUTEX_REG_VALUE_MASK) { + /* Unlock failed */ + dev_dbg(&mutex->pdev->dev, + "Unlock mutex failed, owner %d and expected owner %d\n", + owner, MUTEX_GET_OWNER(reg)); + mutex_unlock(&mutex->lock); + return -EINVAL; + } + + mutex_unlock(&mutex->lock); + return 0; +} +EXPORT_SYMBOL(altera_mutex_unlock); + +/** + * altera_mutex_owned - Determines if this owner owns the mutex + * @mutex: the mutex to be queried + * @owner: Owner ID + * + * Returns 1 if the owner owns the mutex. Otherwise, returns zero. + */ +int altera_mutex_owned(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + u16 actual_owner; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + actual_owner = MUTEX_GET_OWNER(reg); + if (actual_owner == owner) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_owned); + +/** + * altera_mutex_is_locked - Determines if the mutex is locked + * @mutex: the mutex to be queried + * + * Returns 1 if the mutex is locked, 0 if unlocked. + */ +int altera_mutex_is_locked(struct altera_mutex *mutex) +{ + u32 reg; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + reg &= MUTEX_REG_VALUE_MASK; + if (reg) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_is_locked); + +static int altera_mutex_probe(struct platform_device *pdev) +{ + struct altera_mutex *mutex; + struct resource *regs; + + mutex = devm_kzalloc(&pdev->dev, sizeof(struct altera_mutex), + GFP_KERNEL); + if (!mutex) + return -ENOMEM; + + mutex->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + mutex->regs = devm_request_and_ioremap(&pdev->dev, regs); + if (!mutex->regs) + return -EADDRNOTAVAIL; + + mutex_init(&mutex->lock); + + spin_lock(&list_lock); + list_add_tail(&mutex->list, &mutex_list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, mutex); + + return 0; +} + +static int altera_mutex_remove(struct platform_device *pdev) +{ + struct altera_mutex *mutex = platform_get_drvdata(pdev); + + spin_lock(&list_lock); + if (mutex) + list_del(&mutex->list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_mutex_match[] = { + { .compatible = "altr,hwmutex-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mutex_match); + +static struct platform_driver altera_mutex_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_mutex_match), + }, + .remove = altera_mutex_remove, +}; + +static int __init altera_mutex_init(void) +{ + return platform_driver_probe(&altera_mutex_platform_driver, + altera_mutex_probe); +} + +static void __exit altera_mutex_exit(void) +{ + platform_driver_unregister(&altera_mutex_platform_driver); +} + +module_init(altera_mutex_init); +module_exit(altera_mutex_exit); + +MODULE_AUTHOR("Ley Foon Tan "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Hardware Mutex driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_ilc.c b/drivers/misc/altera_ilc.c new file mode 100644 index 0000000000000..5aa98cf752555 --- /dev/null +++ b/drivers/misc/altera_ilc.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2014 Altera Corporation. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "altera_ilc" +#define CTRL_REG 0x80 +#define FREQ_REG 0x84 +#define STP_REG 0x88 +#define VLD_REG 0x8C +#define ILC_MAX_PORTS 32 +#define ILC_FIFO_DEFAULT 32 +#define ILC_ENABLE 0x01 +#define CHAR_SIZE 10 +#define POLL_INTERVAL 1 +#define GET_PORT_COUNT(_val) ((_val & 0x7C) >> 2) +#define GET_VLD_BIT(_val, _offset) (((_val) >> _offset) & 0x1) + +struct altera_ilc { + struct platform_device *pdev; + void __iomem *regs; + unsigned int port_count; + unsigned int irq; + unsigned int channel_offset; + unsigned int interrupt_channels[ILC_MAX_PORTS]; + struct kfifo kfifos[ILC_MAX_PORTS]; + struct device_attribute dev_attr[ILC_MAX_PORTS]; + struct delayed_work ilc_work; + char sysfs[ILC_MAX_PORTS][CHAR_SIZE]; + u32 fifo_depth; +}; + +static int ilc_irq_lookup(struct altera_ilc *ilc, int irq) +{ + int i; + for (i = 0; i < ilc->port_count; i++) { + if (irq == platform_get_irq(ilc->pdev, i)) + return i; + } + return -EPERM; +} + +static ssize_t ilc_show_counter(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, i, id, fifo_len; + unsigned int fifo_buf[ILC_MAX_PORTS]; + char temp[10]; + struct altera_ilc *ilc = dev_get_drvdata(dev); + + fifo_len = 0; + ret = kstrtouint(attr->attr.name, 0, &id); + + for (i = 0; i < ilc->port_count; i++) { + if (id == (ilc->interrupt_channels[i])) { + /*Check for kfifo length*/ + fifo_len = kfifo_len(&ilc->kfifos[i]) + /sizeof(unsigned int); + if (fifo_len <= 0) { + dev_info(&ilc->pdev->dev, "Fifo for interrupt %s is empty\n", + attr->attr.name); + return 0; + } + /*Read from kfifo*/ + ret = kfifo_out(&ilc->kfifos[i], &fifo_buf, + kfifo_len(&ilc->kfifos[i])); + } + } + + for (i = 0; i < fifo_len; i++) { + sprintf(temp, "%u\n", fifo_buf[i]); + strcat(buf, temp); + } + + strcat(buf, "\0"); + + return strlen(buf); +} + +static struct attribute *altera_ilc_attrs[ILC_MAX_PORTS]; + +struct attribute_group altera_ilc_attr_group = { + .name = "ilc_data", + .attrs = altera_ilc_attrs, +}; + +static void ilc_work(struct work_struct *work) +{ + unsigned int ilc_value, ret, offset, stp_reg; + struct altera_ilc *ilc = + container_of(work, struct altera_ilc, ilc_work.work); + + offset = ilc_irq_lookup(ilc, ilc->irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return; + } + + if (GET_VLD_BIT(readl(ilc->regs + VLD_REG), offset)) { + /*Read counter register*/ + ilc_value = readl(ilc->regs + (offset) * 4); + + /*Putting value into kfifo*/ + ret = kfifo_in((&ilc->kfifos[offset]), + (unsigned int *)&ilc_value, sizeof(ilc_value)); + + /*Clearing stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((!(0x1 << offset))&stp_reg, ilc->regs + STP_REG); + + return; + } + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, msecs_to_jiffies(POLL_INTERVAL)); +} + +static irqreturn_t ilc_interrupt_handler(int irq, void *p) +{ + unsigned int offset, stp_reg; + + struct altera_ilc *ilc = (struct altera_ilc *)p; + + /*Update ILC struct*/ + ilc->irq = irq; + + dev_dbg(&ilc->pdev->dev, "Interrupt %u triggered\n", + ilc->irq); + + offset = ilc_irq_lookup(ilc, irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return IRQ_RETVAL(IRQ_NONE); + } + + /*Setting stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((0x1 << offset)|stp_reg, ilc->regs + STP_REG); + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, 0); + + return IRQ_RETVAL(IRQ_NONE); +} + +static int altera_ilc_probe(struct platform_device *pdev) +{ + struct altera_ilc *ilc; + struct resource *regs; + struct device_node *np = pdev->dev.of_node; + int ret, i; + + ilc = devm_kzalloc(&pdev->dev, sizeof(struct altera_ilc), + GFP_KERNEL); + if (!ilc) + return -ENOMEM; + + ilc->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + ilc->regs = devm_request_and_ioremap(&pdev->dev, regs); + if (!ilc->regs) + return -EADDRNOTAVAIL; + + ilc->port_count = GET_PORT_COUNT(readl(ilc->regs + CTRL_REG)); + if (ilc->port_count <= 0) { + dev_warn(&pdev->dev, "No interrupt connected to ILC\n"); + return -EPERM; + } + + /*Check for fifo depth*/ + ret = of_property_read_u32(np, "altr,sw-fifo-depth", + &(ilc->fifo_depth)); + if (ret) { + dev_warn(&pdev->dev, "Fifo depth undefined\n"); + dev_warn(&pdev->dev, "Setting fifo depth to default value (32)\n"); + ilc->fifo_depth = ILC_FIFO_DEFAULT; + } + + /*Initialize Kfifo*/ + for (i = 0; i < ilc->port_count; i++) { + ret = kfifo_alloc(&ilc->kfifos[i], (ilc->fifo_depth * + sizeof(unsigned int)), GFP_KERNEL); + if (ret) { + dev_err(&pdev->dev, "Kfifo failed to initialize\n"); + return ret; + } + } + + /*Register each of the IRQs*/ + for (i = 0; i < ilc->port_count; i++) { + ilc->interrupt_channels[i] = platform_get_irq(pdev, i); + + ret = devm_request_irq(&pdev->dev, (ilc->interrupt_channels[i]), + ilc_interrupt_handler, IRQF_SHARED, "ilc_0", + (void *)(ilc)); + + if (ret < 0) + dev_warn(&pdev->dev, "Failed to register interrupt handler"); + } + + /*Setup sysfs interface*/ + for (i = 0; (i < ilc->port_count); i++) { + sprintf(ilc->sysfs[i], "%d", (ilc->interrupt_channels[i])); + ilc->dev_attr[i].attr.name = ilc->sysfs[i]; + ilc->dev_attr[i].attr.mode = S_IRUGO; + ilc->dev_attr[i].show = ilc_show_counter; + altera_ilc_attrs[i] = &ilc->dev_attr[i].attr; + altera_ilc_attrs[i+1] = NULL; + } + ret = sysfs_create_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Initialize workqueue*/ + INIT_DELAYED_WORK(&ilc->ilc_work, ilc_work); + + /*Global enable ILC softIP*/ + writel(ILC_ENABLE, ilc->regs + CTRL_REG); + + platform_set_drvdata(pdev, ilc); + + dev_info(&pdev->dev, "Driver successfully loaded\n"); + + return 0; +} + +static int altera_ilc_remove(struct platform_device *pdev) +{ + int i; + struct altera_ilc *ilc = platform_get_drvdata(pdev); + + /*Remove sysfs interface*/ + sysfs_remove_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Free up kfifo memory*/ + for (i = 0; i < ilc->port_count; i++) + kfifo_free(&ilc->kfifos[i]); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_ilc_match[] = { + { .compatible = "altr,ilc-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_ilc_match); + +static struct platform_driver altera_ilc_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_ilc_match), + }, + .remove = altera_ilc_remove, +}; + +static int __init altera_ilc_init(void) +{ + return platform_driver_probe(&altera_ilc_platform_driver, + altera_ilc_probe); +} + +static void __exit altera_ilc_exit(void) +{ + platform_driver_unregister(&altera_ilc_platform_driver); +} + +module_init(altera_ilc_init); +module_exit(altera_ilc_exit); + +MODULE_AUTHOR("Chee Nouk Phoon "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Interrupt Latency Counter Driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_sysid.c b/drivers/misc/altera_sysid.c new file mode 100644 index 0000000000000..78a722af6f397 --- /dev/null +++ b/drivers/misc/altera_sysid.c @@ -0,0 +1,141 @@ +/* + * Copyright Altera Corporation (C) 2013. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * Credit: + * Walter Goossens + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "altera_sysid" + +struct altera_sysid { + void __iomem *regs; +}; + +/* System ID Registers*/ +#define SYSID_REG_ID (0x0) +#define SYSID_REG_TIMESTAMP (0x4) + +static ssize_t altera_sysid_show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct altera_sysid *sysid = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", readl(sysid->regs + SYSID_REG_ID)); +} + +static ssize_t altera_sysid_show_timestamp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int reg; + struct tm timestamp; + struct altera_sysid *sysid = dev_get_drvdata(dev); + + reg = readl(sysid->regs + SYSID_REG_TIMESTAMP); + + time_to_tm(reg, 0, ×tamp); + + return sprintf(buf, "%u (%u-%u-%u %u:%u:%u UTC)\n", reg, + (unsigned int)(timestamp.tm_year + 1900), + timestamp.tm_mon + 1, timestamp.tm_mday, timestamp.tm_hour, + timestamp.tm_min, timestamp.tm_sec); +} + +static DEVICE_ATTR(id, S_IRUGO, altera_sysid_show_id, NULL); +static DEVICE_ATTR(timestamp, S_IRUGO, altera_sysid_show_timestamp, NULL); + +static struct attribute *altera_sysid_attrs[] = { + &dev_attr_id.attr, + &dev_attr_timestamp.attr, + NULL, +}; + +struct attribute_group altera_sysid_attr_group = { + .name = "sysid", + .attrs = altera_sysid_attrs, +}; + +static int altera_sysid_probe(struct platform_device *pdev) +{ + struct altera_sysid *sysid; + struct resource *regs; + + sysid = devm_kzalloc(&pdev->dev, sizeof(struct altera_sysid), + GFP_KERNEL); + if (!sysid) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + sysid->regs = devm_request_and_ioremap(&pdev->dev, regs); + if (!sysid->regs) + return -ENOMEM; + + platform_set_drvdata(pdev, sysid); + + return sysfs_create_group(&pdev->dev.kobj, &altera_sysid_attr_group); +} + +static int altera_sysid_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &altera_sysid_attr_group); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_sysid_match[] = { + { .compatible = "altr,sysid-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_sysid_match); + +static struct platform_driver altera_sysid_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_sysid_match), + }, + .remove = altera_sysid_remove, +}; + +static int __init altera_sysid_init(void) +{ + return platform_driver_probe(&altera_sysid_platform_driver, + altera_sysid_probe); +} + +static void __exit altera_sysid_exit(void) +{ + platform_driver_unregister(&altera_sysid_platform_driver); +} + +module_init(altera_sysid_init); +module_exit(altera_sysid_exit); + +MODULE_AUTHOR("Ley Foon Tan "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera System ID driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/fpga-bridge/Kconfig b/drivers/misc/fpga-bridge/Kconfig new file mode 100644 index 0000000000000..725474e9436e7 --- /dev/null +++ b/drivers/misc/fpga-bridge/Kconfig @@ -0,0 +1,20 @@ +# +# FPGA bridge manager configuration +# + +menu "FPGA Bridges" + +config FPGA_BRIDGE + tristate "FPGA Bridge Drivers" + depends on OF + help + Say Y here if you want to support bridges connected between host + processors and FPGAs or between FPGAs. + +config ALTERA_SOCFPGA_BRIDGE + tristate "Altera SoCFPGA Bridges" + depends on FPGA_BRIDGE + help + Say Y to enable drivers for FPGA bridges for Altera socfpga + devices. +endmenu diff --git a/drivers/misc/fpga-bridge/Makefile b/drivers/misc/fpga-bridge/Makefile new file mode 100644 index 0000000000000..3700f8a977d43 --- /dev/null +++ b/drivers/misc/fpga-bridge/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the fpga-bridge drivers +# + +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o +obj-$(CONFIG_ALTERA_SOCFPGA_BRIDGE) += altera-fpga2sdram.o altera-hps2fpga.o \ No newline at end of file diff --git a/drivers/misc/fpga-bridge/altera-fpga2sdram.c b/drivers/misc/fpga-bridge/altera-fpga2sdram.c new file mode 100644 index 0000000000000..d12c363414e00 --- /dev/null +++ b/drivers/misc/fpga-bridge/altera-fpga2sdram.c @@ -0,0 +1,232 @@ +/* + * FPGA to sdram Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fpga-bridge.h" + +#define ALT_SDR_CTL_FPGAPORTRST_OFST 0x80 +#define ALT_SDR_CTL_FPGAPORTRST_PORTRSTN_MSK 0x00003fff +#define ALT_SDR_CTL_FPGAPORTRST_RD_SHIFT 0 +#define ALT_SDR_CTL_FPGAPORTRST_WR_SHIFT 4 +#define ALT_SDR_CTL_FPGAPORTRST_CTRL_SHIFT 8 + +#define FOUR_BIT_MASK 0xf +#define SIX_BIT_MASK 0x3f + +static struct of_device_id altera_fpga_of_match[]; + +struct alt_fpga2sdram_data { + char name[48]; + struct platform_device *pdev; + struct device_node *np; + struct regmap *sdrctl; + int mask; +}; + +static atomic_t instances; + +static int alt_fpga2sdram_enable_show(struct fpga_bridge *bridge) +{ + struct alt_fpga2sdram_data *priv = bridge->priv; + int value; + + regmap_read(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, &value); + + return ((value & priv->mask) == priv->mask); +} + +static inline void _alt_fpga2sdram_enable_set(struct alt_fpga2sdram_data *priv, + bool enable) +{ + int value; + + if (enable) + value = priv->mask; + else + value = 0; + + regmap_update_bits(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, + priv->mask, value); +} +static void alt_fpga2sdram_enable_set(struct fpga_bridge *bridge, bool enable) +{ + _alt_fpga2sdram_enable_set(bridge->priv, enable); +} + +struct prop_map { + char *prop_name; + uint32_t *prop_value; + uint32_t prop_max; +}; +static int alt_fpga2sdram_get_mask(struct alt_fpga2sdram_data *priv) +{ + int i; + uint32_t read, write, cmd; + struct prop_map map[] = { + {"read-ports-mask", &read, FOUR_BIT_MASK}, + {"write-ports-mask", &write, FOUR_BIT_MASK}, + {"cmd-ports-mask", &cmd, SIX_BIT_MASK}, + }; + for (i = 0; i < ARRAY_SIZE(map); i++) { + if (of_property_read_u32(priv->np, map[i].prop_name, + map[i].prop_value)) { + dev_err(&priv->pdev->dev, + "failed to find property, %s\n", + map[i].prop_name); + return -EINVAL; + } else if (*map[i].prop_value > map[i].prop_max) { + dev_err(&priv->pdev->dev, + "%s value 0x%x > than max 0x%x\n", + map[i].prop_name, + *map[i].prop_value, + map[i].prop_max); + return -EINVAL; + } + } + + priv->mask = + (read << ALT_SDR_CTL_FPGAPORTRST_RD_SHIFT) | + (write << ALT_SDR_CTL_FPGAPORTRST_WR_SHIFT) | + (cmd << ALT_SDR_CTL_FPGAPORTRST_CTRL_SHIFT); + + return 0; +} + +struct fpga_bridge_ops altera_fpga2sdram_br_ops = { + .enable_set = alt_fpga2sdram_enable_set, + .enable_show = alt_fpga2sdram_enable_show, +}; + +static struct alt_fpga2sdram_data fpga2sdram_data = { + .name = "fpga2sdram", +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct alt_fpga2sdram_data *priv; + struct alt_fpga2sdram_data *data; + uint32_t init_val; + const struct of_device_id *of_id = of_match_device(altera_fpga_of_match, + &pdev->dev); + int ret = 0; + if (atomic_inc_return(&instances) > 1) { + atomic_dec(&instances); + dev_err(&pdev->dev, + "already one instance of driver\n"); + return -ENODEV; + } + + data = (struct alt_fpga2sdram_data *)of_id->data; + WARN_ON(!data); + + priv = devm_kzalloc(&pdev->dev, sizeof(struct alt_fpga2sdram_data), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->np = pdev->dev.of_node; + priv->pdev = pdev; + priv->mask = data->mask; + strncpy(priv->name, data->name, ARRAY_SIZE(priv->name)); + + priv->sdrctl = syscon_regmap_lookup_by_compatible("altr,sdr-ctl"); + if (IS_ERR(priv->sdrctl)) { + devm_kfree(&pdev->dev, priv); + dev_err(&pdev->dev, + "regmap for altr,sdr-ctl lookup failed.\n"); + return PTR_ERR(priv->sdrctl); + } + + ret = alt_fpga2sdram_get_mask(priv); + if (ret) { + devm_kfree(&pdev->dev, priv); + return ret; + } + + ret = register_fpga_bridge(pdev, &altera_fpga2sdram_br_ops, + priv->name, priv); + + if (!ret) + return ret; + + if (of_property_read_u32(priv->np, "init-val", &init_val)) + dev_info(&priv->pdev->dev, "init-val not specified\n"); + else if (init_val > 1) + dev_warn(&priv->pdev->dev, "invalid init-val %u > 1\n", + init_val); + else { + dev_info(&priv->pdev->dev, + "%s bridge\n", + (init_val ? "enabling" : "disabling")); + + _alt_fpga2sdram_enable_set(priv, init_val); + } + + return ret; +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + remove_fpga_bridge(pdev); + atomic_dec(&instances); + return 0; +} + +static struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-fpga2sdram-bridge", .data = &fpga2sdram_data }, + {}, +}; + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver altera_fpga_driver = { + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_fpga2sdram_bridge", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +static int __init alt_fpga_bridge_init(void) +{ + atomic_set(&instances, 0); + return platform_driver_probe(&altera_fpga_driver, + alt_fpga_bridge_probe); +} + +static void __exit alt_fpga_bridge_exit(void) +{ + platform_driver_unregister(&altera_fpga_driver); +} + +arch_initcall(alt_fpga_bridge_init); +module_exit(alt_fpga_bridge_exit); + +MODULE_DESCRIPTION("Altera SoCFPGA FPGA to SDRAM Bridge"); +MODULE_AUTHOR("Alan Tull "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/fpga-bridge/altera-hps2fpga.c b/drivers/misc/fpga-bridge/altera-hps2fpga.c new file mode 100644 index 0000000000000..072a3a50ac70e --- /dev/null +++ b/drivers/misc/fpga-bridge/altera-hps2fpga.c @@ -0,0 +1,221 @@ +/* + * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fpga-bridge.h" + +#define SOCFPGA_RSTMGR_BRGMODRST 0x1c +#define ALT_RSTMGR_BRGMODRST_H2F_MSK 0x00000001 +#define ALT_RSTMGR_BRGMODRST_LWH2F_MSK 0x00000002 +#define ALT_RSTMGR_BRGMODRST_F2H_MSK 0x00000004 + +#define ALT_L3_REMAP_OFST 0x0 +#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001 +#define ALT_L3_REMAP_H2F_MSK 0x00000008 +#define ALT_L3_REMAP_LWH2F_MSK 0x00000010 + +static struct of_device_id altera_fpga_of_match[]; + +/* The L3 REMAP register is write only, so keep a cached value. */ +static unsigned int l3_remap_value; + +struct altera_hps2fpga_data { + char name[48]; + struct platform_device *pdev; + struct device_node *np; + struct regmap *rstreg; + struct regmap *l3reg; + unsigned int reset_mask; + unsigned int remap_mask; +}; + +static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge) +{ + struct altera_hps2fpga_data *priv = bridge->priv; + unsigned int value; + + regmap_read(priv->rstreg, SOCFPGA_RSTMGR_BRGMODRST, &value); + + return ((value & priv->reset_mask) == 0); +} + +static inline void _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, + bool enable) +{ + unsigned int value; + + /* bring bridge out of reset */ + if (enable) + value = 0; + else + value = priv->reset_mask; + + regmap_update_bits(priv->rstreg, SOCFPGA_RSTMGR_BRGMODRST, + priv->reset_mask, value); + + /* Allow bridge to be visible to L3 masters or not */ + if (priv->remap_mask) { + l3_remap_value |= ALT_L3_REMAP_MPUZERO_MSK; + + if (enable) + l3_remap_value |= priv->remap_mask; + else + l3_remap_value &= ~priv->remap_mask; + + regmap_write(priv->l3reg, ALT_L3_REMAP_OFST, l3_remap_value); + } +} + +static void alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable) +{ + _alt_hps2fpga_enable_set(bridge->priv, enable); +} + +struct fpga_bridge_ops altera_hps2fpga_br_ops = { + .enable_set = alt_hps2fpga_enable_set, + .enable_show = alt_hps2fpga_enable_show, +}; + +static struct altera_hps2fpga_data hps2fpga_data = { + .name = "hps2fpga", + .reset_mask = ALT_RSTMGR_BRGMODRST_H2F_MSK, + .remap_mask = ALT_L3_REMAP_H2F_MSK, +}; + +static struct altera_hps2fpga_data lwhps2fpga_data = { + .name = "lshps2fpga", + .reset_mask = ALT_RSTMGR_BRGMODRST_LWH2F_MSK, + .remap_mask = ALT_L3_REMAP_LWH2F_MSK, +}; + +static struct altera_hps2fpga_data fpga2hps_data = { + .name = "fpga2hps", + .reset_mask = ALT_RSTMGR_BRGMODRST_F2H_MSK, +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct altera_hps2fpga_data *priv; + const struct of_device_id *of_id; + uint32_t init_val; + int rc; + struct clk *clk; + + of_id = of_match_device(altera_fpga_of_match, &pdev->dev); + priv = (struct altera_hps2fpga_data *)of_id->data; + WARN_ON(!priv); + + priv->np = pdev->dev.of_node; + priv->pdev = pdev; + + priv->rstreg = syscon_regmap_lookup_by_compatible("altr,rst-mgr"); + if (IS_ERR(priv->rstreg)) { + dev_err(&priv->pdev->dev, + "regmap for altr,rst-mgr lookup failed.\n"); + return PTR_ERR(priv->rstreg); + } + + priv->l3reg = syscon_regmap_lookup_by_compatible("altr,l3regs"); + if (IS_ERR(priv->l3reg)) { + dev_err(&priv->pdev->dev, + "regmap for altr,l3regs lookup failed.\n"); + return PTR_ERR(priv->l3reg); + } + + clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "no clock specified\n"); + return PTR_ERR(clk); + } + + rc = clk_prepare_enable(clk); + if (rc) { + dev_err(&pdev->dev, "could not enable clock\n"); + return -EBUSY; + } + + rc = register_fpga_bridge(pdev, &altera_hps2fpga_br_ops, + priv->name, priv); + if (rc) + return rc; + + if (of_property_read_u32(priv->np, "init-val", &init_val)) + dev_info(&priv->pdev->dev, "init-val not specified\n"); + else if (init_val > 1) + dev_warn(&priv->pdev->dev, "invalid init-val %u > 1\n", + init_val); + else { + dev_info(&priv->pdev->dev, "%s bridge\n", + (init_val ? "enabling" : "disabling")); + + _alt_hps2fpga_enable_set(priv, init_val); + } + + return rc; +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + remove_fpga_bridge(pdev); + return 0; +} + +static struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-hps2fpga-bridge", .data = &hps2fpga_data }, + { .compatible = "altr,socfpga-lwhps2fpga-bridge", .data = &lwhps2fpga_data }, + { .compatible = "altr,socfpga-fpga2hps-bridge", .data = &fpga2hps_data }, + {}, +}; + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver altera_fpga_driver = { + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_hps2fpga_bridge", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +static int __init alt_fpga_bridge_init(void) +{ + return platform_driver_probe(&altera_fpga_driver, + alt_fpga_bridge_probe); +} + +static void __exit alt_fpga_bridge_exit(void) +{ + platform_driver_unregister(&altera_fpga_driver); +} + +arch_initcall(alt_fpga_bridge_init); +module_exit(alt_fpga_bridge_exit); + +MODULE_DESCRIPTION("Altera SoCFPGA HPS to FPGA Bridge"); +MODULE_AUTHOR("Alan Tull "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/fpga-bridge/fpga-bridge.c b/drivers/misc/fpga-bridge/fpga-bridge.c new file mode 100644 index 0000000000000..6dcbf0190b07b --- /dev/null +++ b/drivers/misc/fpga-bridge/fpga-bridge.c @@ -0,0 +1,229 @@ +/* + * fpga bridge driver + * + * Copyright (C) 2013 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include "fpga-bridge.h" + +static DEFINE_IDA(fpga_bridge_ida); +static struct class *fpga_bridge_class; + +#define FPGA_MAX_DEVICES 256 + +/* + * class attributes + */ +static ssize_t fpga_bridge_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fpga_bridge *bridge = dev_get_drvdata(dev); + int enabled; + + enabled = bridge->br_ops->enable_show(bridge); + + return sprintf(buf, "%d\n", enabled); +} + +static ssize_t fpga_bridge_enable_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fpga_bridge *bridge = dev_get_drvdata(dev); + bool enable; + + if ((count != 1) && (count != 2)) + return -EINVAL; + + if ((count == 2) && (buf[1] != '\n')) + return -EINVAL; + + if ((buf[0] != '0') && (buf[0] != '1')) + return -EINVAL; + + enable = (buf[0] == '1'); + bridge->br_ops->enable_set(bridge, enable); + + return count; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, fpga_bridge_enable_show, + fpga_bridge_enable_set); + +static struct attribute *fpga_bridge_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static const struct attribute_group fpga_bridge_group = { + .attrs = fpga_bridge_attrs, +}; + +const struct attribute_group *fpga_bridge_groups[] = { + &fpga_bridge_group, + NULL, +}; + +static int fpga_bridge_alloc_id(struct fpga_bridge *bridge, int request_nr) +{ + int nr, start; + + /* check specified minor number */ + if (request_nr >= FPGA_MAX_DEVICES) { + dev_err(bridge->parent, + "Out of device ids (%d)\n", request_nr); + return -ENODEV; + } + + /* + * If request_nr == -1, dynamically allocate number. + * If request_nr >= 0, attempt to get specific number. + */ + if (request_nr == -1) + start = 0; + else + start = request_nr; + + nr = ida_simple_get(&fpga_bridge_ida, start, FPGA_MAX_DEVICES, + GFP_KERNEL); + + /* return error code */ + if (nr < 0) + return nr; + + if ((request_nr != -1) && (request_nr != nr)) { + dev_err(bridge->parent, + "Could not get requested device number (%d)\n", nr); + ida_simple_remove(&fpga_bridge_ida, nr); + return -ENODEV; + } + + bridge->nr = nr; + + return 0; +} + +static void fpga_bridge_free_id(int nr) +{ + ida_simple_remove(&fpga_bridge_ida, nr); +} + +int register_fpga_bridge(struct platform_device *pdev, + struct fpga_bridge_ops *br_ops, char *name, void *priv) +{ + struct fpga_bridge *bridge; + const char *dt_label; + int ret; + + if (!br_ops || !br_ops->enable_set || !br_ops->enable_show) { + dev_err(&pdev->dev, + "Attempt to register without fpga_bridge_ops\n"); + return -EINVAL; + } + if (!name || (name[0] == '\0')) { + dev_err(&pdev->dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + bridge = kzalloc(sizeof(struct fpga_bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + platform_set_drvdata(pdev, bridge); + bridge->br_ops = br_ops; + bridge->np = pdev->dev.of_node; + bridge->parent = get_device(&pdev->dev); + bridge->priv = priv; + + strlcpy(bridge->name, name, sizeof(bridge->name)); + + ret = fpga_bridge_alloc_id(bridge, pdev->id); + if (ret) + goto error_kfree; + + dt_label = of_get_property(bridge->np, "label", NULL); + if (dt_label) + snprintf(bridge->label, sizeof(bridge->label), "%s", dt_label); + else + snprintf(bridge->label, sizeof(bridge->label), + "br%d", bridge->nr); + + bridge->dev = device_create(fpga_bridge_class, bridge->parent, 0, + bridge, bridge->label); + if (IS_ERR(bridge->dev)) { + ret = PTR_ERR(bridge->dev); + goto error_device; + } + + dev_info(bridge->parent, "fpga bridge [%s] registered as device %s\n", + bridge->name, bridge->label); + + return 0; + +error_device: + fpga_bridge_free_id(bridge->nr); +error_kfree: + put_device(bridge->parent); + kfree(bridge); + return ret; +} +EXPORT_SYMBOL_GPL(register_fpga_bridge); + +void remove_fpga_bridge(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + + if (bridge && bridge->br_ops && bridge->br_ops->fpga_bridge_remove) + bridge->br_ops->fpga_bridge_remove(bridge); + + platform_set_drvdata(pdev, NULL); + device_unregister(bridge->dev); + fpga_bridge_free_id(bridge->nr); + put_device(bridge->parent); + kfree(bridge); +} +EXPORT_SYMBOL_GPL(remove_fpga_bridge); + +static int __init fpga_bridge_dev_init(void) +{ + pr_info("fpga bridge driver\n"); + + fpga_bridge_class = class_create(THIS_MODULE, "fpga-bridge"); + if (IS_ERR(fpga_bridge_class)) + return PTR_ERR(fpga_bridge_class); + + fpga_bridge_class->dev_groups = fpga_bridge_groups; + + return 0; +} + +static void __exit fpga_bridge_dev_exit(void) +{ + class_destroy(fpga_bridge_class); + ida_destroy(&fpga_bridge_ida); +} + +MODULE_DESCRIPTION("FPGA Bridge Driver"); +MODULE_AUTHOR("Alan Tull "); +MODULE_LICENSE("GPL v2"); + +core_initcall_sync(fpga_bridge_dev_init); +module_exit(fpga_bridge_dev_exit); diff --git a/drivers/misc/fpga-bridge/fpga-bridge.h b/drivers/misc/fpga-bridge/fpga-bridge.h new file mode 100644 index 0000000000000..58fba4d452c18 --- /dev/null +++ b/drivers/misc/fpga-bridge/fpga-bridge.h @@ -0,0 +1,51 @@ +#include +#include +#include + +#ifndef _LINUX_FPGA_BRIDGE_H +#define _LINUX_FPGA_BRIDGE_H + +struct fpga_bridge; + +/*---------------------------------------------------------------------------*/ + +/* + * fpga_bridge_ops are the low level functions implemented by a specific + * fpga bridge driver. + */ +struct fpga_bridge_ops { + /* Returns the FPGA bridge's status */ + int (*enable_show)(struct fpga_bridge *bridge); + + /* Enable a FPGA bridge */ + void (*enable_set)(struct fpga_bridge *bridge, bool enable); + + /* Set FPGA into a specific state during driver remove */ + void (*fpga_bridge_remove)(struct fpga_bridge *bridge); +}; + +struct fpga_bridge { + struct device_node *np; + struct device *parent; + struct device *dev; + struct cdev cdev; + + int nr; + char name[48]; + char label[48]; + unsigned long flags; + struct fpga_bridge_ops *br_ops; + + void *priv; +}; + +#if defined(CONFIG_FPGA_BRIDGE) || defined(CONFIG_FPGA_BRIDGE_MODULE) + +extern int register_fpga_bridge(struct platform_device *pdev, + struct fpga_bridge_ops *br_ops, + char *name, void *priv); + +extern void remove_fpga_bridge(struct platform_device *pdev); + +#endif /* CONFIG_FPGA_BRIDGE */ +#endif /* _LINUX_FPGA_BRIDGE_H */ diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 1ac227c603b7e..3bb2decf7eaef 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -42,8 +42,8 @@ /* Common flag combinations */ #define DW_MCI_DATA_ERROR_FLAGS (SDMMC_INT_DRTO | SDMMC_INT_DCRC | \ - SDMMC_INT_HTO | SDMMC_INT_SBE | \ - SDMMC_INT_EBE) + SDMMC_INT_HTO | SDMMC_INT_FRUN | \ + SDMMC_INT_SBE | SDMMC_INT_EBE) #define DW_MCI_CMD_ERROR_FLAGS (SDMMC_INT_RTO | SDMMC_INT_RCRC | \ SDMMC_INT_RESP_ERR) #define DW_MCI_ERROR_FLAGS (DW_MCI_DATA_ERROR_FLAGS | \ @@ -1302,7 +1302,8 @@ static void dw_mci_tasklet_func(unsigned long priv) if (test_and_clear_bit(EVENT_DATA_ERROR, &host->pending_events)) { dw_mci_stop_dma(host); - send_stop_abort(host, data); + if (data->stop) + send_stop_abort(host, data); state = STATE_DATA_ERROR; break; } @@ -1324,6 +1325,11 @@ static void dw_mci_tasklet_func(unsigned long priv) set_bit(EVENT_DATA_COMPLETE, &host->completed_events); err = dw_mci_data_complete(host, data); + if (!data->stop) { + dw_mci_request_end(host, host->mrq); + goto unlock; + } + if (!err) { if (!data->stop || mrq->sbc) { if (mrq->sbc && data->stop) @@ -1872,6 +1878,9 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id) queue_work(host->card_workqueue, &host->card_work); } + if (pending & SDMMC_INT_HLE) + mci_writel(host, RINTSTS, SDMMC_INT_HLE); + /* Handle SDIO Interrupts */ for (i = 0; i < host->num_slots; i++) { struct dw_mci_slot *slot = host->slot[i]; diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index ed7e0a1bed3ce..02c9f35a46fa2 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -227,6 +227,8 @@ static int m25p_probe(struct spi_device *spi) if (ret) return ret; + spi->addr_width = nor->addr_width; + data = dev_get_platdata(&spi->dev); ppdata.of_node = spi->dev.of_node; @@ -244,6 +246,11 @@ static int m25p_remove(struct spi_device *spi) return mtd_device_unregister(&flash->mtd); } +static void m25p_shutdown(struct spi_device *spi) +{ + struct m25p *flash = spi_get_drvdata(spi); + flash->spi_nor.shutdown(&flash->spi_nor); +} static struct spi_driver m25p80_driver = { .driver = { @@ -253,6 +260,7 @@ static struct spi_driver m25p80_driver = { .id_table = spi_nor_ids, .probe = m25p_probe, .remove = m25p_remove, + .shutdown = m25p_shutdown, /* REVISIT: many of these chips have deep power-down modes, which * should clearly be entered on suspend() to minimize power use. diff --git a/drivers/mtd/nand/denali.c b/drivers/mtd/nand/denali.c index 9f2012a3e7643..bcda82655370f 100644 --- a/drivers/mtd/nand/denali.c +++ b/drivers/mtd/nand/denali.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "denali.h" @@ -52,7 +53,12 @@ MODULE_PARM_DESC(onfi_timing_mode, "Overrides default ONFI setting." INTR_STATUS__TIME_OUT | \ INTR_STATUS__ERASE_FAIL | \ INTR_STATUS__RST_COMP | \ - INTR_STATUS__ERASE_COMP) + INTR_STATUS__ERASE_COMP | \ + INTR_STATUS__ECC_UNCOR_ERR) +/* And here we use a variable for interrupt mask, bcs we want to + * change the irq mask during init. That is, we want to enable R/B + * interrupt during init, but not at other times */ +static uint32_t denali_irq_mask = DENALI_IRQ_ALL; /* indicates whether or not the internal value for the flash bank is * valid or not */ @@ -473,7 +479,7 @@ static void detect_partition_feature(struct denali_nand_info *denali) static uint16_t denali_nand_timing_set(struct denali_nand_info *denali) { uint16_t status = PASS; - uint32_t id_bytes[5], addr; + uint32_t id_bytes[8], addr; uint8_t i, maf_id, device_id; dev_dbg(denali->dev, @@ -488,7 +494,7 @@ static uint16_t denali_nand_timing_set(struct denali_nand_info *denali) addr = (uint32_t)MODE_11 | BANK(denali->flash_bank); index_addr(denali, (uint32_t)addr | 0, 0x90); index_addr(denali, (uint32_t)addr | 1, 0); - for (i = 0; i < 5; i++) + for (i = 0; i < 8; i++) index_addr_read_data(denali, addr | 2, &id_bytes[i]); maf_id = id_bytes[0]; device_id = id_bytes[1]; @@ -560,7 +566,7 @@ static void denali_irq_init(struct denali_nand_info *denali) /* Disable global interrupts */ denali_set_intr_modes(denali, false); - int_mask = DENALI_IRQ_ALL; + int_mask = denali_irq_mask; /* Clear all status bits */ for (i = 0; i < denali->max_banks; ++i) @@ -589,7 +595,7 @@ static void denali_irq_enable(struct denali_nand_info *denali, */ static inline uint32_t denali_irq_detected(struct denali_nand_info *denali) { - return read_interrupt_status(denali) & DENALI_IRQ_ALL; + return read_interrupt_status(denali) & denali_irq_mask; } /* Interrupts are cleared by writing a 1 to the appropriate status bit */ @@ -852,6 +858,16 @@ static int write_oob_data(struct mtd_info *mtd, uint8_t *buf, int page) dev_err(denali->dev, "OOB write failed\n"); status = -EIO; } + + /* set the device back to MAIN_ACCESS */ + { + uint32_t addr; + uint32_t cmd; + addr = BANK(denali->flash_bank) | denali->page; + cmd = MODE_10 | addr; + index_addr(denali, (uint32_t)cmd, MAIN_ACCESS); + } + } else { dev_err(denali->dev, "unable to send pipeline command\n"); status = -EIO; @@ -919,7 +935,12 @@ static bool handle_ecc(struct denali_nand_info *denali, uint8_t *buf, bool check_erased_page = false; unsigned int bitflips = 0; - if (irq_status & INTR_STATUS__ECC_ERR) { + if (denali->have_hw_ecc_fixup && + (irq_status & INTR_STATUS__ECC_UNCOR_ERR)) { + clear_interrupts(denali); + denali_set_intr_modes(denali, true); + check_erased_page = true; + } else if (irq_status & INTR_STATUS__ECC_ERR) { /* read the ECC errors. we'll ignore them for now */ uint32_t err_address = 0, err_correction_info = 0; uint32_t err_byte = 0, err_sector = 0, err_device = 0; @@ -1115,15 +1136,16 @@ static int denali_read_oob(struct mtd_info *mtd, struct nand_chip *chip, static int denali_read_page(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { - unsigned int max_bitflips; + unsigned int max_bitflips = 0; struct denali_nand_info *denali = mtd_to_denali(mtd); dma_addr_t addr = denali->buf.dma_buf; size_t size = denali->mtd.writesize + denali->mtd.oobsize; uint32_t irq_status = 0; - uint32_t irq_mask = INTR_STATUS__ECC_TRANSACTION_DONE | - INTR_STATUS__ECC_ERR; + uint32_t irq_mask = denali->have_hw_ecc_fixup ? + (INTR_STATUS__DMA_CMD_COMP) : + (INTR_STATUS__ECC_TRANSACTION_DONE | INTR_STATUS__ECC_ERR); bool check_erased_page = false; if (page != denali->page) { @@ -1152,14 +1174,24 @@ static int denali_read_page(struct mtd_info *mtd, struct nand_chip *chip, denali_enable_dma(denali, false); if (check_erased_page) { - read_oob_data(&denali->mtd, chip->oob_poi, denali->page); - - /* check ECC failures that may have occurred on erased pages */ - if (check_erased_page) { + if (denali->have_hw_ecc_fixup) { + /* When we have hw ecc fixup, don't check oob. + * That code below looks jacked up anyway. I mean, + * look at it, wtf? */ if (!is_erased(buf, denali->mtd.writesize)) denali->mtd.ecc_stats.failed++; - if (!is_erased(buf, denali->mtd.oobsize)) - denali->mtd.ecc_stats.failed++; + } else { + read_oob_data(&denali->mtd, chip->oob_poi, + denali->page); + + /* check ECC failures that may have occurred on + * erased pages */ + if (check_erased_page) { + if (!is_erased(buf, denali->mtd.writesize)) + denali->mtd.ecc_stats.failed++; + if (!is_erased(buf, denali->mtd.oobsize)) + denali->mtd.ecc_stats.failed++; + } } } return max_bitflips; @@ -1215,6 +1247,13 @@ static uint8_t denali_read_byte(struct mtd_info *mtd) return result; } +static void denali_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + for (i = 0; i < len; i++) + buf[i] = denali_read_byte(mtd); +} + static void denali_select_chip(struct mtd_info *mtd, int chip) { struct denali_nand_info *denali = mtd_to_denali(mtd); @@ -1258,6 +1297,8 @@ static void denali_cmdfunc(struct mtd_info *mtd, unsigned int cmd, int col, { struct denali_nand_info *denali = mtd_to_denali(mtd); uint32_t addr, id; + uint32_t pages_per_block; + uint32_t block; int i; switch (cmd) { @@ -1267,16 +1308,46 @@ static void denali_cmdfunc(struct mtd_info *mtd, unsigned int cmd, int col, read_status(denali); break; case NAND_CMD_READID: - case NAND_CMD_PARAM: reset_buf(denali); /*sometimes ManufactureId read from register is not right * e.g. some of Micron MT29F32G08QAA MLC NAND chips * So here we send READID cmd to NAND insteand * */ addr = (uint32_t)MODE_11 | BANK(denali->flash_bank); - index_addr(denali, (uint32_t)addr | 0, 0x90); - index_addr(denali, (uint32_t)addr | 1, 0); - for (i = 0; i < 5; i++) { + index_addr(denali, (uint32_t)addr | 0, cmd); + index_addr(denali, (uint32_t)addr | 1, col & 0xFF); + for (i = 0; i < 8; i++) { + index_addr_read_data(denali, + (uint32_t)addr | 2, + &id); + write_byte_to_buf(denali, id); + } + break; + case NAND_CMD_PARAM: + reset_buf(denali); + + /* turn on R/B interrupt */ + denali_set_intr_modes(denali, false); + denali_irq_mask = DENALI_IRQ_ALL | INTR_STATUS__INT_ACT; + clear_interrupts(denali); + denali_irq_enable(denali, denali_irq_mask); + denali_set_intr_modes(denali, true); + + addr = (uint32_t)MODE_11 | BANK(denali->flash_bank); + index_addr(denali, (uint32_t)addr | 0, cmd); + index_addr(denali, (uint32_t)addr | 1, col & 0xFF); + /* Wait tR time... */ + udelay(25); + /* And then wait for R/B interrupt */ + wait_for_irq(denali, INTR_STATUS__INT_ACT); + + /* turn off R/B interrupt now */ + denali_irq_mask = DENALI_IRQ_ALL; + denali_set_intr_modes(denali, false); + denali_irq_enable(denali, denali_irq_mask); + denali_set_intr_modes(denali, true); + + for (i = 0; i < 256; i++) { index_addr_read_data(denali, (uint32_t)addr | 2, &id); @@ -1293,6 +1364,18 @@ static void denali_cmdfunc(struct mtd_info *mtd, unsigned int cmd, int col, case NAND_CMD_READOOB: /* TODO: Read OOB data */ break; + case NAND_CMD_UNLOCK1: + pages_per_block = mtd->erasesize / mtd->writesize; + block = page / pages_per_block; + addr = (uint32_t)MODE_10 | (block * pages_per_block); + index_addr(denali, addr, 0x10); + break; + case NAND_CMD_UNLOCK2: + pages_per_block = mtd->erasesize / mtd->writesize; + block = (page+pages_per_block-1) / pages_per_block; + addr = (uint32_t)MODE_10 | (block * pages_per_block); + index_addr(denali, addr, 0x11); + break; default: pr_err(": unsupported command received 0x%x\n", cmd); break; @@ -1368,29 +1451,6 @@ static struct nand_ecclayout nand_15bit_oob = { .eccbytes = 26, }; -static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; -static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; - -static struct nand_bbt_descr bbt_main_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE - | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, - .offs = 8, - .len = 4, - .veroffs = 12, - .maxblocks = 4, - .pattern = bbt_pattern, -}; - -static struct nand_bbt_descr bbt_mirror_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE - | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, - .offs = 8, - .len = 4, - .veroffs = 12, - .maxblocks = 4, - .pattern = mirror_pattern, -}; - /* initialize driver data structures */ static void denali_drv_init(struct denali_nand_info *denali) { @@ -1416,6 +1476,7 @@ static void denali_drv_init(struct denali_nand_info *denali) int denali_init(struct denali_nand_info *denali) { int ret; + uint32_t val; if (denali->platform == INTEL_CE4100) { /* Due to a silicon limitation, we can only support @@ -1452,6 +1513,7 @@ int denali_init(struct denali_nand_info *denali) denali->mtd.priv = &denali->nand; /* register the driver with the NAND core subsystem */ + denali->nand.read_buf = denali_read_buf; denali->nand.select_chip = denali_select_chip; denali->nand.cmdfunc = denali_cmdfunc; denali->nand.read_byte = denali_read_byte; @@ -1514,14 +1576,19 @@ int denali_init(struct denali_nand_info *denali) * this stage requires information regarding ECC and * bad block management. */ - /* Bad block management */ - denali->nand.bbt_td = &bbt_main_descr; - denali->nand.bbt_md = &bbt_mirror_descr; + /* Bad block table description is set by nand framework, + see nand_bbt.c */ - /* skip the scan for now until we have OOB read and write support */ denali->nand.bbt_options |= NAND_BBT_USE_FLASH; - denali->nand.options |= NAND_SKIP_BBTSCAN; denali->nand.ecc.mode = NAND_ECC_HW_SYNDROME; + if (denali->have_hw_ecc_fixup) { + /* We have OOB support, so allow scan of BBT + and leave the OOB alone */ + denali->nand.bbt_options |= NAND_BBT_NO_OOB; + } else { + /* skip the scan for now until we have OOB read and write support */ + denali->nand.options |= NAND_SKIP_BBTSCAN; + } /* Denali Controller only support 15bit and 8bit ECC in MRST, * so just let controller do 15bit ECC for MLC and 8bit ECC for @@ -1585,12 +1652,32 @@ int denali_init(struct denali_nand_info *denali) denali->nand.ecc.write_oob = denali_write_oob; denali->nand.erase = denali_erase; + /* Occasionally the controller is in SPARE or MAIN+SPARE + mode upon startup, and we want it to be MAIN only */ + val = ioread32(denali->flash_reg + TRANSFER_MODE); + if (val != 0) { + int i; + dev_dbg(denali->dev, + "setting TRANSFER_MODE (%08x) back to MAIN only\n", val); + /* put all banks in MAIN mode, no SPARE */ + iowrite32(0, denali->flash_reg + TRANSFER_SPARE_REG); + for (i = 0; i < 4; i++) + index_addr(denali, MODE_10 | BANK(i) | 1, + MAIN_ACCESS); + } + if (nand_scan_tail(&denali->mtd)) { ret = -ENXIO; goto failed_req_irq; } - ret = mtd_device_register(&denali->mtd, NULL, 0); + /* We use the parse function and pass the of_node bcs + we want to pick up partitions from device tree */ + ret = mtd_device_parse_register(&denali->mtd, NULL, + &(struct mtd_part_parser_data){ + .of_node = denali->dev->of_node, + }, + 0, 0); if (ret) { dev_err(denali->dev, "Spectra: Failed to register MTD: %d\n", ret); diff --git a/drivers/mtd/nand/denali.h b/drivers/mtd/nand/denali.h index 9668174624217..d824626dd4128 100644 --- a/drivers/mtd/nand/denali.h +++ b/drivers/mtd/nand/denali.h @@ -214,6 +214,16 @@ #define INTR_STATUS(__bank) (0x410 + ((__bank) * 0x50)) #define INTR_EN(__bank) (0x420 + ((__bank) * 0x50)) +/* + * Some versions of the IP have the ECC fixup handled in hardware. In this + * configuration we only get interrupted when the error is uncorrectable. + * Unfortunately this bit replaces INTR_STATUS__ECC_TRANSACTION_DONE from the + * old IP. + * taken from patch by Jamie Iles + * support hardware with internal ECC fixup + */ +#define INTR_STATUS__ECC_UNCOR_ERR 0x0001 + #define INTR_STATUS__ECC_TRANSACTION_DONE 0x0001 #define INTR_STATUS__ECC_ERR 0x0002 #define INTR_STATUS__DMA_CMD_COMP 0x0004 @@ -326,6 +336,11 @@ #define CHNL_ACTIVE__CHANNEL2 0x0004 #define CHNL_ACTIVE__CHANNEL3 0x0008 +#define FLASH_BURST_LENGTH 0x770 +#define CHIP_INTERLEAVE_ENABLE_AND_ALLOW_INT_READS 0X780 +#define NO_OF_BLOCKS_PER_LUN 0X790 +#define LUN_STATUS_CMD 0X7A0 + #define ACTIVE_SRC_ID 0x800 #define ACTIVE_SRC_ID__VALUE 0x00ff @@ -476,7 +491,7 @@ struct denali_nand_info { struct device *dev; int total_used_banks; uint32_t block; /* stored for future use */ - uint16_t page; + uint32_t page; void __iomem *flash_reg; /* Mapped io reg base address */ void __iomem *flash_mem; /* Mapped io reg base address */ @@ -494,6 +509,7 @@ struct denali_nand_info { uint32_t blksperchip; uint32_t bbtskipbytes; uint32_t max_banks; + bool have_hw_ecc_fixup; }; extern int denali_init(struct denali_nand_info *denali); diff --git a/drivers/mtd/nand/denali_dt.c b/drivers/mtd/nand/denali_dt.c index 35cb17f578007..9683a220cfb3f 100644 --- a/drivers/mtd/nand/denali_dt.c +++ b/drivers/mtd/nand/denali_dt.c @@ -92,6 +92,9 @@ static int denali_dt_probe(struct platform_device *ofdev) } clk_prepare_enable(dt->clk); + denali->have_hw_ecc_fixup = of_property_read_bool(ofdev->dev.of_node, + "have-hw-ecc-fixup"); + ret = denali_init(denali); if (ret) goto out_disable_clk; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 4f3e80c68a266..8f164f58b722b 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2502,6 +2502,8 @@ static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, struct mtd_oob_ops ops; int ret; + nand_unlock(mtd, to, len); + nand_get_device(mtd, FL_WRITING); ops.len = len; ops.datbuf = (uint8_t *)buf; @@ -2619,6 +2621,8 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to, return -EINVAL; } + nand_unlock(mtd, to, ops->len); + nand_get_device(mtd, FL_WRITING); switch (ops->mode) { @@ -2692,6 +2696,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, if (check_offs_len(mtd, instr->addr, instr->len)) return -EINVAL; + nand_unlock(mtd, instr->addr, instr->len); + /* Grab the lock and see if the device is available */ nand_get_device(mtd, FL_ERASING); diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index c713c86567102..f164a625a6abe 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -47,6 +47,25 @@ static int read_sr(struct spi_nor *nor) return val; } +/* + * Read the flag status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. + */ +static int read_fsr(struct spi_nor *nor) +{ + int ret; + u8 val; + + ret = nor->read_reg(nor, SPINOR_OP_RDFSR, &val, 1); + if (ret < 0) { + pr_err("error %d reading FSR\n", ret); + return ret; + } + + return val; +} + /* * Read configuration register, returning its value in the * location. Return the configuration register value. @@ -165,6 +184,32 @@ static int spi_nor_wait_till_ready(struct spi_nor *nor) return -ETIMEDOUT; } +static int spi_nor_wait_till_fsr_ready(struct spi_nor *nor) +{ + unsigned long deadline; + int sr; + int fsr; + + deadline = jiffies + MAX_READY_WAIT_JIFFIES; + + do { + cond_resched(); + + sr = read_sr(nor); + if (sr < 0) { + break; + } else if (!(sr & SR_WIP)) { + fsr = read_fsr(nor); + if (fsr < 0) + break; + if (fsr & FSR_READY) + return 0; + } + } while (!time_after_eq(jiffies, deadline)); + + return -ETIMEDOUT; +} + /* * Service routine to read status register until ready, or timeout occurs. * Returns non-zero if error. @@ -402,6 +447,7 @@ struct flash_info { #define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */ #define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */ #define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */ +#define USE_FSR 0x80 /* use flag status register */ }; #define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ @@ -488,6 +534,8 @@ const struct spi_device_id spi_nor_ids[] = { { "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, 0) }, { "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K) }, { "n25q512a", INFO(0x20bb20, 0, 64 * 1024, 1024, SECT_4K) }, + { "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024, USE_FSR) }, + { "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048, USE_FSR) }, /* PMC */ { "pm25lv512", INFO(0, 0, 32 * 1024, 2, SECT_4K_PMC) }, @@ -851,6 +899,11 @@ static int set_quad_mode(struct spi_nor *nor, u32 jedec_id) } } +static void spi_nor_shutdown(struct spi_nor *nor) +{ + set_4byte(nor, nor->jedec_id, 0); +} + static int spi_nor_check(struct spi_nor *nor) { if (!nor->dev || !nor->read || !nor->write || @@ -863,6 +916,8 @@ static int spi_nor_check(struct spi_nor *nor) nor->read_id = spi_nor_read_id; if (!nor->wait_till_ready) nor->wait_till_ready = spi_nor_wait_till_ready; + if (!nor->shutdown) + nor->shutdown = spi_nor_shutdown; return 0; } @@ -929,6 +984,8 @@ int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id, mutex_init(&nor->lock); + nor->jedec_id = info->jedec_id; + /* * Atmel, SST and Intel/Numonyx serial nor tend to power * up with the software protection bits set @@ -965,6 +1022,10 @@ int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id, else mtd->_write = spi_nor_write; + if ((info->flags & USE_FSR) && + nor->wait_till_ready == spi_nor_wait_till_ready) + nor->wait_till_ready = spi_nor_wait_till_fsr_ready; + /* prefer "small sector" erase if possible */ if (info->flags & SECT_4K) { nor->erase_opcode = SPINOR_OP_BE_4K; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 213b5cbb9dcce..e121717aa9e88 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -59,6 +59,12 @@ config SPI_ALTERA help This is the driver for the Altera SPI Controller. +config SPI_CADENCE_QSPI + tristate "Cadence QSPI Controller" + depends on ARCH_SOCFPGA + help + This is the driver for the Cadence QSPI Controller. + config SPI_ATH79 tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver" depends on ATH79 && GPIOLIB @@ -574,6 +580,10 @@ config SPI_DESIGNWARE help general driver for SPI controller core from DesignWare +config SPI_DW_PL330_DMA + bool "DMA support for DW SPI controller through DMA PL330 controller" + depends on SPI_DESIGNWARE && PL330_DMA + config SPI_DW_PCI tristate "PCI interface driver for DW SPI core" depends on SPI_DESIGNWARE && PCI diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 929c9f5eac01d..d8c413ebed9e5 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -24,10 +24,13 @@ obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o obj-$(CONFIG_SPI_BUTTERFLY) += spi-butterfly.o obj-$(CONFIG_SPI_CADENCE) += spi-cadence.o obj-$(CONFIG_SPI_CLPS711X) += spi-clps711x.o +obj-$(CONFIG_SPI_CADENCE_QSPI) += spi-cadence-qspi-mod.o +spi-cadence-qspi-mod-objs := spi-cadence-qspi-apb.o spi-cadence-qspi.o obj-$(CONFIG_SPI_COLDFIRE_QSPI) += spi-coldfire-qspi.o obj-$(CONFIG_SPI_DAVINCI) += spi-davinci.o obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o +obj-$(CONFIG_SPI_DW_PL330_DMA) += spi-dw-pl330.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-midpci.o spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o obj-$(CONFIG_SPI_EFM32) += spi-efm32.o diff --git a/drivers/spi/spi-cadence-qspi-apb.c b/drivers/spi/spi-cadence-qspi-apb.c new file mode 100644 index 0000000000000..782c84099a91d --- /dev/null +++ b/drivers/spi/spi-cadence-qspi-apb.c @@ -0,0 +1,1231 @@ +/* + * Driver for Cadence QSPI Controller + * + * Copyright (C) 2012 Altera Corporation + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * OpenIB.org BSD license below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include +#include +#include +#include +#include +#include +#include "spi-cadence-qspi.h" +#include "spi-cadence-qspi-apb.h" + +/****************************************************************************/ + +#include + +typedef int (*PRINTF_FUNC)(const char *fmt, ...); + +#ifdef DEBUG +static void __hex_dump(unsigned int address_to_print, + const unsigned char *buffer, + int length, + PRINTF_FUNC _printfunc) +{ + int i; + int j; + for (i = 0; i < length; i += 16) { + _printfunc("%08x: ", address_to_print+i); + for (j = 0; j < 8; j++) { + if ((i+j) < length) + _printfunc("%02x ", buffer[i+j]); + else + _printfunc(" "); + } + _printfunc(" "); + for (j = 8; j < 16; j++) { + if ((i+j) < length) + _printfunc("%02x ", buffer[i+j]); + else + _printfunc(" "); + } + _printfunc(" "); + for (j = 0; j < 16; j++) { + if ((i+j) < length) + _printfunc("%c", + isprint(buffer[i+j]) ? + buffer[i+j] : '.'); + else + break; + } + _printfunc("\n"); + } +} +#endif /* #ifdef DEBUG */ + +#ifdef DEBUG +#define hex_dump(a, b, c) __hex_dump(a, b, c, (PRINTF_FUNC)&printk) +#else +#define hex_dump(a, b, c) +#endif + + +/****************************************************************************/ + +/* 1-beat single, 4-byte width = 4-byte single = 2**2 */ +#define CQSPI_NUMSGLREQBYTES (2) +/* 16-beat burst, 4-byte width = 64-byte bursts = 2**6 */ +#define CQSPI_NUMBURSTREQBYTES (6) + +void cadence_qspi_apb_delay(struct struct_cqspi *cadence_qspi, + unsigned int ref_clk, unsigned int sclk_hz); + +static unsigned int cadence_qspi_apb_cmd2addr(const unsigned char* addr_buf, + unsigned int addr_width) +{ + unsigned int addr; + + pr_debug("%s addr_buf %p addr_width %d\n", + __func__, addr_buf, addr_width); + + addr = (addr_buf[0] << 16) | (addr_buf[1] << 8) | addr_buf[2]; + + if (addr_width == 4) + addr = (addr << 8) | addr_buf[3]; + + return addr; +} + +static void cadence_qspi_apb_read_fifo_data(void *dest, const void *src_ahb_addr, + unsigned int bytes) +{ + unsigned int temp; + int remaining = bytes; + unsigned int *dest_ptr = (unsigned int *)dest; + unsigned int *src_ptr = (unsigned int *)src_ahb_addr; + + while (remaining > 0) { + if (remaining >= CQSPI_FIFO_WIDTH) { + *dest_ptr = CQSPI_READL(src_ptr); + remaining -= CQSPI_FIFO_WIDTH; + } else { + /* dangling bytes */ + temp = CQSPI_READL(src_ptr); + memcpy(dest_ptr, &temp, remaining); + break; + } + dest_ptr++; + } + + return; +} + +static void cadence_qspi_apb_write_fifo_data(void *dest_ahb_addr, + const void *src, unsigned int bytes) +{ + unsigned int temp; + int remaining = bytes; + unsigned int *dest_ptr = (unsigned int *)dest_ahb_addr; + unsigned int *src_ptr = (unsigned int *)src; + + while (remaining > 0) { + if (remaining >= CQSPI_FIFO_WIDTH) { + CQSPI_WRITEL(*src_ptr, dest_ptr); + remaining -= CQSPI_FIFO_WIDTH; + } else { + /* dangling bytes */ + memcpy(&temp, src_ptr, remaining); + CQSPI_WRITEL(temp, dest_ptr); + break; + } + src_ptr++; + } + + return; +} + +/* Return 1 if idle, otherwise return 0 (busy). */ +static unsigned int cadence_qspi_wait_idle(void * reg_base) { + unsigned int count = 0; + unsigned timeout; + + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + if (CQSPI_REG_IS_IDLE(reg_base)) { + /* Read few times in succession to ensure it does + not transition low again */ + count++; + if (count >= CQSPI_POLL_IDLE_RETRY) + return 1; + } else { + count = 0; + } + } + + /* Timeout, in busy mode. */ + pr_err("QSPI: QSPI is still busy after %dms timeout.\n", + CQSPI_TIMEOUT_MS); + return 0; +} + +static void cadence_qspi_apb_readdata_capture(void *reg_base, + unsigned int bypass, unsigned int delay) +{ + unsigned int reg; + + pr_debug("%s %d %d\n", __func__, bypass, delay); + reg = CQSPI_READL(reg_base + CQSPI_REG_READCAPTURE); + + if (bypass) { + reg |= (1 << CQSPI_REG_READCAPTURE_BYPASS_LSB); + } else { + reg &= ~(1 << CQSPI_REG_READCAPTURE_BYPASS_LSB); + } + + reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK + << CQSPI_REG_READCAPTURE_DELAY_LSB); + + reg |= ((delay & CQSPI_REG_READCAPTURE_DELAY_MASK) + << CQSPI_REG_READCAPTURE_DELAY_LSB); + + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_READCAPTURE); + + return; +} + +static void cadence_qspi_apb_config_baudrate_div(void *reg_base, + unsigned int ref_clk_hz, unsigned int sclk_hz) +{ + unsigned int reg; + unsigned int div; + + pr_debug("%s %d %d\n", __func__, ref_clk_hz, sclk_hz); + + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg &= ~(CQSPI_REG_CONFIG_BAUD_MASK << CQSPI_REG_CONFIG_BAUD_LSB); + + div = ref_clk_hz / sclk_hz; + + /* Recalculate the baudrate divisor based on QSPI specification. */ + if (div > 32) + div = 32; + + /* Check if even number. */ + if (div & 1) + div = (div / 2); + else + div = (div / 2) - 1; + + pr_debug("QSPI: ref_clk %dHz sclk %dHz div 0x%x\n", ref_clk_hz, + sclk_hz, div); + + div = (div & CQSPI_REG_CONFIG_BAUD_MASK) << CQSPI_REG_CONFIG_BAUD_LSB; + reg |= div; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + return; +} + +static void cadence_qspi_apb_chipselect(void * reg_base, + unsigned int chip_select, unsigned int decoder_enable) +{ + unsigned int reg; + + pr_debug("%s\n", __func__); + + pr_debug("QSPI: chipselect %d decode %d\n", chip_select, + decoder_enable); + + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + /* docoder */ + if (decoder_enable) + reg |= CQSPI_REG_CONFIG_DECODE_MASK; + else { + reg &= ~CQSPI_REG_CONFIG_DECODE_MASK; + + /* Convert CS if without decoder. + * CS0 to 4b'1110 + * CS1 to 4b'1101 + * CS2 to 4b'1011 + * CS3 to 4b'0111 + */ + chip_select = 0xF & ~(1 << chip_select); + } + + reg &= ~(CQSPI_REG_CONFIG_CHIPSELECT_MASK + << CQSPI_REG_CONFIG_CHIPSELECT_LSB); + reg |= (chip_select & CQSPI_REG_CONFIG_CHIPSELECT_MASK) + << CQSPI_REG_CONFIG_CHIPSELECT_LSB; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + return; +} + +static int cadence_qspi_apb_exec_flash_cmd(void *reg_base, unsigned int reg) +{ + unsigned int timeout; + + /* Write the CMDCTRL without start execution. */ + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CMDCTRL); + /* Start execute */ + reg |= CQSPI_REG_CMDCTRL_EXECUTE_MASK; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CMDCTRL); + + /* Polling for completion. */ + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + reg = CQSPI_READL(reg_base + CQSPI_REG_CMDCTRL) & + CQSPI_REG_CMDCTRL_INPROGRESS_MASK; + if (!reg) + break; + } + + if (reg != 0) { + pr_err("QSPI: flash cmd execute time out\n"); + return -EIO; + } + + /* Polling QSPI idle status. */ + if (!cadence_qspi_wait_idle(reg_base)) + return -EIO; + + return 0; +} + +/* For command RDID, RDSR. */ +static int cadence_qspi_apb_command_read(void *reg_base, + unsigned int txlen, const unsigned char *txbuf, + unsigned rxlen, unsigned char *rxbuf) +{ + unsigned int reg; + unsigned int read_len; + int status; + + pr_debug("%s txlen %d txbuf %p rxlen %d rxbuf %p\n", + __func__, txlen, txbuf, rxlen, rxbuf); + hex_dump((unsigned int)txbuf, txbuf, txlen); + + if (!rxlen || rxlen > CQSPI_STIG_DATA_LEN_MAX || rxbuf == NULL) { + pr_err("QSPI: Invalid input argument, len %d rxbuf 0x%08x\n", + rxlen, (unsigned int)rxbuf); + return -EINVAL; + } + + reg = txbuf[0] << CQSPI_REG_CMDCTRL_OPCODE_LSB; + + reg |= (0x1 << CQSPI_REG_CMDCTRL_RD_EN_LSB); + + /* 0 means 1 byte. */ + reg |= (((rxlen - 1) & CQSPI_REG_CMDCTRL_RD_BYTES_MASK) + << CQSPI_REG_CMDCTRL_RD_BYTES_LSB); + status = cadence_qspi_apb_exec_flash_cmd(reg_base, reg); + if (status != 0) + return status; + + reg = CQSPI_READL(reg_base + CQSPI_REG_CMDREADDATALOWER); + + /* Put the read value into rx_buf */ + read_len = (rxlen > 4) ? 4 : rxlen; + memcpy(rxbuf, ®, read_len); + hex_dump((unsigned int)rxbuf, rxbuf, read_len); + rxbuf += read_len; + + if (rxlen > 4) { + reg = CQSPI_READL(reg_base + CQSPI_REG_CMDREADDATAUPPER); + + read_len = rxlen - read_len; + memcpy(rxbuf, ®, read_len); + hex_dump((unsigned int)rxbuf, rxbuf, read_len); + } + + return 0; +} + +/* For commands: WRSR, WREN, WRDI, CHIP_ERASE, BE, etc. */ +static int cadence_qspi_apb_command_write(void *reg_base, unsigned txlen, + const unsigned char *txbuf) +{ + unsigned int reg; + unsigned int addr_value; + unsigned int data; + + pr_debug("%s txlen %d txbuf %p\n", __func__, txlen, txbuf); + hex_dump((unsigned int)txbuf, txbuf, txlen); + + if (!txlen || txlen > 5 || txbuf == NULL) { + pr_err("QSPI: Invalid input argument, cmdlen %d txbuf 0x%08x\n", + txlen, (unsigned int)txbuf); + return -EINVAL; + } + + reg = txbuf[0] << CQSPI_REG_CMDCTRL_OPCODE_LSB; + if (txlen == 2 || txlen == 3) { + /* Command with data only. */ + reg |= (0x1 << CQSPI_REG_CMDCTRL_WR_EN_LSB); + reg |= ((txlen - 2) & CQSPI_REG_CMDCTRL_WR_BYTES_MASK) + << CQSPI_REG_CMDCTRL_WR_BYTES_LSB; + + memcpy(&data, &txbuf[1], txlen - 1); + /* Write the data */ + CQSPI_WRITEL(data, reg_base + CQSPI_REG_CMDWRITEDATALOWER); + } + else if (txlen == 4 || txlen == 5) { + /* Command with address */ + reg |= (0x1 << CQSPI_REG_CMDCTRL_ADDR_EN_LSB); + /* Number of bytes to write. */ + reg |= ((txlen - 2) & CQSPI_REG_CMDCTRL_ADD_BYTES_MASK) + << CQSPI_REG_CMDCTRL_ADD_BYTES_LSB; + /* Get address */ + addr_value = cadence_qspi_apb_cmd2addr(&txbuf[1], + txlen >=5 ? 4 : 3); + + CQSPI_WRITEL(addr_value, reg_base + CQSPI_REG_CMDADDRESS); + } + + return cadence_qspi_apb_exec_flash_cmd(reg_base, reg); +} + +static void cadence_qspi_dma_done(void *arg) +{ + struct struct_cqspi *cadence_qspi = arg; + cadence_qspi->dma_done = 1; + wake_up(&cadence_qspi->waitqueue); +} + +#define CQSPI_IS_DMA_READ (true) +#define CQSPI_IS_DMA_WRITE (false) + +static void cadence_qspi_apb_dma_cleanup( + struct struct_cqspi *cadence_qspi, + unsigned datalen, bool do_read) +{ + struct platform_device *pdev = cadence_qspi->pdev; + dma_unmap_single(&pdev->dev, cadence_qspi->dma_addr, + datalen, do_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} + +static int calculate_burst_bytes_exp(int numbytes) +{ + int exp = CQSPI_NUMBURSTREQBYTES; + while ((1< CQSPI_FIFO_WIDTH) { + if ((numbytes % (1 << exp)) == 0) + break; + exp--; + } + return exp; +} + +static int cadence_qspi_apb_dma_start( + struct struct_cqspi *cadence_qspi, + unsigned datalen, unsigned char *databuf, + bool do_read) +{ + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct dma_chan *dmachan; + struct dma_slave_config dmaconf; + struct dma_async_tx_descriptor *dmadesc = NULL; + enum dma_data_direction data_direction; + int burst_len_exp; + int burst_words; + + if (datalen % CQSPI_FIFO_WIDTH) + return -EINVAL; + + burst_len_exp = calculate_burst_bytes_exp(datalen); + burst_words = (1<rxchan; + data_direction = DMA_FROM_DEVICE; + dmaconf.direction = DMA_DEV_TO_MEM; + dmaconf.src_addr = pdata->qspi_ahb_phy; + dmaconf.src_addr_width = 4; + dmaconf.src_maxburst = burst_words; + } else { + dmachan = cadence_qspi->txchan; + data_direction = DMA_TO_DEVICE; + dmaconf.direction = DMA_MEM_TO_DEV; + dmaconf.dst_addr = pdata->qspi_ahb_phy; + dmaconf.dst_addr_width = 4; + dmaconf.dst_maxburst = burst_words; + } + + /* map the buffer address */ + cadence_qspi->dma_addr = dma_map_single(&pdev->dev, + databuf, datalen, data_direction); + if (dma_mapping_error(&pdev->dev, cadence_qspi->dma_addr)) { + dev_err(&pdev->dev, "dma_map_single failed\n"); + return -EINVAL; + } + + /* set up slave config */ + dmaengine_slave_config(dmachan, &dmaconf); + + /* get dmadesc */ + dmadesc = dmaengine_prep_slave_single(dmachan, + cadence_qspi->dma_addr, + datalen, + dmaconf.direction, + DMA_PREP_INTERRUPT); + if (!dmadesc) { + cadence_qspi_apb_dma_cleanup(cadence_qspi, datalen, do_read); + return -ENOMEM; + } + dmadesc->callback = cadence_qspi_dma_done; + dmadesc->callback_param = cadence_qspi; + + /* start DMA */ + cadence_qspi->dma_done = 0; + dmadesc->tx_submit(dmadesc); + dma_async_issue_pending(dmachan); + + return 0; +} + +static int cadence_qspi_apb_indirect_read_dma( + struct struct_cqspi *cadence_qspi, + unsigned rxlen, unsigned char *rxbuf) +{ + int ret = 0; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + void *reg_base = cadence_qspi->iobase; + unsigned int reg; + unsigned int timeout; + int burst_len_exp; + struct cqspi_flash_pdata *f_pdata = + &(pdata->f_pdata[cadence_qspi->current_cs]); + unsigned int page_size = f_pdata->page_size; + /* use watermark of page size less one burst */ + unsigned int watermark = page_size - (1 << CQSPI_NUMBURSTREQBYTES); + + if (rxlen < watermark) + watermark = rxlen; + + ret = cadence_qspi_apb_dma_start(cadence_qspi, + rxlen, rxbuf, CQSPI_IS_DMA_READ); + if (ret) + return ret; + + /* Set up qspi dma */ + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg |= CQSPI_REG_CONFIG_DMA_MASK; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + burst_len_exp = calculate_burst_bytes_exp(rxlen); + + reg = (burst_len_exp << CQSPI_REG_DMA_BURST_LSB) + | (CQSPI_NUMSGLREQBYTES << CQSPI_REG_DMA_SINGLE_LSB); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_DMA); + + /* Set up QSPI transfer */ + CQSPI_WRITEL(watermark, reg_base + CQSPI_REG_INDIRECTRDWATERMARK); + CQSPI_WRITEL(rxlen, reg_base + CQSPI_REG_INDIRECTRDBYTES); + CQSPI_WRITEL(pdata->fifo_depth - CQSPI_REG_SRAM_RESV_WORDS, + reg_base + CQSPI_REG_SRAMPARTITION); + + /* Clear all interrupts. */ + CQSPI_WRITEL(CQSPI_IRQ_STATUS_MASK, reg_base + CQSPI_REG_IRQSTATUS); + + CQSPI_WRITEL(CQSPI_IRQ_MASK_RD, reg_base + CQSPI_REG_IRQMASK); + + /* Start qspi */ + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTRD); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_INDIRECTRD); + CQSPI_WRITEL(CQSPI_REG_INDIRECTRD_START_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + + /* Wait for dma to finish */ + if (!wait_event_interruptible_timeout(cadence_qspi->waitqueue, + cadence_qspi->dma_done, CQSPI_TIMEOUT_MS)) { + pr_err("QSPI: Indirect read DMA timeout\n"); + ret = -ETIMEDOUT; + } + + /* Check indirect done status */ + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTRD); + if (reg & CQSPI_REG_INDIRECTRD_DONE_MASK) + break; + } + + if (!(reg & CQSPI_REG_INDIRECTRD_DONE_MASK)) { + pr_err("QSPI : Indirect read completion status error with reg 0x%08x\n", + reg); + ret = -ETIMEDOUT; + } + + if (ret != 0) { + /* We had an error, cancel the indirect read */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_CANCEL_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + /* and cancel DMA */ + dmaengine_terminate_all(cadence_qspi->rxchan); + } + + /* Disable interrupt */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Clear indirect completion status */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTRD_DONE_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + + cadence_qspi_apb_dma_cleanup(cadence_qspi, rxlen, CQSPI_IS_DMA_READ); + + /* Turn off qspi dma */ + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg &= ~(CQSPI_REG_CONFIG_DMA_MASK); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + return ret; +} + +static int cadence_qspi_apb_indirect_read_setup(void *reg_base, + unsigned int ahb_phy_addr, unsigned txlen, const unsigned char *txbuf, + unsigned int addr_bytes) +{ + unsigned int reg; + unsigned int addr_value; + unsigned int dummy_clk; + unsigned int dummy_bytes; + + pr_debug("%s txlen %d txbuf %p addr_bytes %d\n", + __func__, txlen, txbuf, addr_bytes); + hex_dump((unsigned int)txbuf, txbuf, txlen); + + if ((addr_bytes == 3 && txlen < 4) || (addr_bytes == 4 && txlen < 5)) { + pr_err("QSPI: Invalid txbuf length, length %d\n", txlen); + return -EINVAL; + } + + CQSPI_WRITEL((ahb_phy_addr & CQSPI_INDIRECTTRIGGER_ADDR_MASK), + reg_base + CQSPI_REG_INDIRECTTRIGGER); + + reg = txbuf[0] << CQSPI_REG_RD_INSTR_OPCODE_LSB; + + /* Get address */ + addr_value = cadence_qspi_apb_cmd2addr(&txbuf[1], addr_bytes); + CQSPI_WRITEL(addr_value, reg_base + CQSPI_REG_INDIRECTRDSTARTADDR); + + /* The remaining lenght is dummy bytes. */ + dummy_bytes = txlen - addr_bytes - 1; + + /* Setup dummy clock cycles */ + if (dummy_bytes) { + + if (dummy_bytes > CQSPI_DUMMY_BYTES_MAX) + dummy_bytes = CQSPI_DUMMY_BYTES_MAX; + + reg |= (1 << CQSPI_REG_RD_INSTR_MODE_EN_LSB); + /* Set all high to ensure chip doesn't enter XIP */ + CQSPI_WRITEL(0xFF, reg_base + CQSPI_REG_MODE_BIT); + + /* Convert to clock cycles. */ + dummy_clk = dummy_bytes * CQSPI_DUMMY_CLKS_PER_BYTE; + /* Need to minus the mode byte (8 clocks). */ + dummy_clk -= CQSPI_DUMMY_CLKS_PER_BYTE; + + if (dummy_clk) + reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK) + << CQSPI_REG_RD_INSTR_DUMMY_LSB; + } + + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_RD_INSTR); + + /* Set device size */ + reg = CQSPI_READL(reg_base + CQSPI_REG_SIZE); + reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK; + reg |= (addr_bytes - 1); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_SIZE); + return 0; +} + +static int cadence_qspi_apb_indirect_read_execute( + struct struct_cqspi *cadence_qspi, unsigned rxlen, + unsigned char *rxbuf) +{ + unsigned int reg = 0; + unsigned int timeout; + unsigned int watermark = CQSPI_REG_SRAM_THRESHOLD_BYTES; + unsigned int *irq_status = &(cadence_qspi->irq_status); + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + void *reg_base = cadence_qspi->iobase; + void *ahb_base = cadence_qspi->qspi_ahb_virt; + int remaining = (int)rxlen; + int ret = 0; + +#ifdef DEBUG + unsigned char *saverxbuf = rxbuf; + unsigned saverxlen = rxlen; +#endif /* #ifdef DEBUG */ + + pr_debug("%s rxlen %d rxbuf %p\n", __func__, rxlen, rxbuf); + if (remaining < watermark) + watermark = remaining; + + CQSPI_WRITEL(watermark, reg_base + CQSPI_REG_INDIRECTRDWATERMARK); + CQSPI_WRITEL(remaining, reg_base + CQSPI_REG_INDIRECTRDBYTES); + CQSPI_WRITEL(pdata->fifo_depth - CQSPI_REG_SRAM_RESV_WORDS, + reg_base + CQSPI_REG_SRAMPARTITION); + + /* Clear all interrupts. */ + CQSPI_WRITEL(CQSPI_IRQ_STATUS_MASK, reg_base + CQSPI_REG_IRQSTATUS); + + CQSPI_WRITEL(CQSPI_IRQ_MASK_RD, reg_base + CQSPI_REG_IRQMASK); + + CQSPI_WRITEL(CQSPI_REG_INDIRECTRD_START_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + + while (remaining > 0) { + ret = wait_event_interruptible_timeout(cadence_qspi->waitqueue, + *irq_status, CQSPI_TIMEOUT_MS); + if (!ret) { + pr_err("QSPI: Indirect read timeout\n"); + ret = -ETIMEDOUT; + goto failrd; + } + + if (*irq_status & CQSPI_IRQ_STATUS_ERR) { + /* Error occurred */ + pr_err("QSPI: Indirect read error IRQ status 0x%08x\n", + *irq_status); + ret = -EPERM; + goto failrd; + } + + if (*irq_status & (CQSPI_REG_IRQ_IND_RD_OVERFLOW | + CQSPI_REG_IRQ_IND_COMP | CQSPI_REG_IRQ_WATERMARK)) { + + reg = CQSPI_GET_RD_SRAM_LEVEL(reg_base); + /* convert to bytes */ + reg *= CQSPI_FIFO_WIDTH; + reg = reg > remaining ? remaining : reg; + /* Read data from FIFO. */ + cadence_qspi_apb_read_fifo_data(rxbuf, ahb_base, reg); + rxbuf += reg; + remaining -= reg; + } + } + + /* Check indirect done status */ + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTRD); + if (reg & CQSPI_REG_INDIRECTRD_DONE_MASK) + break; + } + + if (!(reg & CQSPI_REG_INDIRECTRD_DONE_MASK)) { + pr_err("QSPI : Indirect read completion status error with " + "reg 0x%08x\n", reg); + ret = -ETIMEDOUT; + goto failrd; + } + + /* Disable interrupt */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Clear indirect completion status */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTRD_DONE_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + hex_dump((unsigned int)saverxbuf, saverxbuf, saverxlen); + return 0; + +failrd: + /* Disable interrupt */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Cancel the indirect read */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_CANCEL_MASK, + reg_base + CQSPI_REG_INDIRECTRD); + return ret; +} + +static int cadence_qspi_apb_indirect_write_setup(void *reg_base, + unsigned int ahb_phy_addr, unsigned txlen, const unsigned char *txbuf) +{ + unsigned int reg; + unsigned int addr_bytes = (txlen >= 5) ? 4: 3; + + pr_debug("%s txlen %d txbuf %p addr_bytes %d\n", + __func__, txlen, txbuf, addr_bytes); + hex_dump((unsigned int)txbuf, txbuf, txlen); + + if (txlen < 4 || txbuf == NULL) { + pr_err("QSPI: Invalid input argument, txlen %d txbuf 0x%08x\n", + txlen, (unsigned int)txbuf); + return -EINVAL; + } + + CQSPI_WRITEL((ahb_phy_addr & CQSPI_INDIRECTTRIGGER_ADDR_MASK), + reg_base + CQSPI_REG_INDIRECTTRIGGER); + + /* Set opcode. */ + reg = txbuf[0] << CQSPI_REG_WR_INSTR_OPCODE_LSB; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_WR_INSTR); + + /* Setup write address. */ + reg = cadence_qspi_apb_cmd2addr(&txbuf[1], addr_bytes); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_INDIRECTWRSTARTADDR); + + reg = CQSPI_READL(reg_base + CQSPI_REG_SIZE); + reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK; + reg |= (addr_bytes - 1); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_SIZE); + return 0; +} + +static int cadence_qspi_apb_indirect_write_dma( + struct struct_cqspi *cadence_qspi, + unsigned txlen, unsigned char *txbuf) +{ + int ret = 0; + void *reg_base = cadence_qspi->iobase; + unsigned int reg; + unsigned int timeout; + int burst_len_exp; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct cqspi_flash_pdata *f_pdata = + &(pdata->f_pdata[cadence_qspi->current_cs]); + + pr_debug("%s txlen %d txbuf %p\n", __func__, txlen, txbuf); + + ret = cadence_qspi_apb_dma_start(cadence_qspi, + txlen, txbuf, CQSPI_IS_DMA_WRITE); + if (ret) + return ret; + + /* Set up qspi dma */ + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg |= CQSPI_REG_CONFIG_DMA_MASK; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + burst_len_exp = calculate_burst_bytes_exp(txlen); + + reg = (burst_len_exp << CQSPI_REG_DMA_BURST_LSB) + | (CQSPI_NUMSGLREQBYTES << CQSPI_REG_DMA_SINGLE_LSB); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_DMA); + + /* Set up QSPI transfer */ + CQSPI_WRITEL(f_pdata->page_size, + reg_base + CQSPI_REG_INDIRECTWRWATERMARK); + CQSPI_WRITEL(txlen, reg_base + CQSPI_REG_INDIRECTWRBYTES); + CQSPI_WRITEL(CQSPI_REG_SRAM_PARTITION_WR, + reg_base + CQSPI_REG_SRAMPARTITION); + + /* Clear all interrupts. */ + CQSPI_WRITEL(CQSPI_IRQ_STATUS_MASK, reg_base + CQSPI_REG_IRQSTATUS); + + CQSPI_WRITEL(CQSPI_IRQ_MASK_WR, reg_base + CQSPI_REG_IRQMASK); + + /* Start qspi */ + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTWR); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_INDIRECTWR); + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_START_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + + /* Wait for dma to finish */ + if (!wait_event_interruptible_timeout(cadence_qspi->waitqueue, + cadence_qspi->dma_done, CQSPI_TIMEOUT_MS)) { + pr_err("QSPI: Indirect write DMA timeout\n"); + ret = -ETIMEDOUT; + } + + /* Check indirect done status */ + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTWR); + if (reg & CQSPI_REG_INDIRECTWR_DONE_MASK) + break; + } + + if (!(reg & CQSPI_REG_INDIRECTWR_DONE_MASK)) { + pr_err("QSPI : Indirect write completion status error with reg 0x%08x\n", + reg); + ret = -ETIMEDOUT; + } + + if (ret != 0) { + /* We had an error, cancel the indirect write */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_CANCEL_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + /* and cancel DMA */ + dmaengine_terminate_all(cadence_qspi->txchan); + } + + /* Disable interrupt */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Clear indirect completion status */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_DONE_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + + cadence_qspi_apb_dma_cleanup(cadence_qspi, txlen, CQSPI_IS_DMA_WRITE); + + /* Turn off qspi dma */ + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg &= ~(CQSPI_REG_CONFIG_DMA_MASK); + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + + return ret; +} + +static int cadence_qspi_apb_indirect_write_execute( + struct struct_cqspi *cadence_qspi, unsigned txlen, + const unsigned char *txbuf) +{ + int ret; + unsigned int timeout; + unsigned int reg = 0; + unsigned int *irq_status = &(cadence_qspi->irq_status); + void *reg_base = cadence_qspi->iobase; + void *ahb_base = cadence_qspi->qspi_ahb_virt; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct cqspi_flash_pdata *f_pdata = + &(pdata->f_pdata[cadence_qspi->current_cs]); + unsigned int page_size = f_pdata->page_size; + int remaining = (int)txlen; + unsigned int write_bytes; + + pr_debug("%s txlen %d txbuf %p\n", __func__, txlen, txbuf); + hex_dump((unsigned int)txbuf, txbuf, txlen); + CQSPI_WRITEL(remaining, reg_base + CQSPI_REG_INDIRECTWRBYTES); + + CQSPI_WRITEL(CQSPI_REG_SRAM_THRESHOLD_BYTES, reg_base + + CQSPI_REG_INDIRECTWRWATERMARK); + + CQSPI_WRITEL(CQSPI_REG_SRAM_PARTITION_WR, + reg_base + CQSPI_REG_SRAMPARTITION); + + /* Clear all interrupts. */ + CQSPI_WRITEL(CQSPI_IRQ_STATUS_MASK, reg_base + CQSPI_REG_IRQSTATUS); + + CQSPI_WRITEL(CQSPI_IRQ_MASK_WR, reg_base + CQSPI_REG_IRQMASK); + + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_START_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + + /* Write a page or remaining bytes. */ + write_bytes = remaining > page_size ? page_size : remaining; + /* Fill up the data at the begining */ + cadence_qspi_apb_write_fifo_data(ahb_base, txbuf, write_bytes); + txbuf += write_bytes; + remaining -= write_bytes; + + while (remaining > 0) { + ret = wait_event_interruptible_timeout(cadence_qspi->waitqueue, + *irq_status, CQSPI_TIMEOUT_MS); + if (!ret) { + pr_err("QSPI: Indirect write timeout\n"); + ret = -ETIMEDOUT; + goto failwr; + } + + if (*irq_status & CQSPI_IRQ_STATUS_ERR) { + /* Error occurred */ + pr_err("QSPI : Indirect write error" + "IRQ status 0x%08x\n", *irq_status); + ret = -EPERM; + goto failwr; + } + + if (*irq_status & (CQSPI_REG_IRQ_UNDERFLOW | + CQSPI_REG_IRQ_IND_COMP | CQSPI_REG_IRQ_WATERMARK)){ + /* Calculate number of bytes to write. */ + write_bytes = remaining > page_size ? + page_size : remaining; + + cadence_qspi_apb_write_fifo_data(ahb_base, txbuf, + write_bytes); + txbuf += write_bytes; + remaining -= write_bytes; + } + } + + /* Check indirect done status */ + timeout = cadence_qspi_init_timeout(CQSPI_TIMEOUT_MS); + while (cadence_qspi_check_timeout(timeout)) { + reg = CQSPI_READL(reg_base + CQSPI_REG_INDIRECTWR); + if (reg & CQSPI_REG_INDIRECTWR_DONE_MASK) + break; + } + + if (!(reg & CQSPI_REG_INDIRECTWR_DONE_MASK)) { + pr_err("QSPI: Indirect write completion status error with " + "reg 0x%08x\n", reg); + ret = -ETIMEDOUT; + goto failwr; + } + + /* Disable interrupt. */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Clear indirect completion status */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_DONE_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + return 0; + +failwr: + /* Disable interrupt. */ + CQSPI_WRITEL(0, reg_base + CQSPI_REG_IRQMASK); + + /* Cancel the indirect write */ + CQSPI_WRITEL(CQSPI_REG_INDIRECTWR_CANCEL_MASK, + reg_base + CQSPI_REG_INDIRECTWR); + return ret; +} + +void cadence_qspi_apb_controller_enable(void *reg_base) +{ + unsigned int reg; + pr_debug("%s\n", __func__); + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg |= CQSPI_REG_CONFIG_ENABLE_MASK; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + return; +} + +void cadence_qspi_apb_controller_disable(void *reg_base) +{ + unsigned int reg; + pr_debug("%s\n", __func__); + reg = CQSPI_READL(reg_base + CQSPI_REG_CONFIG); + reg &= ~CQSPI_REG_CONFIG_ENABLE_MASK; + CQSPI_WRITEL(reg, reg_base + CQSPI_REG_CONFIG); + return; +} + +unsigned int cadence_qspi_apb_is_controller_ready(void *reg_base) +{ + return cadence_qspi_wait_idle(reg_base); +} + +void cadence_qspi_apb_controller_init(struct struct_cqspi *cadence_qspi) +{ + cadence_qspi_apb_controller_disable(cadence_qspi->iobase); + + /* Configure the remap address register, no remap */ + CQSPI_WRITEL(0, cadence_qspi->iobase + CQSPI_REG_REMAP); + + /* Disable all interrupts. */ + CQSPI_WRITEL(0, cadence_qspi->iobase + CQSPI_REG_IRQMASK); + + cadence_qspi_apb_controller_enable(cadence_qspi->iobase); + return; +} + +unsigned int calculate_ticks_for_ns(unsigned int ref_clk_hz, + unsigned int ns_val) +{ + unsigned int ticks; + ticks = ref_clk_hz; + ticks /= 1000; + ticks *= ns_val; + ticks += 999999; + ticks /= 1000000; + return ticks; +} + +void cadence_qspi_apb_delay(struct struct_cqspi *cadence_qspi, + unsigned int ref_clk, unsigned int sclk_hz) +{ + void __iomem *iobase = cadence_qspi->iobase; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct cqspi_flash_pdata *f_pdata = + &(pdata->f_pdata[cadence_qspi->current_cs]); + unsigned int ref_clk_ns; + unsigned int sclk_ns; + unsigned int tshsl, tchsh, tslch, tsd2d; + unsigned int reg; + unsigned int tsclk; + + pr_debug("%s %d %d\n", __func__, ref_clk, sclk_hz); + + /* Convert to ns. */ + ref_clk_ns = (1000000000) / pdata->master_ref_clk_hz; + + /* Convert to ns. */ + sclk_ns = (1000000000) / sclk_hz; + + /* calculate the number of ref ticks for one sclk tick */ + tsclk = (pdata->master_ref_clk_hz + sclk_hz - 1) / sclk_hz; + + tshsl = calculate_ticks_for_ns(pdata->master_ref_clk_hz, + f_pdata->tshsl_ns); + /* this particular value must be at least one sclk */ + if (tshsl < tsclk) + tshsl = tsclk; + + tchsh = calculate_ticks_for_ns(pdata->master_ref_clk_hz, + f_pdata->tchsh_ns); + tslch = calculate_ticks_for_ns(pdata->master_ref_clk_hz, + f_pdata->tslch_ns); + tsd2d = calculate_ticks_for_ns(pdata->master_ref_clk_hz, + f_pdata->tsd2d_ns); + + pr_debug("%s tshsl %d tsd2d %d tchsh %d tslch %d\n", + __func__, tshsl, tsd2d, tchsh, tslch); + + reg = ((tshsl & CQSPI_REG_DELAY_TSHSL_MASK) + << CQSPI_REG_DELAY_TSHSL_LSB); + reg |= ((tchsh & CQSPI_REG_DELAY_TCHSH_MASK) + << CQSPI_REG_DELAY_TCHSH_LSB); + reg |= ((tslch & CQSPI_REG_DELAY_TSLCH_MASK) + << CQSPI_REG_DELAY_TSLCH_LSB); + reg |= ((tsd2d & CQSPI_REG_DELAY_TSD2D_MASK) + << CQSPI_REG_DELAY_TSD2D_LSB); + CQSPI_WRITEL(reg, iobase + CQSPI_REG_DELAY); + + return; +} + +void cadence_qspi_switch_cs(struct struct_cqspi *cadence_qspi, + unsigned int cs) +{ + unsigned int reg; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct cqspi_flash_pdata *f_pdata = &(pdata->f_pdata[cs]); + void __iomem *iobase = cadence_qspi->iobase; + + pr_debug("%s\n", __func__); + cadence_qspi_apb_controller_disable(iobase); + + /* Configure page size and block size. */ + reg = CQSPI_READL(iobase + CQSPI_REG_SIZE); + /* clear the previous value */ + reg &= ~(CQSPI_REG_SIZE_PAGE_MASK << CQSPI_REG_SIZE_PAGE_LSB); + reg &= ~(CQSPI_REG_SIZE_BLOCK_MASK << CQSPI_REG_SIZE_BLOCK_LSB); + reg |= (f_pdata->page_size << CQSPI_REG_SIZE_PAGE_LSB); + reg |= (f_pdata->block_size << CQSPI_REG_SIZE_BLOCK_LSB); + CQSPI_WRITEL(reg, iobase + CQSPI_REG_SIZE); + + /* configure the chip select */ + cadence_qspi_apb_chipselect (iobase, cs, pdata->ext_decoder); + cadence_qspi_apb_controller_enable(iobase); + return; +} + +int cadence_qspi_apb_process_queue(struct struct_cqspi *cadence_qspi, + struct spi_device *spi, unsigned int n_trans, + struct spi_transfer **spi_xfer) +{ + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct spi_transfer *cmd_xfer = spi_xfer[0]; + struct spi_transfer *data_xfer = (n_trans >= 2) ? spi_xfer[1] : NULL; + void __iomem *iobase = cadence_qspi->iobase; + unsigned int sclk; + int ret = 0; + struct cqspi_flash_pdata *f_pdata; + + pr_debug("%s %d\n", __func__, n_trans); + + if (!cmd_xfer->len) { + pr_err("QSPI: SPI transfer length is 0.\n"); + return -EINVAL; + } + + /* Switch chip select. */ + if (cadence_qspi->current_cs != spi->chip_select) { + cadence_qspi->current_cs = spi->chip_select; + cadence_qspi_switch_cs(cadence_qspi, spi->chip_select); + } + + /* Setup baudrate divisor and delays */ + f_pdata = &(pdata->f_pdata[cadence_qspi->current_cs]); + sclk = cmd_xfer->speed_hz ? + cmd_xfer->speed_hz : spi->max_speed_hz; + cadence_qspi_apb_controller_disable(iobase); + cadence_qspi_apb_config_baudrate_div(iobase, + pdata->master_ref_clk_hz, sclk); + cadence_qspi_apb_delay(cadence_qspi, + pdata->master_ref_clk_hz, sclk); + cadence_qspi_apb_readdata_capture(iobase, 1, + f_pdata->read_delay); + cadence_qspi_apb_controller_enable(iobase); + + /* + * Use STIG command to send if the transfer length is less than + * 4 or if only one transfer. + */ + if ((cmd_xfer->len < 4) || (n_trans == 1)) { + /* STIG command */ + if (data_xfer && data_xfer->rx_buf) { + /* STIG read */ + ret = cadence_qspi_apb_command_read(iobase, + cmd_xfer->len, cmd_xfer->tx_buf, + data_xfer->len, data_xfer->rx_buf); + } else { + /* STIG write */ + ret = cadence_qspi_apb_command_write(iobase, + cmd_xfer->len, cmd_xfer->tx_buf); + } + } else if (cmd_xfer->len >= 4 && (n_trans == 2)){ + /* Indirect operation */ + if (data_xfer->rx_buf) { + /* Indirect read */ + ret = cadence_qspi_apb_indirect_read_setup(iobase, + pdata->qspi_ahb_phy, cmd_xfer->len, + cmd_xfer->tx_buf, spi->addr_width); + if (pdata->enable_dma && + !(data_xfer->len % CQSPI_FIFO_WIDTH)) { + ret = cadence_qspi_apb_indirect_read_dma( + cadence_qspi, data_xfer->len, + data_xfer->rx_buf); + } else { + ret = cadence_qspi_apb_indirect_read_execute( + cadence_qspi, data_xfer->len, + data_xfer->rx_buf); + } + } else { + /* Indirect write */ + ret = cadence_qspi_apb_indirect_write_setup( + iobase, pdata->qspi_ahb_phy, + cmd_xfer->len, cmd_xfer->tx_buf); + if (pdata->enable_dma && + !(data_xfer->len % CQSPI_FIFO_WIDTH)) { + ret = cadence_qspi_apb_indirect_write_dma( + cadence_qspi, data_xfer->len, + (unsigned char *)data_xfer->tx_buf); + } else { + ret = cadence_qspi_apb_indirect_write_execute( + cadence_qspi, data_xfer->len, + data_xfer->tx_buf); + } + } + } else { + pr_err("QSPI : Unknown SPI transfer.\n"); + return -EINVAL; + } + return ret; +} + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/spi/spi-cadence-qspi-apb.h b/drivers/spi/spi-cadence-qspi-apb.h new file mode 100644 index 0000000000000..9f481d58ab589 --- /dev/null +++ b/drivers/spi/spi-cadence-qspi-apb.h @@ -0,0 +1,227 @@ +/* + * Driver for Cadence QSPI Controller + * + * Copyright (C) 2012 Altera Corporation + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * OpenIB.org BSD license below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef __CADENCE_QSPI_APB_H__ +#define __CADENCE_QSPI_APB_H__ + +#include "spi-cadence-qspi.h" + +/* Operation timeout value */ +#define CQSPI_TIMEOUT_MS (5000) +#define CQSPI_POLL_IDLE_RETRY (3) + +#define CQSPI_FIFO_WIDTH (4) + +/* Controller sram size in word */ +#define CQSPI_REG_SRAM_RESV_WORDS (2) +#define CQSPI_REG_SRAM_PARTITION_WR (1) + +#define CQSPI_REG_SRAM_THRESHOLD_BYTES (50) + +/* Instruction type */ +#define CQSPI_INST_TYPE_SINGLE (0) +#define CQSPI_INST_TYPE_DUAL (1) +#define CQSPI_INST_TYPE_QUAD (2) + +#define CQSPI_DUMMY_CLKS_PER_BYTE (8) +#define CQSPI_DUMMY_BYTES_MAX (4) + +#define CQSPI_STIG_DATA_LEN_MAX (8) + +#define CQSPI_INDIRECTTRIGGER_ADDR_MASK (0xFFFFF) + +/* Register map */ +#define CQSPI_REG_CONFIG 0x00 +#define CQSPI_REG_CONFIG_ENABLE_MASK (1 << 0) +#define CQSPI_REG_CONFIG_DECODE_MASK (1 << 9) +#define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10 +#define CQSPI_REG_CONFIG_DMA_MASK (1 << 15) +#define CQSPI_REG_CONFIG_BAUD_LSB 19 +#define CQSPI_REG_CONFIG_IDLE_LSB 31 +#define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF +#define CQSPI_REG_CONFIG_BAUD_MASK 0xF + +#define CQSPI_REG_RD_INSTR 0x04 +#define CQSPI_REG_RD_INSTR_OPCODE_LSB 0 +#define CQSPI_REG_RD_INSTR_TYPE_INSTR_LSB 8 +#define CQSPI_REG_RD_INSTR_TYPE_ADDR_LSB 12 +#define CQSPI_REG_RD_INSTR_TYPE_DATA_LSB 16 +#define CQSPI_REG_RD_INSTR_MODE_EN_LSB 20 +#define CQSPI_REG_RD_INSTR_DUMMY_LSB 24 +#define CQSPI_REG_RD_INSTR_TYPE_INSTR_MASK 0x3 +#define CQSPI_REG_RD_INSTR_TYPE_ADDR_MASK 0x3 +#define CQSPI_REG_RD_INSTR_TYPE_DATA_MASK 0x3 +#define CQSPI_REG_RD_INSTR_DUMMY_MASK 0x1F + +#define CQSPI_REG_WR_INSTR 0x08 +#define CQSPI_REG_WR_INSTR_OPCODE_LSB 0 + +#define CQSPI_REG_DELAY 0x0C +#define CQSPI_REG_DELAY_TSLCH_LSB 0 +#define CQSPI_REG_DELAY_TCHSH_LSB 8 +#define CQSPI_REG_DELAY_TSD2D_LSB 16 +#define CQSPI_REG_DELAY_TSHSL_LSB 24 +#define CQSPI_REG_DELAY_TSLCH_MASK 0xFF +#define CQSPI_REG_DELAY_TCHSH_MASK 0xFF +#define CQSPI_REG_DELAY_TSD2D_MASK 0xFF +#define CQSPI_REG_DELAY_TSHSL_MASK 0xFF + +#define CQSPI_REG_READCAPTURE 0x10 +#define CQSPI_REG_READCAPTURE_BYPASS_LSB 0 +#define CQSPI_REG_READCAPTURE_DELAY_LSB 1 +#define CQSPI_REG_READCAPTURE_DELAY_MASK 0xF + +#define CQSPI_REG_SIZE 0x14 +#define CQSPI_REG_SIZE_ADDRESS_LSB 0 +#define CQSPI_REG_SIZE_PAGE_LSB 4 +#define CQSPI_REG_SIZE_BLOCK_LSB 16 +#define CQSPI_REG_SIZE_ADDRESS_MASK 0xF +#define CQSPI_REG_SIZE_PAGE_MASK 0xFFF +#define CQSPI_REG_SIZE_BLOCK_MASK 0x3F + +#define CQSPI_REG_SRAMPARTITION 0x18 +#define CQSPI_REG_INDIRECTTRIGGER 0x1C + +#define CQSPI_REG_DMA 0x20 +#define CQSPI_REG_DMA_SINGLE_LSB 0 +#define CQSPI_REG_DMA_BURST_LSB 8 +#define CQSPI_REG_DMA_SINGLE_MASK 0xFF +#define CQSPI_REG_DMA_BURST_MASK 0xFF + +#define CQSPI_REG_REMAP 0x24 +#define CQSPI_REG_MODE_BIT 0x28 + +#define CQSPI_REG_SDRAMLEVEL 0x2C +#define CQSPI_REG_SDRAMLEVEL_RD_LSB 0 +#define CQSPI_REG_SDRAMLEVEL_WR_LSB 16 +#define CQSPI_REG_SDRAMLEVEL_RD_MASK 0xFFFF +#define CQSPI_REG_SDRAMLEVEL_WR_MASK 0xFFFF + +#define CQSPI_REG_IRQSTATUS 0x40 +#define CQSPI_REG_IRQMASK 0x44 + +#define CQSPI_REG_INDIRECTRD 0x60 +#define CQSPI_REG_INDIRECTRD_START_MASK (1 << 0) +#define CQSPI_REG_INDIRECTRD_CANCEL_MASK (1 << 1) +#define CQSPI_REG_INDIRECTRD_DONE_MASK (1 << 5) + +#define CQSPI_REG_INDIRECTRDWATERMARK 0x64 +#define CQSPI_REG_INDIRECTRDSTARTADDR 0x68 +#define CQSPI_REG_INDIRECTRDBYTES 0x6C + +#define CQSPI_REG_CMDCTRL 0x90 +#define CQSPI_REG_CMDCTRL_EXECUTE_MASK (1 << 0) +#define CQSPI_REG_CMDCTRL_INPROGRESS_MASK (1 << 1) +#define CQSPI_REG_CMDCTRL_WR_BYTES_LSB 12 +#define CQSPI_REG_CMDCTRL_WR_EN_LSB 15 +#define CQSPI_REG_CMDCTRL_ADD_BYTES_LSB 16 +#define CQSPI_REG_CMDCTRL_ADDR_EN_LSB 19 +#define CQSPI_REG_CMDCTRL_RD_BYTES_LSB 20 +#define CQSPI_REG_CMDCTRL_RD_EN_LSB 23 +#define CQSPI_REG_CMDCTRL_OPCODE_LSB 24 +#define CQSPI_REG_CMDCTRL_WR_BYTES_MASK 0x7 +#define CQSPI_REG_CMDCTRL_ADD_BYTES_MASK 0x3 +#define CQSPI_REG_CMDCTRL_RD_BYTES_MASK 0x7 + +#define CQSPI_REG_INDIRECTWR 0x70 +#define CQSPI_REG_INDIRECTWR_START_MASK (1 << 0) +#define CQSPI_REG_INDIRECTWR_CANCEL_MASK (1 << 1) +#define CQSPI_REG_INDIRECTWR_DONE_MASK (1 << 5) + +#define CQSPI_REG_INDIRECTWRWATERMARK 0x74 +#define CQSPI_REG_INDIRECTWRSTARTADDR 0x78 +#define CQSPI_REG_INDIRECTWRBYTES 0x7C + +#define CQSPI_REG_CMDADDRESS 0x94 +#define CQSPI_REG_CMDREADDATALOWER 0xA0 +#define CQSPI_REG_CMDREADDATAUPPER 0xA4 +#define CQSPI_REG_CMDWRITEDATALOWER 0xA8 +#define CQSPI_REG_CMDWRITEDATAUPPER 0xAC + +/* Interrupt status bits */ +#define CQSPI_REG_IRQ_MODE_ERR (1 << 0) +#define CQSPI_REG_IRQ_UNDERFLOW (1 << 1) +#define CQSPI_REG_IRQ_IND_COMP (1 << 2) +#define CQSPI_REG_IRQ_IND_RD_REJECT (1 << 3) +#define CQSPI_REG_IRQ_WR_PROTECTED_ERR (1 << 4) +#define CQSPI_REG_IRQ_ILLEGAL_AHB_ERR (1 << 5) +#define CQSPI_REG_IRQ_WATERMARK (1 << 6) +#define CQSPI_REG_IRQ_IND_RD_OVERFLOW (1 << 12) + +#define CQSPI_IRQ_STATUS_ERR (CQSPI_REG_IRQ_MODE_ERR | \ + CQSPI_REG_IRQ_IND_RD_REJECT | \ + CQSPI_REG_IRQ_WR_PROTECTED_ERR | \ + CQSPI_REG_IRQ_ILLEGAL_AHB_ERR) + +#define CQSPI_IRQ_MASK_RD (CQSPI_REG_IRQ_MODE_ERR | \ + CQSPI_REG_IRQ_IND_RD_REJECT | \ + CQSPI_REG_IRQ_WATERMARK | \ + CQSPI_REG_IRQ_IND_RD_OVERFLOW | \ + CQSPI_REG_IRQ_IND_COMP) + +#define CQSPI_IRQ_MASK_WR (CQSPI_REG_IRQ_MODE_ERR | \ + CQSPI_REG_IRQ_WR_PROTECTED_ERR | \ + CQSPI_REG_IRQ_IND_COMP | \ + CQSPI_REG_IRQ_WATERMARK | \ + CQSPI_REG_IRQ_UNDERFLOW) + +#define CQSPI_IRQ_STATUS_MASK (0xFFFFFFFF) + +#define CQSPI_REG_IS_IDLE(base) \ + ((CQSPI_READL(base + CQSPI_REG_CONFIG) >> \ + CQSPI_REG_CONFIG_IDLE_LSB) & 0x1) + +#define CQSPI_CAL_DELAY(tdelay_ns, tref_ns, tsclk_ns) \ + ((((tdelay_ns) - (tsclk_ns)) / (tref_ns))) + +#define CQSPI_GET_RD_SRAM_LEVEL(reg_basse) \ + (((CQSPI_READL(reg_base + CQSPI_REG_SDRAMLEVEL)) >> \ + CQSPI_REG_SDRAMLEVEL_RD_LSB) & CQSPI_REG_SDRAMLEVEL_RD_MASK) + +#define CQSPI_READ_IRQ_STATUS(reg_base) \ + CQSPI_READL(reg_base + CQSPI_REG_IRQSTATUS) + +#define CQSPI_CLEAR_IRQ(reg_base, status) \ + CQSPI_WRITEL(status, reg_base + CQSPI_REG_IRQSTATUS) + +/* Functions call declaration */ +unsigned int cadence_qspi_apb_is_controller_ready(void *reg_base_addr); +void cadence_qspi_apb_controller_init(struct struct_cqspi *cadence_qspi); +int cadence_qspi_apb_process_queue(struct struct_cqspi *cadence_qspi, + struct spi_device *spi, unsigned int n_trans, + struct spi_transfer **spi_xfer); +void cadence_qspi_apb_controller_enable(void *reg_base_addr); +void cadence_qspi_apb_controller_disable(void *reg_base_addr); + +#endif /* __CADENCE_QSPI_APB_H__ */ diff --git a/drivers/spi/spi-cadence-qspi.c b/drivers/spi/spi-cadence-qspi.c new file mode 100644 index 0000000000000..1c3dcdb330f9e --- /dev/null +++ b/drivers/spi/spi-cadence-qspi.c @@ -0,0 +1,596 @@ +/* + * Driver for Cadence QSPI Controller + * + * Copyright Altera Corporation (C) 2012-2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-cadence-qspi.h" +#include "spi-cadence-qspi-apb.h" + +#define CADENCE_QSPI_NAME "cadence-qspi" + +unsigned int cadence_qspi_init_timeout(const unsigned long timeout_in_ms) +{ + return (jiffies + msecs_to_jiffies(timeout_in_ms)); +} + +unsigned int cadence_qspi_check_timeout(const unsigned long timeout) +{ + return (time_before(jiffies, timeout)); +} + +static irqreturn_t cadence_qspi_irq_handler(int this_irq, void *dev) +{ + struct struct_cqspi *cadence_qspi = dev; + + /* Read interrupt status */ + cadence_qspi->irq_status = CQSPI_READ_IRQ_STATUS(cadence_qspi->iobase); + + /* Clear interrupt */ + CQSPI_CLEAR_IRQ(cadence_qspi->iobase, cadence_qspi->irq_status); + + wake_up(&cadence_qspi->waitqueue); + + return IRQ_HANDLED; +} + +static void cadence_qspi_work(struct work_struct *work) +{ + struct struct_cqspi *cadence_qspi + = container_of(work, struct struct_cqspi, work); + unsigned long flags; + + pr_debug("%s\n", __func__); + + spin_lock_irqsave(&cadence_qspi->lock, flags); + while ((!list_empty(&cadence_qspi->msg_queue)) && + cadence_qspi->running) { + struct spi_message *spi_msg; + struct spi_device *spi; + struct spi_transfer *spi_xfer; + struct spi_transfer *xfer[CQSPI_MAX_TRANS]; + int status = 0; + int n_trans = 0; + int skip_xfer = 0; + + spi_msg = container_of(cadence_qspi->msg_queue.next, + struct spi_message, queue); + list_del_init(&spi_msg->queue); + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + spi = spi_msg->spi; + list_for_each_entry(spi_xfer, &spi_msg->transfers, + transfer_list) { + if (n_trans >= CQSPI_MAX_TRANS) { + dev_err(&spi->dev,"ERROR: Number of SPI " + "transfer is more than %d.\n", + CQSPI_MAX_TRANS); + /* Skip process the queue if number of + * transaction is greater than max 2. */ + skip_xfer = 1; + break; + } + xfer[n_trans++] = spi_xfer; + } + + if (!skip_xfer) { + + status = cadence_qspi_apb_process_queue(cadence_qspi, + spi, n_trans, xfer); + + if (!status) { + spi_msg->actual_length += xfer[0]->len; + if (n_trans > 1) + spi_msg->actual_length += xfer[1]->len; + } + + spi_msg->status = status; + spi_msg->complete(spi_msg->context); + } + spin_lock_irqsave(&cadence_qspi->lock, flags); + } + spin_unlock_irqrestore(&cadence_qspi->lock, flags); +} + +static int cadence_qspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct struct_cqspi *cadence_qspi = + spi_master_get_devdata(spi->master); + struct spi_transfer *spi_xfer; + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + unsigned long flags; + + pr_debug("%s\n", __func__); + + list_for_each_entry(spi_xfer, &msg->transfers, transfer_list) { + if (spi_xfer->speed_hz > (pdata->master_ref_clk_hz / 2)) { + dev_err(&spi->dev, "speed_hz%d greater than " + "maximum %dHz\n", + spi_xfer->speed_hz, + (pdata->master_ref_clk_hz / 2)); + msg->status = -EINVAL; + return -EINVAL; + } + } + + spin_lock_irqsave(&cadence_qspi->lock, flags); + + if (!cadence_qspi->running) { + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + return -ESHUTDOWN; + } + + msg->status = -EINPROGRESS; + msg->actual_length = 0; + + list_add_tail(&msg->queue, &cadence_qspi->msg_queue); + queue_work(cadence_qspi->workqueue, &cadence_qspi->work); + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + return 0; +} + +static int cadence_qspi_setup(struct spi_device *spi) +{ + pr_debug("%s\n", __func__); + + if (spi->chip_select > spi->master->num_chipselect) { + dev_err(&spi->dev, "%d chip select is out of range\n", + spi->chip_select); + return -EINVAL; + } + pr_debug("cadence_qspi : bits per word %d, chip select %d, " + "speed %d KHz\n", spi->bits_per_word, spi->chip_select, + spi->max_speed_hz); + return 0; +} + +static int cadence_qspi_start_queue(struct struct_cqspi *cadence_qspi) +{ + unsigned long flags; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&cadence_qspi->lock, flags); + + if (cadence_qspi->running) { + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + return -EBUSY; + } + + if (!cadence_qspi_apb_is_controller_ready (cadence_qspi->iobase) ) { + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + return -EBUSY; + } + + cadence_qspi->running = true; + + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + + queue_work(cadence_qspi->workqueue, &cadence_qspi->work); + return 0; +} + +static int cadence_qspi_stop_queue(struct struct_cqspi *cadence_qspi) +{ + unsigned long flags; + unsigned limit = 500; + int status = 0; + + spin_lock_irqsave(&cadence_qspi->lock, flags); + cadence_qspi->running = false; + /* We will wait until controller process all the queue and ensure the + * controller is not busy. */ + while ((!list_empty(&cadence_qspi->msg_queue) || + !cadence_qspi_apb_is_controller_ready(cadence_qspi->iobase)) + && limit--) { + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + msleep(10); + spin_lock_irqsave(&cadence_qspi->lock, flags); + } + + if (!list_empty(&cadence_qspi->msg_queue) || + !cadence_qspi_apb_is_controller_ready(cadence_qspi->iobase)) + status = -EBUSY; + + spin_unlock_irqrestore(&cadence_qspi->lock, flags); + return status; +} + +static int cadence_qspi_of_get_pdata(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *nc; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + struct cqspi_flash_pdata *f_pdata; + unsigned int cs; + unsigned int prop; + + if (of_property_read_u32(np, "bus-num", &prop)) { + dev_err(&pdev->dev, "couldn't determine bus-num\n"); + return -ENXIO; + } + pdata->bus_num = prop; + + if (of_property_read_u32(np, "num-chipselect", &prop)) { + dev_err(&pdev->dev, "couldn't determine num-chipselect\n"); + return -ENXIO; + } + pdata->num_chipselect = prop; + + if (of_property_read_u32(np, "ext-decoder", &prop)) { + dev_err(&pdev->dev, "couldn't determine ext-decoder\n"); + return -ENXIO; + } + pdata->ext_decoder = prop; + + if (of_property_read_u32(np, "fifo-depth", &prop)) { + dev_err(&pdev->dev, "couldn't determine fifo-depth\n"); + return -ENXIO; + } + pdata->fifo_depth = prop; + + pdata->enable_dma = of_property_read_bool(np, "dmas"); + dev_info(&pdev->dev, "DMA %senabled\n", + pdata->enable_dma ? "" : "NOT "); + + /* Get flash devices platform data */ + for_each_child_of_node(np, nc) { + if (of_property_read_u32(nc, "reg", &cs)) { + dev_err(&pdev->dev, "couldn't determine reg\n"); + return -ENXIO; + } + + f_pdata = &(pdata->f_pdata[cs]); + + if (of_property_read_u32(nc, "page-size", &prop)) { + dev_err(&pdev->dev, "couldn't determine page-size\n"); + return -ENXIO; + } + f_pdata->page_size = prop; + + if (of_property_read_u32(nc, "block-size", &prop)) { + dev_err(&pdev->dev, "couldn't determine block-size\n"); + return -ENXIO; + } + f_pdata->block_size = prop; + + if (of_property_read_u32(nc, "read-delay", &prop)) { + dev_err(&pdev->dev, "couldn't determine read-delay\n"); + return -ENXIO; + } + f_pdata->read_delay = prop; + + if (of_property_read_u32(nc, "tshsl-ns", &prop)) { + dev_err(&pdev->dev, "couldn't determine tshsl-ns\n"); + return -ENXIO; + } + f_pdata->tshsl_ns = prop; + + if (of_property_read_u32(nc, "tsd2d-ns", &prop)) { + dev_err(&pdev->dev, "couldn't determine tsd2d-ns\n"); + return -ENXIO; + } + f_pdata->tsd2d_ns = prop; + + if (of_property_read_u32(nc, "tchsh-ns", &prop)) { + dev_err(&pdev->dev, "couldn't determine tchsh-ns\n"); + return -ENXIO; + } + f_pdata->tchsh_ns = prop; + + if (of_property_read_u32(nc, "tslch-ns", &prop)) { + dev_err(&pdev->dev, "couldn't determine tslch-ns\n"); + return -ENXIO; + } + f_pdata->tslch_ns = prop; + } + return 0; +} + +static void cadence_qspi_dma_shutdown(struct struct_cqspi *cadence_qspi) +{ + struct platform_device *pdev = cadence_qspi->pdev; + struct cqspi_platform_data *pdata = pdev->dev.platform_data; + if (cadence_qspi->txchan) + dma_release_channel(cadence_qspi->txchan); + if (cadence_qspi->rxchan) + dma_release_channel(cadence_qspi->rxchan); + pdata->enable_dma = 0; + cadence_qspi->rxchan = cadence_qspi->txchan = NULL; +} + +static void cadence_qspi_dma_init(struct struct_cqspi *cadence_qspi) +{ + struct platform_device *pdev = cadence_qspi->pdev; + + cadence_qspi->txchan = dma_request_slave_channel(&pdev->dev, "tx"); + if (cadence_qspi->txchan) + dev_dbg(&pdev->dev, "TX channel %s %d selected\n", + dma_chan_name(cadence_qspi->txchan), + cadence_qspi->txchan->chan_id); + else + dev_err(&pdev->dev, "could not get TX dma channel\n"); + + + cadence_qspi->rxchan = dma_request_slave_channel(&pdev->dev, "rx"); + if (cadence_qspi->rxchan) + dev_dbg(&pdev->dev, "RX channel %s %d selected\n", + dma_chan_name(cadence_qspi->rxchan), + cadence_qspi->rxchan->chan_id); + else + dev_err(&pdev->dev, "could not get RX dma channel\n"); + + if (!cadence_qspi->rxchan || !cadence_qspi->txchan) { + /* Error, fall back to non-dma mode */ + cadence_qspi_dma_shutdown(cadence_qspi); + dev_info(&pdev->dev, "falling back to non-DMA operation\n"); + } +} + +static int cadence_qspi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct struct_cqspi *cadence_qspi; + struct resource *res; + struct resource *res_ahb; + struct cqspi_platform_data *pdata; + int status; + + pr_debug("%s\n", __func__); + pr_debug("%s %s %s\n", __func__, + pdev->name, pdev->id_entry->name); + + master = spi_alloc_master(&pdev->dev, sizeof(*cadence_qspi)); + if (master == NULL) { + dev_err(&pdev->dev, "spi_alloc_master failed\n"); + return -ENOMEM; + } + + master->mode_bits = SPI_CS_HIGH | SPI_CPOL | SPI_CPHA; + master->setup = cadence_qspi_setup; + master->transfer = cadence_qspi_transfer; + master->dev.of_node = pdev->dev.of_node; + + cadence_qspi = spi_master_get_devdata(master); + cadence_qspi->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cadence_qspi->iobase = devm_request_and_ioremap(&pdev->dev, res); + if (!cadence_qspi->iobase) { + dev_err(&pdev->dev, "devm_request_and_ioremap res 0 failed\n"); + status = -EADDRNOTAVAIL; + goto err_ioremap; + } + cadence_qspi->res = res; + + res_ahb = platform_get_resource(pdev, IORESOURCE_MEM, 1); + cadence_qspi->qspi_ahb_virt = + devm_request_and_ioremap(&pdev->dev, res_ahb); + if (!cadence_qspi->qspi_ahb_virt) { + dev_err(&pdev->dev, "devm_request_and_ioremap res 1 failed\n"); + status = -EADDRNOTAVAIL; + goto err_ahbremap; + } + cadence_qspi->res_ahb = res_ahb; + + cadence_qspi->workqueue = + create_singlethread_workqueue(dev_name(master->dev.parent)); + if (!cadence_qspi->workqueue) { + dev_err(&pdev->dev, "create_workqueue failed\n"); + status = -ENOMEM; + goto err_wq; + } + + cadence_qspi->running = false; + INIT_WORK(&cadence_qspi->work, cadence_qspi_work); + spin_lock_init(&cadence_qspi->lock); + INIT_LIST_HEAD(&cadence_qspi->msg_queue); + init_waitqueue_head(&cadence_qspi->waitqueue); + status = cadence_qspi_start_queue(cadence_qspi); + if (status) { + dev_err(&pdev->dev, "problem starting queue.\n"); + goto err_start_q; + } + + cadence_qspi->irq = platform_get_irq(pdev, 0); + + if (cadence_qspi->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + status = -ENXIO; + goto err_irq; + } + + status = request_irq(cadence_qspi->irq, cadence_qspi_irq_handler, + 0, pdev->name, cadence_qspi); + if (status) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err_irq; + } + + pdata = kmalloc(sizeof(struct cqspi_platform_data), GFP_KERNEL); + if (!pdata) { + status = -ENOMEM; + goto err_pdata; + } + + pdev->dev.platform_data = pdata; + pdata->qspi_ahb_phy = res_ahb->start; + + cadence_qspi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(cadence_qspi->clk)) { + dev_err(&pdev->dev, "cannot get qspi clk\n"); + return PTR_ERR(cadence_qspi->clk); + } + pdata->master_ref_clk_hz = clk_get_rate(cadence_qspi->clk); + + status = cadence_qspi_of_get_pdata(pdev); + if (status) { + dev_err(&pdev->dev, "Get platform data failed.\n"); + goto err_of; + } + + master->bus_num = pdata->bus_num; + master->num_chipselect = pdata->num_chipselect; + + platform_set_drvdata(pdev, master); + cadence_qspi_apb_controller_init(cadence_qspi); + cadence_qspi->current_cs = -1; + pr_debug("%s call spi_register_master\n", __func__); + status = spi_register_master(master); + if (status) { + dev_err(&pdev->dev, "spi_register_master failed\n"); + goto err_of; + } + + if (pdata->enable_dma) + cadence_qspi_dma_init(cadence_qspi); + + dev_info(&pdev->dev, "Cadence QSPI controller driver\n"); + return 0; + +err_of: + kfree(pdata); +err_pdata: + free_irq(cadence_qspi->irq, cadence_qspi); +err_start_q: +err_irq: + destroy_workqueue(cadence_qspi->workqueue); +err_wq: +err_ahbremap: +err_ioremap: + spi_master_put(master); + dev_err(&pdev->dev, "Cadence QSPI controller probe failed\n"); + return status; +} + +static int cadence_qspi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct struct_cqspi *cadence_qspi = spi_master_get_devdata(master); + + cadence_qspi_dma_shutdown(cadence_qspi); + + cadence_qspi_apb_controller_disable(cadence_qspi->iobase); + + platform_set_drvdata(pdev, NULL); + destroy_workqueue(cadence_qspi->workqueue); + free_irq(cadence_qspi->irq, cadence_qspi); + iounmap(cadence_qspi->iobase); + iounmap(cadence_qspi->qspi_ahb_virt); + release_mem_region(cadence_qspi->res->start, + resource_size(cadence_qspi->res)); + release_mem_region(cadence_qspi->res_ahb->start, + resource_size(cadence_qspi->res_ahb)); + kfree(pdev->dev.platform_data); + spi_unregister_master(master); + spi_master_put(master); + + return 0; +} + +#ifdef CONFIG_PM + +static int cadence_qspi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct struct_cqspi *cadence_qspi = spi_master_get_devdata(master); + int status=0; + + /* Stop the queue */ + status = cadence_qspi_stop_queue(cadence_qspi); + if (status != 0) + return status; + /* Disable the controller to conserve the power */ + cadence_qspi_apb_controller_disable(cadence_qspi->iobase); + return 0; +} + +static int cadence_qspi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct struct_cqspi *cadence_qspi = spi_master_get_devdata(master); + int status = 0; + + cadence_qspi_apb_controller_enable(cadence_qspi->iobase); + /* Start the queue running */ + status = cadence_qspi_start_queue(cadence_qspi); + if (status != 0) { + cadence_qspi_apb_controller_disable(cadence_qspi->iobase); + dev_err(dev, "problem starting queue (%d)\n", status); + return status; + } + return 0; +} +static struct dev_pm_ops cadence_qspi__dev_pm_ops = +{ + .suspend = cadence_qspi_suspend, + .resume = cadence_qspi_resume, +}; +#define CADENCE_QSPI_DEV_PM_OPS (&cadence_qspi__dev_pm_ops) +#else +#define CADENCE_QSPI_DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static struct of_device_id cadence_qspi_of_match[] = { + { .compatible = "cadence,qspi",}, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, cadence_qspi_of_match); +#else +#define cadence_qspi_of_match NULL +#endif /* CONFIG_OF */ + + +static struct platform_driver cadence_qspi_platform_driver = +{ + .probe = cadence_qspi_probe, + .remove = cadence_qspi_remove, + .driver = { + .name = CADENCE_QSPI_NAME, + .owner = THIS_MODULE, + .pm = CADENCE_QSPI_DEV_PM_OPS, + .of_match_table = cadence_qspi_of_match, + }, +}; + +static int __init cadence_qspi_init(void) +{ + return platform_driver_register(&cadence_qspi_platform_driver); +} +static void __exit cadence_qspi_exit(void) +{ + platform_driver_unregister(&cadence_qspi_platform_driver); +} + +module_init(cadence_qspi_init); +module_exit(cadence_qspi_exit); + +MODULE_AUTHOR("Ley Foon Tan "); +MODULE_DESCRIPTION("Cadence QSPI Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" CADENCE_QSPI_NAME); diff --git a/drivers/spi/spi-cadence-qspi.h b/drivers/spi/spi-cadence-qspi.h new file mode 100644 index 0000000000000..6ce5046a18b2f --- /dev/null +++ b/drivers/spi/spi-cadence-qspi.h @@ -0,0 +1,90 @@ +/* + * Driver for Cadence QSPI Controller + * + * Copyright Altera Corporation (C) 2012-2014. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __CADENCE_QSPI_H__ +#define __CADENCE_QSPI_H__ + +#define CQSPI_MAX_TRANS (2) + +#define CQSPI_MAX_CHIP_SELECT (16) + +struct cqspi_flash_pdata { + unsigned int page_size; + unsigned int block_size; + unsigned int read_delay; + unsigned int tshsl_ns; + unsigned int tsd2d_ns; + unsigned int tchsh_ns; + unsigned int tslch_ns; +}; + +struct cqspi_platform_data { + unsigned int bus_num; + unsigned int num_chipselect; + unsigned int qspi_ahb_phy; + unsigned int master_ref_clk_hz; + unsigned int ext_decoder; + unsigned int fifo_depth; + unsigned int enable_dma; + unsigned int tx_dma_peri_id; + unsigned int rx_dma_peri_id; + struct cqspi_flash_pdata f_pdata[CQSPI_MAX_CHIP_SELECT]; +}; + +struct struct_cqspi +{ + struct work_struct work; + struct workqueue_struct *workqueue; + wait_queue_head_t waitqueue; + struct list_head msg_queue; + struct platform_device *pdev; + + struct clk *clk; + + /* lock protects queue and registers */ + spinlock_t lock; + /* Virtual base address of the controller */ + void __iomem *iobase; + /* QSPI AHB virtual address */ + void __iomem *qspi_ahb_virt; + /* phys mem */ + struct resource *res; + /* AHB phys mem */ + struct resource *res_ahb; + /* Interrupt */ + int irq; + /* Interrupt status */ + unsigned int irq_status; + /* Current chip select */ + int current_cs; + /* Is queue running */ + bool running; + /* DMA support */ + struct dma_chan *txchan; + struct dma_chan *rxchan; + dma_addr_t dma_addr; + int dma_done; +}; + +/* Kernel function hook */ +#define CQSPI_WRITEL __raw_writel +#define CQSPI_READL __raw_readl +unsigned int cadence_qspi_init_timeout(const unsigned long timeout_in_ms); +unsigned int cadence_qspi_check_timeout(const unsigned long timeout); + +#endif /* __CADENCE_QSPI_H__ */ diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c index a5cba14ac3d2f..5e99e912736fa 100644 --- a/drivers/spi/spi-dw-mmio.c +++ b/drivers/spi/spi-dw-mmio.c @@ -17,6 +17,10 @@ #include #include #include +#include +#include +#include +#include #include "spi-dw.h" @@ -33,7 +37,9 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) struct dw_spi *dws; struct resource *mem; int ret; - +#ifdef CONFIG_OF + unsigned int prop; +#endif dwsmmio = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mmio), GFP_KERNEL); if (!dwsmmio) @@ -48,10 +54,11 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) return -EINVAL; } - dws->regs = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(dws->regs)) { - dev_err(&pdev->dev, "SPI region map failed\n"); - return PTR_ERR(dws->regs); + dws->regs = ioremap_nocache(mem->start, resource_size(mem)); + dws->paddr = mem->start; + if (!dws->regs) { + dev_err(&pdev->dev, "SPI region already mapped\n"); + return -ENOMEM; } dws->irq = platform_get_irq(pdev, 0); @@ -67,8 +74,23 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) if (ret) return ret; - dws->bus_num = pdev->id; +#ifdef CONFIG_OF + if(of_property_read_u32(pdev->dev.of_node, "num-chipselect", &prop)) { + dev_err(&pdev->dev, "couldn't determine num-chipselect\n"); + return -ENXIO; + } + dws->num_cs = prop; + + if(of_property_read_u32(pdev->dev.of_node, "bus-num", &prop)) { + dev_err(&pdev->dev, "couldn't determine bus-num\n"); + return -ENXIO; + } + dws->bus_num = prop; +#else dws->num_cs = 4; + dws->bus_num = pdev->id; +#endif + dws->max_freq = clk_get_rate(dwsmmio->clk); if (pdev->dev.of_node) { @@ -91,6 +113,11 @@ static int dw_spi_mmio_probe(struct platform_device *pdev) } } } +#ifdef CONFIG_SPI_DW_PL330_DMA + ret = dw_spi_pl330_init(dws); + if (ret) + goto out; +#endif ret = dw_spi_add_host(&pdev->dev, dws); if (ret) @@ -114,12 +141,23 @@ static int dw_spi_mmio_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static struct of_device_id dw_spi_mmio_of_match[] = { + { .compatible = "snps,dw-spi-mmio", }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match); +#else +#define dw_spi_mmio_of_match NULL +#endif /* CONFIG_OF */ + static struct platform_driver dw_spi_mmio_driver = { .probe = dw_spi_mmio_probe, .remove = dw_spi_mmio_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, + .of_match_table = dw_spi_mmio_of_match, }, }; module_platform_driver(dw_spi_mmio_driver); diff --git a/drivers/spi/spi-dw-pl330.c b/drivers/spi/spi-dw-pl330.c new file mode 100644 index 0000000000000..413a382935c34 --- /dev/null +++ b/drivers/spi/spi-dw-pl330.c @@ -0,0 +1,226 @@ +/* + * DMA handling for DW core with DMA PL330 controller + * + * Modified from linux/driver/spi/spi-dw-mid.c + * + * Copyright (c) 2012, Altera Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "spi-dw.h" +#include +#include + + +/*Burst size configuration*/ +enum spi_pl330_brst_sz { + PL330_DMA_BRSTSZ_1B = 0x1, + PL330_DMA_BRSTSZ_2B = 0x2, +}; + +/* TX & RX FIFO depth supported by HW */ +#define SSI_FIFO_DEPTH 0xFF + +/* Maximum burst length + Note: Can be up to 16, but now is default to 1 in the PL330 driver. + Burst transfer is not supported in PL330 driver */ +#define SSI_DMA_MAXBURST 16 + + +static int spi_pl330_dma_chan_alloc(struct dw_spi *dws) +{ + struct device_node *np = dws->master->dev.of_node; + void *filter_param_rx, *filter_param_tx; + dma_cap_mask_t mask; + int lenp; + + + /* If DMA channel already allocated */ + if (dws->rxchan && dws->txchan) + return 0; + + filter_param_tx = of_find_property(np, "tx-dma-channel", &lenp); + if (!filter_param_tx) + return -1; + filter_param_rx = of_find_property(np, "rx-dma-channel", &lenp); + if (!filter_param_rx) + return -1; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* 1. Init rx channel */ + dws->rxchan = dma_request_channel(mask, pl330_filter, filter_param_rx); + while (!dws->rxchan) { + /* all DMA channels are busy, try again */ + msleep(10); + dws->rxchan = dma_request_channel(mask, pl330_filter, filter_param_rx); + } + + /* 2. Init tx channel */ + dws->txchan = dma_request_channel(mask, pl330_filter, filter_param_tx); + while (!dws->txchan) { + /* all DMA channels are busy, try again */ + msleep(10); + dws->txchan = dma_request_channel(mask, pl330_filter, filter_param_tx); + } + + dws->dma_inited = 1; + + return 0; +} + +static void spi_pl330_dma_chan_release(struct dw_spi *dws) +{ + dma_release_channel(dws->txchan); + dma_release_channel(dws->rxchan); + dws->txchan = 0; + dws->rxchan = 0; + dws->dma_inited = 0; +} + +/* + * dws->dma_chan_done is cleared before the dma transfer starts, + * callback for rx/tx channel will each increment it by 1. + * Reaching 2 means the whole spi transaction is done. + */ +static void spi_pl330_dma_done(void *arg) +{ + struct dw_spi *dws = arg; + + if (++dws->dma_chan_done != 2) + return; + dw_spi_xfer_done(dws); +} + +static int spi_pl330_dma_transfer(struct dw_spi *dws, int cs_change) +{ + struct dma_async_tx_descriptor *txdesc = NULL, *rxdesc = NULL; + struct dma_chan *txchan, *rxchan; + struct dma_slave_config txconf, rxconf; + u16 dma_ctrl = 0; + + /* 1. setup DMA related registers */ + if (cs_change) { + spi_enable_chip(dws, 0); + /* Setup peripheral's burst watermark for TX and RX FIFO */ + dw_writew(dws, DW_SPI_DMARDLR, SSI_DMA_MAXBURST - 1); + dw_writew(dws, DW_SPI_DMATDLR, SSI_FIFO_DEPTH - SSI_DMA_MAXBURST); + + if (dws->tx_dma) + dma_ctrl |= 0x2; + if (dws->rx_dma) + dma_ctrl |= 0x1; + dw_writew(dws, DW_SPI_DMACR, dma_ctrl); + spi_enable_chip(dws, 1); + } + + dws->dma_chan_done = 0; + txchan = dws->txchan; + rxchan = dws->rxchan; + + /* 2. Prepare the TX dma transfer */ + txconf.direction = DMA_MEM_TO_DEV; + txconf.dst_addr = dws->dma_addr; + /* Note: By default the burst_len (dst_maxburst) for DMA_MEM_TO_DEV is set + to 1 and the burst_size (src_addr_width) for memory is set to + peripheral's configuration in PL330 driver (driver/dma/pl330.c). + Therefore the config listed below can be skipped + i. txconf.dst_maxburst + ii. txconf.src_addr_width + Max DMA width is 16-bit + */ + if (dws->dma_width == 1) + txconf.dst_addr_width = PL330_DMA_BRSTSZ_1B; + else + txconf.dst_addr_width = PL330_DMA_BRSTSZ_2B; + + txchan->device->device_control(txchan, DMA_SLAVE_CONFIG, + (unsigned long) &txconf); + + memset(&dws->tx_sgl, 0, sizeof(dws->tx_sgl)); + dws->tx_sgl.dma_address = dws->tx_dma; + dws->tx_sgl.length = dws->len; + + txdesc = txchan->device->device_prep_slave_sg(txchan, + &dws->tx_sgl, + 1, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT, + NULL); + txdesc->callback = spi_pl330_dma_done; + txdesc->callback_param = dws; + + /* 3. Prepare the RX dma transfer */ + rxconf.direction = DMA_DEV_TO_MEM; + rxconf.src_addr = dws->dma_addr; + /* Note: By default the burst_len (src_maxburst) for DMA_DEV_TO_MEM is set + to 1 and the burst_size (dst_addr_width) for memory is set to + peripheral's configuration in PL330 driver (driver/dma/pl330.c). + Therefore the config listed below can be skipped + txconf.src_maxburst + txconf.dst_addr_width + */ + if (dws->dma_width == 1) + rxconf.src_addr_width = PL330_DMA_BRSTSZ_1B; + else + rxconf.src_addr_width = PL330_DMA_BRSTSZ_2B; + + rxchan->device->device_control(rxchan, DMA_SLAVE_CONFIG, + (unsigned long) &rxconf); + + memset(&dws->rx_sgl, 0, sizeof(dws->rx_sgl)); + dws->rx_sgl.dma_address = dws->rx_dma; + dws->rx_sgl.length = dws->len; + + rxdesc = rxchan->device->device_prep_slave_sg(rxchan, + &dws->rx_sgl, + 1, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT, + NULL); + rxdesc->callback = spi_pl330_dma_done; + rxdesc->callback_param = dws; + + /* rx must be started before tx due to spi instinct */ + rxdesc->tx_submit(rxdesc); + dma_async_issue_pending(rxchan); + txdesc->tx_submit(txdesc); + dma_async_issue_pending(txchan); + + return 0; +} + +static struct dw_spi_dma_ops pl330_dma_ops = { + .dma_transfer = spi_pl330_dma_transfer, + .dma_chan_alloc = spi_pl330_dma_chan_alloc, + .dma_chan_release = spi_pl330_dma_chan_release, +}; + +int dw_spi_pl330_init(struct dw_spi *dws) +{ + dws->fifo_len = SSI_FIFO_DEPTH; + dws->dma_ops = &pl330_dma_ops; + + return 0; +} diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c index 29f33143b7956..e017c443e9fb2 100644 --- a/drivers/spi/spi-dw.c +++ b/drivers/spi/spi-dw.c @@ -239,6 +239,43 @@ static void *next_transfer(struct dw_spi *dws) */ static int map_dma_buffers(struct dw_spi *dws) { +#ifdef CONFIG_SPI_DW_PL330_DMA + if (!dws->dma_inited + || !dws->cur_chip->enable_dma + || !dws->dma_ops) + return 0; + + if (dws->cur_msg->is_dma_mapped) { + if (dws->cur_transfer->tx_dma) + dws->tx_dma = dws->cur_transfer->tx_dma; + + if (dws->cur_transfer->rx_dma) + dws->rx_dma = dws->cur_transfer->rx_dma; + } else { + if (dws->cur_transfer->tx_buf != NULL) { + dws->tx_dma = dma_map_single(dws->master->dev, + (void *)dws->cur_transfer->tx_buf, + dws->cur_transfer->len, + DMA_TO_DEVICE); + if (dma_mapping_error(dws->master->dev, dws->tx_dma)) { + dev_err(&dws->master->dev, "dma_map_single Tx failed\n"); + return 0; + } + } + + if (dws->cur_transfer->rx_buf != NULL) { + dws->rx_dma = dma_map_single(dws->master->_dev, + dws->cur_transfer->rx_buf, + dws->cur_transfer->len, DMA_FROM_DEVICE); + if (dma_mapping_error(dws->master->dev, dws->rx_dma)) { + dev_err(&dws->master->dev, "dma_map_single Rx failed\n"); + dma_unmap_single(dws->master->dev, dws->tx_dma, + dws->cur_transfer->len, DMA_TO_DEVICE); + return 0; + } + } + } +#else if (!dws->cur_msg->is_dma_mapped || !dws->dma_inited || !dws->cur_chip->enable_dma @@ -250,6 +287,7 @@ static int map_dma_buffers(struct dw_spi *dws) if (dws->cur_transfer->rx_dma) dws->rx_dma = dws->cur_transfer->rx_dma; +#endif return 1; } @@ -271,7 +309,7 @@ static void giveback(struct dw_spi *dws) transfer_list); if (!last_transfer->cs_change) - spi_chip_sel(dws, dws->cur_msg->spi, 0); + spi_chip_sel(dws, msg->spi, 0); spi_finalize_current_message(dws->master); } @@ -291,6 +329,15 @@ void dw_spi_xfer_done(struct dw_spi *dws) /* Update total byte transferred return count actual bytes read */ dws->cur_msg->actual_length += dws->len; + if (dws->dma_mapped) { + if (!dws->cur_msg->is_dma_mapped) { + dma_unmap_single(&dws->master->dev, dws->rx_dma, + dws->cur_transfer->len, DMA_FROM_DEVICE); + dma_unmap_single(&dws->master->dev, dws->tx_dma, + dws->cur_transfer->len, DMA_TO_DEVICE); + } + } + /* Move to next transfer */ dws->cur_msg->state = next_transfer(dws); @@ -547,8 +594,7 @@ static int dw_spi_setup(struct spi_device *spi) /* Only alloc on first setup */ chip = spi_get_ctldata(spi); if (!chip) { - chip = devm_kzalloc(&spi->dev, sizeof(struct chip_data), - GFP_KERNEL); + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); if (!chip) return -ENOMEM; spi_set_ctldata(spi, chip); @@ -606,6 +652,14 @@ static int dw_spi_setup(struct spi_device *spi) return 0; } +static void dw_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + + kfree(chip); + spi_set_ctldata(spi, NULL); +} + /* Restart the controller, disable all interrupts, clean rx fifo */ static void spi_hw_init(struct dw_spi *dws) { @@ -661,6 +715,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) master->bus_num = dws->bus_num; master->num_chipselect = dws->num_cs; master->setup = dw_spi_setup; + master->cleanup = dw_spi_cleanup; master->transfer_one_message = dw_spi_transfer_one_message; master->max_speed_hz = dws->max_freq; diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h index 6d2acad34f64f..e3feb6e43a023 100644 --- a/drivers/spi/spi-dw.h +++ b/drivers/spi/spi-dw.h @@ -88,6 +88,8 @@ struct dw_spi_dma_ops { int (*dma_init)(struct dw_spi *dws); void (*dma_exit)(struct dw_spi *dws); int (*dma_transfer)(struct dw_spi *dws, int cs_change); + int (*dma_chan_alloc)(struct dw_spi *dws); + void (*dma_chan_release)(struct dw_spi *dws); }; struct dw_spi { @@ -234,4 +236,5 @@ extern void dw_spi_xfer_done(struct dw_spi *dws); /* platform related setup */ extern int dw_spi_mid_init(struct dw_spi *dws); /* Intel MID platforms */ +extern int dw_spi_pl330_init(struct dw_spi *dws); /* PL330 support setup */ #endif /* DW_SPI_HEADER_H */ diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index b24aa010f68c5..420f8b4563d77 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -175,6 +175,12 @@ config BFIN_JTAG_COMM_CONSOLE bool "Console on Blackfin JTAG" depends on BFIN_JTAG_COMM=y +config NEWHAVEN_LCD + tristate "NEWHAVEN LCD" + depends on I2C + help + Add support for a TTY device on a Newhaven I2C LCD device. + config SERIAL_NONSTANDARD bool "Non-standard serial port support" depends on HAS_IOMEM diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 58ad1c05b7f8f..f6a3d565ef2c6 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o +obj-$(CONFIG_NEWHAVEN_LCD) += newhaven_lcd.o obj-y += ipwireless/ diff --git a/drivers/tty/newhaven_lcd.c b/drivers/tty/newhaven_lcd.c new file mode 100644 index 0000000000000..56d1c2d5e2bbe --- /dev/null +++ b/drivers/tty/newhaven_lcd.c @@ -0,0 +1,640 @@ +/* + * TTY on a LCD connected to I2C + * Supports Newhaven NHD-0216K3Z-NSW-BBW + * + * Copyright (C) 2013 Altera Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define DRV_NAME "lcd-comm" +#define DEV_NAME "ttyLCD" + +#include +#include +#include +#include +#include +#include + +#define LCD_COMMAND 0xfe +#define LCD_DISPLAY_ON 0x41 +#define LCD_DISPLAY_OFF 0x42 +#define LCD_SET_CURSOR 0x45 +#define LCD_BACKSPACE 0x4e +#define LCD_CLEAR_SCREEN 0x51 +#define LCD_BRIGHTNESS 0x53 +#define LCD_CUSTOM_CHAR 0x54 +#define LCD_BYTES_PER_FONT 8 +#define LCD_BYTES_PER_FONT_CMD (LCD_BYTES_PER_FONT + 3) + +#define LCD_BRIGHTNESS_MIN 1 +#define LCD_BRIGHTNESS_MAX 8 + +#define ASCII_BS 0x08 +#define ASCII_LF 0x0a +#define ASCII_CR 0x0d +#define ASCII_ESC 0x1b +#define ASCII_SPACE 0x20 +#define ASCII_BACKSLASH 0x5c +#define ASCII_TILDE 0x7e + +/* The NewHaven display has 8 custom characters that are user-loadable init + its cg ram. */ +#define CUSTOM_BACKSLASH 0x00 +#define CUSTOM_TILDE 0x01 + +struct custom_font { + u8 mapping; + u8 font[LCD_BYTES_PER_FONT]; +}; + +struct custom_font custom_fonts[] = { + { CUSTOM_BACKSLASH, { 0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, }, }, + { CUSTOM_TILDE, { 0x00, 0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00, }, }, +}; + +struct lcd { + struct device *dev; + struct i2c_client *client; + struct tty_driver *lcd_tty_driver; + struct tty_port port; + unsigned int width; + unsigned int height; + unsigned int brightness; + char *buffer; + unsigned int top_line; + unsigned int cursor_line; + unsigned int cursor_col; +}; + +#define MAX_LCDS 1 +static struct lcd lcd_data_static[MAX_LCDS]; + +static int lcd_cmd_no_params(struct lcd *lcd_data, u8 cmd) +{ + int count; + u8 buf[2] = {LCD_COMMAND, cmd}; + + count = i2c_master_send(lcd_data->client, buf, sizeof(buf)); + if (count != sizeof(buf)) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + msleep(1); + return 0; +} + +static int lcd_cmd_one_param(struct lcd *lcd_data, u8 cmd, u8 param) +{ + int count; + u8 buf[3] = {LCD_COMMAND, cmd, param}; + + count = i2c_master_send(lcd_data->client, buf, sizeof(buf)); + if (count != sizeof(buf)) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + msleep(1); + return 0; +} + +static int lcd_cmd_backlight_brightness(struct lcd *lcd_data, u8 brightness) +{ + return lcd_cmd_one_param(lcd_data, LCD_BRIGHTNESS, brightness); +} + +static int lcd_cmd_display_on(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_DISPLAY_ON); +} + +static int lcd_cmd_display_off(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_DISPLAY_OFF); +} + +static int lcd_cmd_clear_screen(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_CLEAR_SCREEN); +} + +static int lcd_cmd_backspace(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_BACKSPACE); +} + +/* Note that this has to happen early on or the LCD module will not + process the command */ +static int lcd_load_custom_fonts(struct lcd *lcd_data) +{ + u8 buf[LCD_BYTES_PER_FONT_CMD]; + int count, i; + + for (i = 0; i < ARRAY_SIZE(custom_fonts); i++) { + buf[0] = LCD_COMMAND; + buf[1] = LCD_CUSTOM_CHAR; + buf[2] = custom_fonts[i].mapping; + memcpy(buf + 3, &custom_fonts[i].font, LCD_BYTES_PER_FONT); + + count = i2c_master_send(lcd_data->client, buf, sizeof(buf)); + if (count != sizeof(buf)) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + } + return 0; +} + +static char lcd_translate_printable_char(char val) +{ + if (val == ASCII_BACKSLASH) + return CUSTOM_BACKSLASH; + else if (val == ASCII_TILDE) + return CUSTOM_TILDE; + + return val; +} + +/* From NHD-0216K3Z-NSW-BBY Display Module datasheet. */ +#define LCD_CURSOR_LINE_MULTIPLIER 0x40 + +static int lcd_cmd_set_cursor(struct lcd *lcd_data, u8 line, u8 col) +{ + u8 cursor; + + BUG_ON((line >= lcd_data->height) || (col >= lcd_data->width)); + + cursor = col + (LCD_CURSOR_LINE_MULTIPLIER * line); + return lcd_cmd_one_param(lcd_data, LCD_SET_CURSOR, cursor); +} + +/* + * Map a line on the lcd display to a line on the buffer. + * Note that the top line on the display (line 0) may not be line 0 on the + * buffer due to scrolling. + */ +static unsigned int lcd_line_to_buf_line(struct lcd *lcd_data, + unsigned int line) +{ + unsigned int buf_line; + + buf_line = line + lcd_data->top_line; + + if (buf_line >= lcd_data->height) + buf_line -= lcd_data->height; + + return buf_line; +} + +/* Returns a pointer to the line, column position in the lcd buffer */ +static char *lcd_buf_pointer(struct lcd *lcd_data, unsigned int line, + unsigned int col) +{ + unsigned int buf_line; + char *buf; + + if ((lcd_data->cursor_line >= lcd_data->height) || + (lcd_data->cursor_col >= lcd_data->width)) + return lcd_data->buffer; + + buf_line = lcd_line_to_buf_line(lcd_data, line); + + buf = lcd_data->buffer + (buf_line * lcd_data->width) + col; + + return buf; +} + +static void lcd_clear_buffer_line(struct lcd *lcd_data, int line) +{ + char *buf; + + BUG_ON(line >= lcd_data->height); + + buf = lcd_buf_pointer(lcd_data, line, 0); + memset(buf, ASCII_SPACE, lcd_data->width); +} + +static void lcd_clear_buffer(struct lcd *lcd_data) +{ + memset(lcd_data->buffer, ASCII_SPACE, + lcd_data->width * lcd_data->height); + lcd_data->cursor_line = 0; + lcd_data->cursor_col = 0; + lcd_data->top_line = 0; +} + +static void lcd_reprint_one_line(struct lcd *lcd_data, u8 line) +{ + char *buf = lcd_buf_pointer(lcd_data, line, 0); + + lcd_cmd_set_cursor(lcd_data, line, 0); + i2c_master_send(lcd_data->client, buf, lcd_data->width); +} + +static void lcd_print_top_n_lines(struct lcd *lcd_data, u8 lines) +{ + unsigned int disp_line = 0; + + while (disp_line < lines) + lcd_reprint_one_line(lcd_data, disp_line++); +} + +static void lcd_add_char_at_cursor(struct lcd *lcd_data, char val) +{ + char *buf; + + buf = lcd_buf_pointer(lcd_data, lcd_data->cursor_line, + lcd_data->cursor_col); + + *buf = val; + + if (lcd_data->cursor_col < (lcd_data->width - 1)) + lcd_data->cursor_col++; +} + +static void lcd_crlf(struct lcd *lcd_data) +{ + if (lcd_data->cursor_line < (lcd_data->height - 1)) { + /* Next line is blank, carriage return to beginning of line. */ + lcd_data->cursor_line++; + if (lcd_data->cursor_line >= lcd_data->height) + lcd_data->cursor_line = 0; + + } else { + /* Display is full. Scroll up one line. */ + lcd_data->top_line++; + if (lcd_data->top_line >= lcd_data->height) + lcd_data->top_line = 0; + + lcd_cmd_clear_screen(lcd_data); + lcd_clear_buffer_line(lcd_data, lcd_data->cursor_line); + lcd_print_top_n_lines(lcd_data, lcd_data->height); + } + + lcd_cmd_set_cursor(lcd_data, lcd_data->height - 1, 0); + lcd_data->cursor_col = 0; +} + +static void lcd_backspace(struct lcd *lcd_data) +{ + if (lcd_data->cursor_col > 0) { + lcd_cmd_backspace(lcd_data); + lcd_data->cursor_col--; + } +} + +static int lcd_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct lcd *lcd_data = tty->driver_data; + int buf_i = 0, left; + char val; + +#ifdef DEBUG + char *dbgbuf = kzalloc(count + 1, GFP_KERNEL); + strncpy(dbgbuf, buf, count); + pr_debug("\n%s: count=%d buf[0]=%02x --->%s<---\n", __func__, count, + buf[0], dbgbuf); +#endif /* DEBUG */ + + if (count == 0) { +#ifdef DEBUG + kfree(dbgbuf); +#endif /* DEBUG */ + return 0; + } + + while (buf_i < count) { + left = count - buf_i; + + /* process displayable chars */ + if ((0x20 <= buf[buf_i]) && (buf[buf_i] <= 0x7f)) { + while ((buf_i < count) && + ((0x20 <= buf[buf_i]) && (buf[buf_i] <= 0x7f))) { + val = lcd_translate_printable_char(buf[buf_i]); + lcd_add_char_at_cursor(lcd_data, val); + buf_i++; + } + + /* flush the line out to the display when we get to eol */ + lcd_reprint_one_line(lcd_data, lcd_data->cursor_line); + + /* + * ECMA-48 CSI sequences (from console_codes man page) + * + * ESC [ 2 J : erase whole display. + * ESC [ 2 K : erase whole line. + */ + } else if (buf[buf_i] == ASCII_ESC) { + if ((left >= 4) && + (buf[buf_i + 1] == '[') && + (buf[buf_i + 2] == '2') && + (buf[buf_i + 3] == 'J')) { + pr_debug("ESC [2J = clear screan\n"); + lcd_clear_buffer(lcd_data); + lcd_cmd_clear_screen(lcd_data); + buf_i += 4; + + } else if ((left >= 4) && + (buf[buf_i + 1] == '[') && + (buf[buf_i + 2] == '2') && + (buf[buf_i + 3] == 'K')) { + pr_debug("ESC [2K = clear line\n"); + lcd_clear_buffer_line(lcd_data, lcd_data->cursor_line); + lcd_reprint_one_line(lcd_data, lcd_data->cursor_line); + lcd_cmd_set_cursor(lcd_data, lcd_data->cursor_line, 0); + lcd_data->cursor_col = 0; + buf_i += 4; + + } else { + pr_debug("Unsupported escape sequence\n"); + buf_i++; + } + + } else if ((left >= 2) && + (buf[buf_i] == ASCII_CR) && (buf[buf_i + 1] == ASCII_LF)) { + pr_debug("ASCII_CR/LF\n"); + lcd_crlf(lcd_data); + buf_i += 2; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_CR)) { + pr_debug("ASCII_CR\n"); + lcd_crlf(lcd_data); + buf_i++; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_LF)) { + pr_debug("ASCII_LF\n"); + lcd_crlf(lcd_data); + buf_i++; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_BS)) { + pr_debug("ASCII_BS\n"); + lcd_backspace(lcd_data); + buf_i++; + + } else { + pr_debug("%s - Unsupported command 0x%02x\n", __func__, buf[buf_i]); + buf_i++; + } + } + +#ifdef DEBUG + kfree(dbgbuf); +#endif /* DEBUG */ + return count; +} + +static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lcd *lcd_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", lcd_data->brightness); +} + +static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lcd *lcd_data = dev_get_drvdata(dev); + int ret, brightness; + + ret = sscanf(buf, "%d", &brightness); + if (ret != 1) + return -EINVAL; + + if ((brightness < LCD_BRIGHTNESS_MIN) || + (brightness > LCD_BRIGHTNESS_MAX)) { + dev_err(lcd_data->dev, "out of range (%d to %d)\n", + LCD_BRIGHTNESS_MIN, LCD_BRIGHTNESS_MAX); + return -EINVAL; + } + + lcd_data->brightness = brightness; + lcd_cmd_backlight_brightness(lcd_data, brightness); + + return count; +} +static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR, brightness_show, brightness_store); + +static struct attribute *lcd_attrs[] = { + &dev_attr_brightness.attr, + NULL, +}; + +static struct attribute_group lcd_attr_group = { + .attrs = lcd_attrs, +}; + +static int lcd_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct lcd *lcd_data; + + lcd_data = &lcd_data_static[tty->index]; + if (lcd_data == NULL) + return -ENODEV; + + tty->driver_data = lcd_data; + + return tty_port_install(&lcd_data->port, driver, tty); +} + +static int lcd_open(struct tty_struct *tty, struct file *filp) +{ + struct lcd *lcd_data = tty->driver_data; + unsigned long flags; + + tty->driver_data = lcd_data; + spin_lock_irqsave(&lcd_data->port.lock, flags); + lcd_data->port.count++; + spin_unlock_irqrestore(&lcd_data->port.lock, flags); + tty_port_tty_set(&lcd_data->port, tty); + + return 0; +} + +static void lcd_close(struct tty_struct *tty, struct file *filp) +{ + struct lcd *lcd_data = tty->driver_data; + unsigned long flags; + bool last; + + spin_lock_irqsave(&lcd_data->port.lock, flags); + --lcd_data->port.count; + last = (lcd_data->port.count == 0); + spin_unlock_irqrestore(&lcd_data->port.lock, flags); + if (last) + tty_port_tty_set(&lcd_data->port, NULL); +} + +static int lcd_write_room(struct tty_struct *tty) +{ + struct lcd *lcd_data = tty->driver_data; + + return lcd_data->height * lcd_data->width; +} + +static const struct tty_operations lcd_ops = { + .install = lcd_install, + .open = lcd_open, + .close = lcd_close, + .write = lcd_write, + .write_room = lcd_write_room, +}; + +static int lcd_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct device_node *np = client->dev.of_node; + struct lcd *lcd_data; + struct tty_driver *lcd_tty_driver; + unsigned int width = 0, height = 0, i, brightness = 0; + char *buffer; + int ret = -ENOMEM; + + of_property_read_u32(np, "height", &height); + of_property_read_u32(np, "width", &width); + if ((width == 0) || (height == 0)) { + dev_err(&client->dev, + "Need to specify lcd width/height in device tree\n"); + ret = -EINVAL; + goto err_devtree; + } + + of_property_read_u32(np, "brightness", &brightness); + if ((brightness < LCD_BRIGHTNESS_MIN) || + (brightness > LCD_BRIGHTNESS_MAX)) { + dev_info(&client->dev, + "lcd brighness not set or out of range, defaulting to maximum\n"); + brightness = LCD_BRIGHTNESS_MAX; + } + + for (i = 0 ; i < MAX_LCDS ; i++) + if (lcd_data_static[i].client == NULL) + break; + if (i >= MAX_LCDS) { + ret = -ENODEV; + dev_warn(&client->dev, + "More than %d I2C LCD displays found. Giving up.\n", + MAX_LCDS); + goto err_devtree; + } + lcd_data = &lcd_data_static[i]; + + buffer = kzalloc(height * width, GFP_KERNEL); + if (!buffer) + goto err_devtree; + + i2c_set_clientdata(client, lcd_data); + + lcd_data->client = client; + lcd_data->dev = &client->dev; + lcd_data->height = height; + lcd_data->width = width; + lcd_data->buffer = buffer; + lcd_data->brightness = brightness; + + dev_set_drvdata(&client->dev, lcd_data); + tty_port_init(&lcd_data->port); + lcd_tty_driver = alloc_tty_driver(MAX_LCDS); + if (!lcd_tty_driver) + goto err_driver; + + lcd_tty_driver->driver_name = DRV_NAME; + lcd_tty_driver->name = DEV_NAME; + lcd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + lcd_tty_driver->subtype = SERIAL_TYPE_NORMAL; + lcd_tty_driver->init_termios = tty_std_termios; + tty_set_operations(lcd_tty_driver, &lcd_ops); + + ret = tty_register_driver(lcd_tty_driver); + if (ret) + goto err_register; + + lcd_data->lcd_tty_driver = lcd_tty_driver; + + lcd_clear_buffer(lcd_data); + lcd_load_custom_fonts(lcd_data); + lcd_cmd_display_on(lcd_data); + lcd_cmd_backlight_brightness(lcd_data, brightness); + lcd_cmd_clear_screen(lcd_data); + + ret = sysfs_create_group(&lcd_data->dev->kobj, &lcd_attr_group); + if (ret) { + dev_err(lcd_data->dev, "Can't create sysfs attrs for lcd\n"); + return ret; + } + + dev_info(&client->dev, "LCD driver initialized\n"); + + return 0; + +err_register: + put_tty_driver(lcd_data->lcd_tty_driver); +err_driver: + kfree(buffer); +err_devtree: + return ret; +} + +static int __exit lcd_remove(struct i2c_client *client) +{ + struct lcd *lcd_data = i2c_get_clientdata(client); + + lcd_cmd_display_off(lcd_data); + + sysfs_remove_group(&lcd_data->dev->kobj, &lcd_attr_group); + tty_unregister_driver(lcd_data->lcd_tty_driver); + put_tty_driver(lcd_data->lcd_tty_driver); + kfree(lcd_data->buffer); + + return 0; +} + +static const struct of_device_id lcd_of_match[] = { + { .compatible = "newhaven,nhd-0216k3z-nsw-bbw", }, + {}, +}; + +static const struct i2c_device_id lcd_id[] = { + { DRV_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lcd_id); + +static struct i2c_driver lcd_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lcd_of_match, + }, + .probe = lcd_probe, + .remove = lcd_remove, + .id_table = lcd_id, +}; + +static int __init lcd_init(void) +{ + return i2c_add_driver(&lcd_i2c_driver); +} +subsys_initcall(lcd_init); + +static void __exit lcd_exit(void) +{ + i2c_del_driver(&lcd_i2c_driver); +} +module_exit(lcd_exit); + +MODULE_DESCRIPTION("LCD 2x16"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index f93807b3631a8..4396a1f481562 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -1,40 +1,29 @@ config USB_DWC2 - bool "DesignWare USB2 DRD Core Support" + tristate "DesignWare USB2 DRD Core Support" depends on USB help Say Y here if your system has a Dual Role Hi-Speed USB controller based on the DesignWare HSOTG IP Core. - For host mode, if you choose to build the driver as dynamically - linked modules, the core module will be called dwc2.ko, the PCI - bus interface module (if you have a PCI bus system) will be - called dwc2_pci.ko, and the platform interface module (for - controllers directly connected to the CPU) will be called - dwc2_platform.ko. For gadget mode, there will be a single - module called dwc2_gadget.ko. - - NOTE: The s3c-hsotg driver is now renamed to dwc2_gadget. The - host and gadget drivers are still currently separate drivers. - There are plans to merge the dwc2_gadget driver with the dwc2 - host driver in the near future to create a dual-role driver. + If you choose to build the driver as dynamically + linked modules, a single dwc2.ko(regardless of mode of operation) + will get built for both platform IPs and PCI. if USB_DWC2 +choice + bool "DWC2 Mode Selection" + default USB_DWC2_DUAL_ROLE if (USB && USB_GADGET) + default USB_DWC2_HOST if (USB && !USB_GADGET) + default USB_DWC2_PERIPHERAL if (!USB && USB_GADGET) + config USB_DWC2_HOST - tristate "Host only mode" + bool "Host only mode" depends on USB help The Designware USB2.0 high-speed host controller - integrated into many SoCs. - -config USB_DWC2_PLATFORM - bool "DWC2 Platform" - depends on USB_DWC2_HOST - default USB_DWC2_HOST - help - The Designware USB2.0 platform interface module for - controllers directly connected to the CPU. This is only - used for host mode. + integrated into many SoCs. Select this option if you want the + driver to operate in Host-only mode. config USB_DWC2_PCI bool "DWC2 PCI" @@ -47,11 +36,31 @@ config USB_DWC2_PCI comment "Gadget mode requires USB Gadget support to be enabled" config USB_DWC2_PERIPHERAL - tristate "Gadget only mode" - depends on USB_GADGET + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_DWC2 help The Designware USB2.0 high-speed gadget controller - integrated into many SoCs. + integrated into many SoCs. Select this option if you want the + driver to operate in Peripheral-only mode. This option requires + USB_GADGET=y. + +config USB_DWC2_DUAL_ROLE + bool "Dual Role mode" + depends on ((USB=y || USB=USB_DWC2) && (USB_GADGET=y || USB_GADGET=USB_DWC2)) + help + Select this option if you want the driver to work in a dual-role + mode. In this mode both host and gadget features are enabled, and + the role will be determined by the cable that gets plugged-in. This + option requires USB_GADGET=y. +endchoice + +config USB_DWC2_PLATFORM + bool + depends on !PCI + default y + help + The Designware USB2.0 platform interface module for + controllers directly connected to the CPU. config USB_DWC2_DEBUG bool "Enable Debugging Messages" diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile index b73d2a527970e..302613570fab7 100644 --- a/drivers/usb/dwc2/Makefile +++ b/drivers/usb/dwc2/Makefile @@ -1,10 +1,17 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG -obj-$(CONFIG_USB_DWC2_HOST) += dwc2.o +obj-$(CONFIG_USB_DWC2) += dwc2.o dwc2-y := core.o core_intr.o -dwc2-y += hcd.o hcd_intr.o -dwc2-y += hcd_queue.o hcd_ddma.o + +ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) + dwc2-y += hcd.o hcd_intr.o + dwc2-y += hcd_queue.o hcd_ddma.o +endif + +ifneq ($(filter y,$(CONFIG_USB_DWC2_PERIPHERAL) $(CONFIG_USB_DWC2_DUAL_ROLE)),) + dwc2-y += gadget.o +endif # NOTE: The previous s3c-hsotg peripheral mode only driver has been moved to # this location and renamed gadget.c. When building for dynamically linked @@ -19,10 +26,4 @@ ifneq ($(CONFIG_USB_DWC2_PCI),) dwc2_pci-y := pci.o endif -ifneq ($(CONFIG_USB_DWC2_PLATFORM),) - obj-$(CONFIG_USB_DWC2_HOST) += dwc2_platform.o - dwc2_platform-y := platform.o -endif - -obj-$(CONFIG_USB_DWC2_PERIPHERAL) += dwc2_gadget.o -dwc2_gadget-y := gadget.o +dwc2-$(CONFIG_USB_DWC2_PLATFORM) += platform.o diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 27d2c9b8a0344..33c1a5816fe0b 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -118,6 +118,7 @@ static int dwc2_core_reset(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; + u32 gusbcfg; dev_vdbg(hsotg->dev, "%s()\n", __func__); @@ -148,6 +149,23 @@ static int dwc2_core_reset(struct dwc2_hsotg *hsotg) } } while (greset & GRSTCTL_CSFTRST); + if (hsotg->dr_mode == USB_DR_MODE_HOST) { + gusbcfg = readl(hsotg->regs + GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + gusbcfg |= GUSBCFG_FORCEHOSTMODE; + writel(gusbcfg, hsotg->regs + GUSBCFG); + } else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) { + gusbcfg = readl(hsotg->regs + GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg |= GUSBCFG_FORCEDEVMODE; + writel(gusbcfg, hsotg->regs + GUSBCFG); + } else if (hsotg->dr_mode == USB_DR_MODE_OTG) { + gusbcfg = readl(hsotg->regs + GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + writel(gusbcfg, hsotg->regs + GUSBCFG); + } + /* * NOTE: This long sleep is _very_ important, otherwise the core will * not stay in host mode after a connector ID change! @@ -404,6 +422,11 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) if (hsotg->core_params->ts_dline > 0) usbcfg |= GUSBCFG_TERMSELDLPULSE; + /* Set external VBUS indicator as needed. */ + if (hsotg->core_params->phy_type == DWC2_PHY_TYPE_PARAM_ULPI) + usbcfg |= (GUSBCFG_ULPI_INT_VBUS_IND | + GUSBCFG_INDICATORPASSTHROUGH); + writel(usbcfg, hsotg->regs + GUSBCFG); /* Reset the Controller */ @@ -2674,23 +2697,23 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) hwcfg2 = readl(hsotg->regs + GHWCFG2); hwcfg3 = readl(hsotg->regs + GHWCFG3); hwcfg4 = readl(hsotg->regs + GHWCFG4); - gnptxfsiz = readl(hsotg->regs + GNPTXFSIZ); grxfsiz = readl(hsotg->regs + GRXFSIZ); dev_dbg(hsotg->dev, "hwcfg1=%08x\n", hwcfg1); dev_dbg(hsotg->dev, "hwcfg2=%08x\n", hwcfg2); dev_dbg(hsotg->dev, "hwcfg3=%08x\n", hwcfg3); dev_dbg(hsotg->dev, "hwcfg4=%08x\n", hwcfg4); - dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); - /* Force host mode to get HPTXFSIZ exact power on value */ + /* Force host mode to get HPTXFSIZ / GNPTXFSIZ exact power on value */ gusbcfg = readl(hsotg->regs + GUSBCFG); gusbcfg |= GUSBCFG_FORCEHOSTMODE; writel(gusbcfg, hsotg->regs + GUSBCFG); usleep_range(100000, 150000); + gnptxfsiz = readl(hsotg->regs + GNPTXFSIZ); hptxfsiz = readl(hsotg->regs + HPTXFSIZ); + dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); gusbcfg = readl(hsotg->regs + GUSBCFG); gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; @@ -2725,6 +2748,14 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) width = (hwcfg3 & GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK) >> GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT; hw->max_transfer_size = (1 << (width + 11)) - 1; + + /* Clip max_transfer_size to 65535. dwc2_hc_setup_align_buf() allocates + * coherent buffers with this size, and if it's too large we can + * exhaust the coherent DMA pool. + */ + if (hw->max_transfer_size > 65535) + hw->max_transfer_size = 65535; + width = (hwcfg3 & GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK) >> GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT; hw->max_packet_count = (1 << (width + 4)) - 1; diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 1efd10cc96291..5bb7e80123166 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -84,7 +84,7 @@ static const char * const s3c_hsotg_supply_names[] = { */ #define EP0_MPS_LIMIT 64 -struct s3c_hsotg; +struct dwc2_hsotg; struct s3c_hsotg_req; /** @@ -130,7 +130,7 @@ struct s3c_hsotg_req; struct s3c_hsotg_ep { struct usb_ep ep; struct list_head queue; - struct s3c_hsotg *parent; + struct dwc2_hsotg *parent; struct s3c_hsotg_req *req; struct dentry *debugfs; @@ -139,6 +139,7 @@ struct s3c_hsotg_ep { unsigned int last_load; unsigned int fifo_load; unsigned short fifo_size; + unsigned short fifo_index; unsigned char dir_in; unsigned char index; @@ -153,65 +154,6 @@ struct s3c_hsotg_ep { char name[10]; }; -/** - * struct s3c_hsotg - driver state. - * @dev: The parent device supplied to the probe function - * @driver: USB gadget driver - * @phy: The otg phy transceiver structure for phy control. - * @uphy: The otg phy transceiver structure for old USB phy control. - * @plat: The platform specific configuration data. This can be removed once - * all SoCs support usb transceiver. - * @regs: The memory area mapped for accessing registers. - * @irq: The IRQ number we are using - * @supplies: Definition of USB power supplies - * @phyif: PHY interface width - * @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos. - * @num_of_eps: Number of available EPs (excluding EP0) - * @debug_root: root directrory for debugfs. - * @debug_file: main status file for debugfs. - * @debug_fifo: FIFO status file for debugfs. - * @ep0_reply: Request used for ep0 reply. - * @ep0_buff: Buffer for EP0 reply data, if needed. - * @ctrl_buff: Buffer for EP0 control requests. - * @ctrl_req: Request for EP0 control packets. - * @setup: NAK management for EP0 SETUP - * @last_rst: Time of last reset - * @eps: The endpoints being supplied to the gadget framework - */ -struct s3c_hsotg { - struct device *dev; - struct usb_gadget_driver *driver; - struct phy *phy; - struct usb_phy *uphy; - struct s3c_hsotg_plat *plat; - - spinlock_t lock; - - void __iomem *regs; - int irq; - struct clk *clk; - - struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsotg_supply_names)]; - - u32 phyif; - unsigned int dedicated_fifos:1; - unsigned char num_of_eps; - - struct dentry *debug_root; - struct dentry *debug_file; - struct dentry *debug_fifo; - - struct usb_request *ep0_reply; - struct usb_request *ctrl_req; - u8 ep0_buff[8]; - u8 ctrl_buff[8]; - - struct usb_gadget gadget; - unsigned int setup; - unsigned long last_rst; - struct s3c_hsotg_ep *eps; -}; - /** * struct s3c_hsotg_req - data transfer request * @req: The USB gadget request @@ -226,6 +168,7 @@ struct s3c_hsotg_req { unsigned char mapped; }; +#if defined(CONFIG_USB_DWC2_PERIPHERAL) || defined(CONFIG_USB_DWC2_DUAL_ROLE) #define call_gadget(_hs, _entry) \ do { \ if ((_hs)->gadget.speed != USB_SPEED_UNKNOWN && \ @@ -235,6 +178,9 @@ do { \ spin_lock(&_hs->lock); \ } \ } while (0) +#else +#define call_gadget(_hs, _entry) do {} while (0) +#endif struct dwc2_hsotg; struct dwc2_host_chan; @@ -492,15 +438,23 @@ struct dwc2_hw_params { * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic * and periodic schedules * + * These are common for both host and peripheral modes: + * * @dev: The struct device pointer * @regs: Pointer to controller regs - * @core_params: Parameters that define how the core should be configured * @hw_params: Parameters that were autodetected from the * hardware registers + * @core_params: Parameters that define how the core should be configured * @op_state: The operational State, during transitions (a_host=> * a_peripheral and b_device=>b_host) this may not match * the core, but allows the software to determine * transitions + * @dr_mode: Requested mode of operation, one of following: + * - USB_DR_MODE_PERIPHERAL + * - USB_DR_MODE_HOST + * - USB_DR_MODE_OTG + * @lock: Spinlock that protects all the driver data structures + * @priv: Stores a pointer to the struct usb_hcd * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth * transfer are in process of being queued * @srp_success: Stores status of SRP request in the case of a FS PHY @@ -510,6 +464,9 @@ struct dwc2_hw_params { * interrupt * @wkp_timer: Timer object for handling Wakeup Detected interrupt * @lx_state: Lx state of connected device + * + * These are for host mode: + * * @flags: Flags for handling root port state changes * @non_periodic_sched_inactive: Inactive QHs in the non-periodic schedule. * Transfers associated with these QHs are not currently @@ -578,11 +535,31 @@ struct dwc2_hw_params { * @status_buf_dma: DMA address for status_buf * @start_work: Delayed work for handling host A-cable connection * @reset_work: Delayed work for handling a port reset - * @lock: Spinlock that protects all the driver data structures - * @priv: Stores a pointer to the struct usb_hcd * @otg_port: OTG port number * @frame_list: Frame list * @frame_list_dma: Frame list DMA address + * + * These are for peripheral mode: + * + * @driver: USB gadget driver + * @phy: The otg phy transceiver structure for phy control. + * @uphy: The otg phy transceiver structure for old USB phy control. + * @plat: The platform specific configuration data. This can be removed once + * all SoCs support usb transceiver. + * @supplies: Definition of USB power supplies + * @phyif: PHY interface width + * @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos. + * @num_of_eps: Number of available EPs (excluding EP0) + * @debug_root: Root directrory for debugfs. + * @debug_file: Main status file for debugfs. + * @debug_fifo: FIFO status file for debugfs. + * @ep0_reply: Request used for ep0 reply. + * @ep0_buff: Buffer for EP0 reply data, if needed. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_req: Request for EP0 control packets. + * @setup: NAK management for EP0 SETUP + * @last_rst: Time of last reset + * @eps: The endpoints being supplied to the gadget framework */ struct dwc2_hsotg { struct device *dev; @@ -592,6 +569,10 @@ struct dwc2_hsotg { /** Params to actually use */ struct dwc2_core_params *core_params; enum usb_otg_state op_state; + enum usb_dr_mode dr_mode; + + spinlock_t lock; + void *priv; unsigned int queuing_high_bandwidth:1; unsigned int srp_success:1; @@ -601,6 +582,14 @@ struct dwc2_hsotg { struct timer_list wkp_timer; enum dwc2_lx_state lx_state; + /* DWC OTG HW Release versions */ +#define DWC2_CORE_REV_2_71a 0x4f54271a +#define DWC2_CORE_REV_2_90a 0x4f54290a +#define DWC2_CORE_REV_2_92a 0x4f54292a +#define DWC2_CORE_REV_2_94a 0x4f54294a +#define DWC2_CORE_REV_3_00a 0x4f54300a + +#if defined(CONFIG_USB_DWC2_HOST) || defined(CONFIG_USB_DWC2_DUAL_ROLE) union dwc2_hcd_internal_flags { u32 d32; struct { @@ -647,19 +636,10 @@ struct dwc2_hsotg { struct delayed_work start_work; struct delayed_work reset_work; - spinlock_t lock; - void *priv; u8 otg_port; u32 *frame_list; dma_addr_t frame_list_dma; - /* DWC OTG HW Release versions */ -#define DWC2_CORE_REV_2_71a 0x4f54271a -#define DWC2_CORE_REV_2_90a 0x4f54290a -#define DWC2_CORE_REV_2_92a 0x4f54292a -#define DWC2_CORE_REV_2_94a 0x4f54294a -#define DWC2_CORE_REV_3_00a 0x4f54300a - #ifdef DEBUG u32 frrem_samples; u64 frrem_accum; @@ -678,6 +658,39 @@ struct dwc2_hsotg { u32 hfnum_other_samples_b; u64 hfnum_other_frrem_accum_b; #endif +#endif /* CONFIG_USB_DWC2_HOST || CONFIG_USB_DWC2_DUAL_ROLE */ + +#if defined(CONFIG_USB_DWC2_PERIPHERAL) || defined(CONFIG_USB_DWC2_DUAL_ROLE) + /* Gadget structures */ + struct usb_gadget_driver *driver; + struct phy *phy; + struct usb_phy *uphy; + struct s3c_hsotg_plat *plat; + + struct clk *clk; + + struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsotg_supply_names)]; + + u32 phyif; + int fifo_mem; + unsigned int dedicated_fifos:1; + unsigned char num_of_eps; + u32 fifo_map; + + struct dentry *debug_root; + struct dentry *debug_file; + struct dentry *debug_fifo; + + struct usb_request *ep0_reply; + struct usb_request *ctrl_req; + u8 ep0_buff[8]; + u8 ctrl_buff[8]; + + struct usb_gadget gadget; + unsigned int setup; + unsigned long last_rst; + struct s3c_hsotg_ep *eps; +#endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; /* Reasons for halting a host channel */ @@ -947,4 +960,47 @@ extern void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); */ extern u16 dwc2_get_otg_version(struct dwc2_hsotg *hsotg); +/* Gadget defines */ +#if defined(CONFIG_USB_DWC2_PERIPHERAL) || defined(CONFIG_USB_DWC2_DUAL_ROLE) +extern int s3c_hsotg_remove(struct dwc2_hsotg *hsotg); +extern void s3c_hsotg_core_init(struct dwc2_hsotg *dwc2); +extern int s3c_hsotg_suspend(struct dwc2_hsotg *dwc2); +extern int s3c_hsotg_resume(struct dwc2_hsotg *dwc2); +extern int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq); +irqreturn_t s3c_hsotg_irq(int irq, void *pw); +#else +static inline void s3c_hsotg_core_init(struct dwc2_hsotg *dwc2) {} +static inline int s3c_hsotg_remove(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int s3c_hsotg_suspend(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int s3c_hsotg_resume(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) +{ return 0; } +static inline irqreturn_t s3c_hsotg_irq(int irq, void *pw) +{ return IRQ_HANDLED; } +#endif + +#if defined(CONFIG_USB_DWC2_HOST) || defined(CONFIG_USB_DWC2_DUAL_ROLE) +/** + * dwc2_hcd_get_frame_number() - Returns current frame number + * + * @hsotg: The DWC2 HCD + */ +extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +#else +static inline void dwc2_set_all_params(struct dwc2_core_params *params, int value) {} +static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} +static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq, + const struct dwc2_core_params *params) +{ return 0; } +#endif + #endif /* __DWC2_CORE_H__ */ diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index c93918b70d032..12408752a2bb9 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -287,9 +287,11 @@ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) * Release lock before scheduling workq as it holds spinlock during * scheduling. */ - spin_unlock(&hsotg->lock); - queue_work(hsotg->wq_otg, &hsotg->wf_otg); - spin_lock(&hsotg->lock); + if (hsotg->wq_otg) { + spin_unlock(&hsotg->lock); + queue_work(hsotg->wq_otg, &hsotg->wf_otg); + spin_lock(&hsotg->lock); + } /* Clear interrupt */ writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); @@ -337,6 +339,7 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) } /* Change to L0 state */ hsotg->lx_state = DWC2_L0; + call_gadget(hsotg, resume); } else { if (hsotg->lx_state != DWC2_L1) { u32 pcgcctl = readl(hsotg->regs + PCGCTL); @@ -397,6 +400,7 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d\n", !!(dsts & DSTS_SUSPSTS), hsotg->hw_params.power_optimized); + call_gadget(hsotg, suspend); } else { if (hsotg->op_state == OTG_STATE_A_PERIPHERAL) { dev_dbg(hsotg->dev, "a_peripheral->a_host\n"); @@ -472,6 +476,9 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev) spin_lock(&hsotg->lock); + if (dwc2_is_device_mode(hsotg)) + retval = s3c_hsotg_irq(irq, dev); + gintsts = dwc2_read_common_intr(hsotg); if (gintsts & ~GINTSTS_PRTINT) retval = IRQ_HANDLED; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f3c56a2fed5bf..2d98100550141 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -38,6 +38,7 @@ #include #include "core.h" +#include "hw.h" /* conversion functions */ static inline struct s3c_hsotg_req *our_req(struct usb_request *req) @@ -50,9 +51,9 @@ static inline struct s3c_hsotg_ep *our_ep(struct usb_ep *ep) return container_of(ep, struct s3c_hsotg_ep, ep); } -static inline struct s3c_hsotg *to_hsotg(struct usb_gadget *gadget) +static inline struct dwc2_hsotg *to_hsotg(struct usb_gadget *gadget) { - return container_of(gadget, struct s3c_hsotg, gadget); + return container_of(gadget, struct dwc2_hsotg, gadget); } static inline void __orr32(void __iomem *ptr, u32 val) @@ -66,7 +67,7 @@ static inline void __bic32(void __iomem *ptr, u32 val) } /* forward decleration of functions */ -static void s3c_hsotg_dump(struct s3c_hsotg *hsotg); +static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); /** * using_dma - return the DMA status of the driver. @@ -87,7 +88,7 @@ static void s3c_hsotg_dump(struct s3c_hsotg *hsotg); * * Until this issue is sorted out, we always return 'false'. */ -static inline bool using_dma(struct s3c_hsotg *hsotg) +static inline bool using_dma(struct dwc2_hsotg *hsotg) { return false; /* support is not complete */ } @@ -97,7 +98,7 @@ static inline bool using_dma(struct s3c_hsotg *hsotg) * @hsotg: The device state * @ints: A bitmask of the interrupts to enable */ -static void s3c_hsotg_en_gsint(struct s3c_hsotg *hsotg, u32 ints) +static void s3c_hsotg_en_gsint(struct dwc2_hsotg *hsotg, u32 ints) { u32 gsintmsk = readl(hsotg->regs + GINTMSK); u32 new_gsintmsk; @@ -115,7 +116,7 @@ static void s3c_hsotg_en_gsint(struct s3c_hsotg *hsotg, u32 ints) * @hsotg: The device state * @ints: A bitmask of the interrupts to enable */ -static void s3c_hsotg_disable_gsint(struct s3c_hsotg *hsotg, u32 ints) +static void s3c_hsotg_disable_gsint(struct dwc2_hsotg *hsotg, u32 ints) { u32 gsintmsk = readl(hsotg->regs + GINTMSK); u32 new_gsintmsk; @@ -136,7 +137,7 @@ static void s3c_hsotg_disable_gsint(struct s3c_hsotg *hsotg, u32 ints) * Set or clear the mask for an individual endpoint's interrupt * request. */ -static void s3c_hsotg_ctrl_epint(struct s3c_hsotg *hsotg, +static void s3c_hsotg_ctrl_epint(struct dwc2_hsotg *hsotg, unsigned int ep, unsigned int dir_in, unsigned int en) { @@ -161,7 +162,7 @@ static void s3c_hsotg_ctrl_epint(struct s3c_hsotg *hsotg, * s3c_hsotg_init_fifo - initialise non-periodic FIFOs * @hsotg: The device instance. */ -static void s3c_hsotg_init_fifo(struct s3c_hsotg *hsotg) +static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) { unsigned int ep; unsigned int addr; @@ -184,16 +185,33 @@ static void s3c_hsotg_init_fifo(struct s3c_hsotg *hsotg) /* start at the end of the GNPTXFSIZ, rounded up */ addr = 2048 + 1024; - size = 768; /* - * currently we allocate TX FIFOs for all possible endpoints, - * and assume that they are all the same size. + * Because we have not enough memory to have each TX FIFO of size at + * least 3072 bytes (the maximum single packet size), we create four + * FIFOs of lenght 1024, and four of length 3072 bytes, and assing + * them to endpoints dynamically according to maxpacket size value of + * given endpoint. */ - for (ep = 1; ep <= 15; ep++) { + /* 256*4=1024 bytes FIFO length */ + size = 256; + for (ep = 1; ep <= 4; ep++) { + val = addr; + val |= size << FIFOSIZE_DEPTH_SHIFT; + WARN_ONCE(addr + size > hsotg->fifo_mem, + "insufficient fifo memory"); + addr += size; + + writel(val, hsotg->regs + DPTXFSIZN(ep)); + } + /* 768*4=3072 bytes FIFO length */ + size = 768; + for (ep = 5; ep <= 8; ep++) { val = addr; val |= size << FIFOSIZE_DEPTH_SHIFT; + WARN_ONCE(addr + size > hsotg->fifo_mem, + "insufficient fifo memory"); addr += size; writel(val, hsotg->regs + DPTXFSIZN(ep)); @@ -268,7 +286,7 @@ static inline int is_ep_periodic(struct s3c_hsotg_ep *hs_ep) * This is the reverse of s3c_hsotg_map_dma(), called for the completion * of a request to ensure the buffer is ready for access by the caller. */ -static void s3c_hsotg_unmap_dma(struct s3c_hsotg *hsotg, +static void s3c_hsotg_unmap_dma(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) { @@ -297,7 +315,7 @@ static void s3c_hsotg_unmap_dma(struct s3c_hsotg *hsotg, * * This routine is only needed for PIO */ -static int s3c_hsotg_write_fifo(struct s3c_hsotg *hsotg, +static int s3c_hsotg_write_fifo(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) { @@ -502,7 +520,7 @@ static unsigned get_ep_limit(struct s3c_hsotg_ep *hs_ep) * Start the given request running by setting the endpoint registers * appropriately, and writing any data to the FIFOs. */ -static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, +static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req, bool continuing) @@ -692,7 +710,7 @@ static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, * DMA memory, then we map the memory and mark our request to allow us to * cleanup on completion. */ -static int s3c_hsotg_map_dma(struct s3c_hsotg *hsotg, +static int s3c_hsotg_map_dma(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, struct usb_request *req) { @@ -721,7 +739,7 @@ static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, { struct s3c_hsotg_req *hs_req = our_req(req); struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hs = hs_ep->parent; + struct dwc2_hsotg *hs = hs_ep->parent; bool first; dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", @@ -753,7 +771,7 @@ static int s3c_hsotg_ep_queue_lock(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hs = hs_ep->parent; + struct dwc2_hsotg *hs = hs_ep->parent; unsigned long flags = 0; int ret = 0; @@ -784,7 +802,7 @@ static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, struct usb_request *req) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg *hsotg = hs_ep->parent; dev_dbg(hsotg->dev, "%s: ep %p, req %p\n", __func__, ep, req); @@ -799,7 +817,7 @@ static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, * Convert the given wIndex into a pointer to an driver endpoint * structure, or return NULL if it is not a valid endpoint. */ -static struct s3c_hsotg_ep *ep_from_windex(struct s3c_hsotg *hsotg, +static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, u32 windex) { struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; @@ -828,7 +846,7 @@ static struct s3c_hsotg_ep *ep_from_windex(struct s3c_hsotg *hsotg, * Create a request and queue it on the given endpoint. This is useful as * an internal method of sending replies to certain control requests, etc. */ -static int s3c_hsotg_send_reply(struct s3c_hsotg *hsotg, +static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep, void *buff, int length) @@ -869,7 +887,7 @@ static int s3c_hsotg_send_reply(struct s3c_hsotg *hsotg, * @hsotg: The device state * @ctrl: USB control request */ -static int s3c_hsotg_process_req_status(struct s3c_hsotg *hsotg, +static int s3c_hsotg_process_req_status(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; @@ -940,7 +958,7 @@ static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) * @hsotg: The device state * @ctrl: USB control request */ -static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, +static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; @@ -1013,8 +1031,8 @@ static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, return 1; } -static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg); -static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg); +static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg); +static void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg); /** * s3c_hsotg_stall_ep0 - stall ep0 @@ -1022,7 +1040,8 @@ static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg); * * Set stall for ep0 as response for setup request. */ -static void s3c_hsotg_stall_ep0(struct s3c_hsotg *hsotg) { +static void s3c_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) +{ struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; u32 reg; u32 ctrl; @@ -1060,7 +1079,7 @@ static void s3c_hsotg_stall_ep0(struct s3c_hsotg *hsotg) { * needs to work out what to do next (and whether to pass it on to the * gadget driver). */ -static void s3c_hsotg_process_control(struct s3c_hsotg *hsotg, +static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; @@ -1145,7 +1164,7 @@ static void s3c_hsotg_complete_setup(struct usb_ep *ep, struct usb_request *req) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg *hsotg = hs_ep->parent; if (req->status < 0) { dev_dbg(hsotg->dev, "%s: failed %d\n", __func__, req->status); @@ -1167,7 +1186,7 @@ static void s3c_hsotg_complete_setup(struct usb_ep *ep, * Enqueue a request on EP0 if necessary to received any SETUP packets * received from the host. */ -static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg) +static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) { struct usb_request *req = hsotg->ctrl_req; struct s3c_hsotg_req *hs_req = our_req(req); @@ -1210,7 +1229,7 @@ static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg) * * Note, expects the ep to already be locked as appropriate. */ -static void s3c_hsotg_complete_request(struct s3c_hsotg *hsotg, +static void s3c_hsotg_complete_request(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req, int result) @@ -1275,7 +1294,7 @@ static void s3c_hsotg_complete_request(struct s3c_hsotg *hsotg, * endpoint, so sort out whether we need to read the data into a request * that has been made for that endpoint. */ -static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) +static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) { struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; struct s3c_hsotg_req *hs_req = hs_ep->req; @@ -1340,7 +1359,7 @@ static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) * currently believed that we do not need to wait for any space in * the TxFIFO. */ -static void s3c_hsotg_send_zlp(struct s3c_hsotg *hsotg, +static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, struct s3c_hsotg_req *req) { u32 ctrl; @@ -1382,7 +1401,7 @@ static void s3c_hsotg_send_zlp(struct s3c_hsotg *hsotg, * transfer for an OUT endpoint has been completed, either by a short * packet or by the finish of a transfer. */ -static void s3c_hsotg_handle_outdone(struct s3c_hsotg *hsotg, +static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum, bool was_setup) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); @@ -1455,7 +1474,7 @@ static void s3c_hsotg_handle_outdone(struct s3c_hsotg *hsotg, * * Return the current frame number */ -static u32 s3c_hsotg_read_frameno(struct s3c_hsotg *hsotg) +static u32 s3c_hsotg_read_frameno(struct dwc2_hsotg *hsotg) { u32 dsts; @@ -1482,7 +1501,7 @@ static u32 s3c_hsotg_read_frameno(struct s3c_hsotg *hsotg) * as the actual data should be sent to the memory directly and we turn * on the completion interrupts to get notifications of transfer completion. */ -static void s3c_hsotg_handle_rx(struct s3c_hsotg *hsotg) +static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) { u32 grxstsr = readl(hsotg->regs + GRXSTSP); u32 epnum, status, size; @@ -1574,7 +1593,7 @@ static u32 s3c_hsotg_ep0_mps(unsigned int mps) * Configure the maximum packet size for the given endpoint, updating * the hardware control registers to reflect this. */ -static void s3c_hsotg_set_ep_maxpacket(struct s3c_hsotg *hsotg, +static void s3c_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, unsigned int ep, unsigned int mps) { struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; @@ -1629,7 +1648,7 @@ static void s3c_hsotg_set_ep_maxpacket(struct s3c_hsotg *hsotg, * @hsotg: The driver state * @idx: The index for the endpoint (0..15) */ -static void s3c_hsotg_txfifo_flush(struct s3c_hsotg *hsotg, unsigned int idx) +static void s3c_hsotg_txfifo_flush(struct dwc2_hsotg *hsotg, unsigned int idx) { int timeout; int val; @@ -1650,6 +1669,7 @@ static void s3c_hsotg_txfifo_flush(struct s3c_hsotg *hsotg, unsigned int idx) dev_err(hsotg->dev, "%s: timeout flushing fifo (GRSTCTL=%08x)\n", __func__, val); + break; } udelay(1); @@ -1664,7 +1684,7 @@ static void s3c_hsotg_txfifo_flush(struct s3c_hsotg *hsotg, unsigned int idx) * Check to see if there is a request that has data to send, and if so * make an attempt to write data into the FIFO. */ -static int s3c_hsotg_trytx(struct s3c_hsotg *hsotg, +static int s3c_hsotg_trytx(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep) { struct s3c_hsotg_req *hs_req = hs_ep->req; @@ -1697,7 +1717,7 @@ static int s3c_hsotg_trytx(struct s3c_hsotg *hsotg, * An IN transfer has been completed, update the transfer's state and then * call the relevant completion routines. */ -static void s3c_hsotg_complete_in(struct s3c_hsotg *hsotg, +static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep) { struct s3c_hsotg_req *hs_req = hs_ep->req; @@ -1774,7 +1794,7 @@ static void s3c_hsotg_complete_in(struct s3c_hsotg *hsotg, * * Process and clear any interrupt pending for an individual endpoint */ -static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, +static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, int dir_in) { struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; @@ -1832,7 +1852,7 @@ static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, if (dir_in) { int epctl = readl(hsotg->regs + epctl_reg); - s3c_hsotg_txfifo_flush(hsotg, idx); + s3c_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index); if ((epctl & DXEPCTL_STALL) && (epctl & DXEPCTL_EPTYPE_BULK)) { @@ -1899,7 +1919,7 @@ static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, * Handle updating the device settings after the enumeration phase has * been completed. */ -static void s3c_hsotg_irq_enumdone(struct s3c_hsotg *hsotg) +static void s3c_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) { u32 dsts = readl(hsotg->regs + DSTS); int ep0_mps = 0, ep_mps; @@ -1976,11 +1996,12 @@ static void s3c_hsotg_irq_enumdone(struct s3c_hsotg *hsotg) * Go through the requests on the given endpoint and mark them * completed with the given result code. */ -static void kill_all_requests(struct s3c_hsotg *hsotg, +static void kill_all_requests(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep, int result, bool force) { struct s3c_hsotg_req *req, *treq; + unsigned size; list_for_each_entry_safe(req, treq, &ep->queue, queue) { /* @@ -1994,9 +2015,11 @@ static void kill_all_requests(struct s3c_hsotg *hsotg, s3c_hsotg_complete_request(hsotg, ep, req, result); } - if(hsotg->dedicated_fifos) - if ((readl(hsotg->regs + DTXFSTS(ep->index)) & 0xffff) * 4 < 3072) - s3c_hsotg_txfifo_flush(hsotg, ep->index); + if (!hsotg->dedicated_fifos) + return; + size = (readl(hsotg->regs + DTXFSTS(ep->index)) & 0xffff) * 4; + if (size < ep->fifo_size) + s3c_hsotg_txfifo_flush(hsotg, ep->fifo_index); } /** @@ -2007,7 +2030,7 @@ static void kill_all_requests(struct s3c_hsotg *hsotg, * transactions and signal the gadget driver that this * has happened. */ -static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg) +static void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) { unsigned ep; @@ -2022,7 +2045,7 @@ static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg) * @hsotg: The device state: * @periodic: True if this is a periodic FIFO interrupt */ -static void s3c_hsotg_irq_fifoempty(struct s3c_hsotg *hsotg, bool periodic) +static void s3c_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) { struct s3c_hsotg_ep *ep; int epno, ret; @@ -2056,7 +2079,7 @@ static void s3c_hsotg_irq_fifoempty(struct s3c_hsotg *hsotg, bool periodic) * * Issue a soft reset to the core, and await the core finishing it. */ -static int s3c_hsotg_corereset(struct s3c_hsotg *hsotg) +static int s3c_hsotg_corereset(struct dwc2_hsotg *hsotg) { int timeout; u32 grstctl; @@ -2104,7 +2127,7 @@ static int s3c_hsotg_corereset(struct s3c_hsotg *hsotg) * * Issue a soft reset to the core, and await the core finishing it. */ -static void s3c_hsotg_core_init(struct s3c_hsotg *hsotg) +void s3c_hsotg_core_init(struct dwc2_hsotg *hsotg) { s3c_hsotg_corereset(hsotg); @@ -2236,14 +2259,13 @@ static void s3c_hsotg_core_init(struct s3c_hsotg *hsotg) * @irq: The IRQ number triggered * @pw: The pw value when registered the handler. */ -static irqreturn_t s3c_hsotg_irq(int irq, void *pw) +irqreturn_t s3c_hsotg_irq(int irq, void *pw) { - struct s3c_hsotg *hsotg = pw; + struct dwc2_hsotg *hsotg = pw; int retry_count = 8; u32 gintsts; u32 gintmsk; - spin_lock(&hsotg->lock); irq_retry: gintsts = readl(hsotg->regs + GINTSTS); gintmsk = readl(hsotg->regs + GINTMSK); @@ -2253,33 +2275,12 @@ static irqreturn_t s3c_hsotg_irq(int irq, void *pw) gintsts &= gintmsk; - if (gintsts & GINTSTS_OTGINT) { - u32 otgint = readl(hsotg->regs + GOTGINT); - - dev_info(hsotg->dev, "OTGInt: %08x\n", otgint); - - writel(otgint, hsotg->regs + GOTGINT); - } - - if (gintsts & GINTSTS_SESSREQINT) { - dev_dbg(hsotg->dev, "%s: SessReqInt\n", __func__); - writel(GINTSTS_SESSREQINT, hsotg->regs + GINTSTS); - } - if (gintsts & GINTSTS_ENUMDONE) { writel(GINTSTS_ENUMDONE, hsotg->regs + GINTSTS); s3c_hsotg_irq_enumdone(hsotg); } - if (gintsts & GINTSTS_CONIDSTSCHNG) { - dev_dbg(hsotg->dev, "ConIDStsChg (DSTS=0x%08x, GOTCTL=%08x)\n", - readl(hsotg->regs + DSTS), - readl(hsotg->regs + GOTGCTL)); - - writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); - } - if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) { u32 daint = readl(hsotg->regs + DAINT); u32 daintmsk = readl(hsotg->regs + DAINTMSK); @@ -2360,25 +2361,6 @@ static irqreturn_t s3c_hsotg_irq(int irq, void *pw) s3c_hsotg_handle_rx(hsotg); } - if (gintsts & GINTSTS_MODEMIS) { - dev_warn(hsotg->dev, "warning, mode mismatch triggered\n"); - writel(GINTSTS_MODEMIS, hsotg->regs + GINTSTS); - } - - if (gintsts & GINTSTS_USBSUSP) { - dev_info(hsotg->dev, "GINTSTS_USBSusp\n"); - writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); - - call_gadget(hsotg, suspend); - } - - if (gintsts & GINTSTS_WKUPINT) { - dev_info(hsotg->dev, "GINTSTS_WkUpIn\n"); - writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); - - call_gadget(hsotg, resume); - } - if (gintsts & GINTSTS_ERLYSUSP) { dev_dbg(hsotg->dev, "GINTSTS_ErlySusp\n"); writel(GINTSTS_ERLYSUSP, hsotg->regs + GINTSTS); @@ -2414,10 +2396,9 @@ static irqreturn_t s3c_hsotg_irq(int irq, void *pw) if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) goto irq_retry; - spin_unlock(&hsotg->lock); - return IRQ_HANDLED; } +EXPORT_SYMBOL(s3c_hsotg_irq); /** * s3c_hsotg_ep_enable - enable the given endpoint @@ -2430,13 +2411,14 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg *hsotg = hs_ep->parent; unsigned long flags; int index = hs_ep->index; u32 epctrl_reg; u32 epctrl; u32 mps; int dir_in; + int i, val, size; int ret = 0; dev_dbg(hsotg->dev, @@ -2509,17 +2491,8 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, break; case USB_ENDPOINT_XFER_INT: - if (dir_in) { - /* - * Allocate our TxFNum by simply using the index - * of the endpoint for the moment. We could do - * something better if the host indicates how - * many FIFOs we are expecting to use. - */ - + if (dir_in) hs_ep->periodic = 1; - epctrl |= DXEPCTL_TXFNUM(index); - } epctrl |= DXEPCTL_EPTYPE_INTERRUPT; break; @@ -2533,8 +2506,25 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, * if the hardware has dedicated fifos, we must give each IN EP * a unique tx-fifo even if it is non-periodic. */ - if (dir_in && hsotg->dedicated_fifos) - epctrl |= DXEPCTL_TXFNUM(index); + if (dir_in && hsotg->dedicated_fifos) { + size = hs_ep->ep.maxpacket*hs_ep->mc; + for (i = 1; i <= 8; ++i) { + if (hsotg->fifo_map & (1<regs + DPTXFSIZN(i)); + val = (val >> FIFOSIZE_DEPTH_SHIFT)*4; + if (val < size) + continue; + hsotg->fifo_map |= 1<fifo_index = i; + hs_ep->fifo_size = val; + break; + } + if (i == 8) + return -ENOMEM; + } /* for non control endpoints, set PID to D0 */ if (index) @@ -2561,14 +2551,14 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, static int s3c_hsotg_ep_disable(struct usb_ep *ep) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg *hsotg = hs_ep->parent; int dir_in = hs_ep->dir_in; int index = hs_ep->index; unsigned long flags; u32 epctrl_reg; u32 ctrl; - dev_info(hsotg->dev, "%s(ep %p)\n", __func__, ep); + dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); if (ep == &hsotg->eps[0].ep) { dev_err(hsotg->dev, "%s: called for ep0\n", __func__); @@ -2581,6 +2571,9 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) /* terminate all requests with shutdown */ kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + hsotg->fifo_map &= ~(1<fifo_index); + hs_ep->fifo_index = 0; + hs_ep->fifo_size = 0; ctrl = readl(hsotg->regs + epctrl_reg); ctrl &= ~DXEPCTL_EPENA; @@ -2623,10 +2616,10 @@ static int s3c_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) { struct s3c_hsotg_req *hs_req = our_req(req); struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hs = hs_ep->parent; + struct dwc2_hsotg *hs = hs_ep->parent; unsigned long flags; - dev_info(hs->dev, "ep_dequeue(%p,%p)\n", ep, req); + dev_dbg(hs->dev, "ep_dequeue(%p,%p)\n", ep, req); spin_lock_irqsave(&hs->lock, flags); @@ -2649,7 +2642,7 @@ static int s3c_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hs = hs_ep->parent; + struct dwc2_hsotg *hs = hs_ep->parent; int index = hs_ep->index; u32 epreg; u32 epctl; @@ -2713,7 +2706,7 @@ static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) static int s3c_hsotg_ep_sethalt_lock(struct usb_ep *ep, int value) { struct s3c_hsotg_ep *hs_ep = our_ep(ep); - struct s3c_hsotg *hs = hs_ep->parent; + struct dwc2_hsotg *hs = hs_ep->parent; unsigned long flags = 0; int ret = 0; @@ -2742,19 +2735,20 @@ static struct usb_ep_ops s3c_hsotg_ep_ops = { * A wrapper for platform code responsible for controlling * low-level USB code */ -static void s3c_hsotg_phy_enable(struct s3c_hsotg *hsotg) +static void s3c_hsotg_phy_enable(struct dwc2_hsotg *hsotg) { struct platform_device *pdev = to_platform_device(hsotg->dev); dev_dbg(hsotg->dev, "pdev 0x%p\n", pdev); - if (hsotg->phy) { - phy_init(hsotg->phy); - phy_power_on(hsotg->phy); - } else if (hsotg->uphy) + if (hsotg->uphy) usb_phy_init(hsotg->uphy); - else if (hsotg->plat->phy_init) + else if (hsotg->plat && hsotg->plat->phy_init) hsotg->plat->phy_init(pdev, hsotg->plat->phy_type); + else { + phy_init(hsotg->phy); + phy_power_on(hsotg->phy); + } } /** @@ -2764,24 +2758,25 @@ static void s3c_hsotg_phy_enable(struct s3c_hsotg *hsotg) * A wrapper for platform code responsible for controlling * low-level USB code */ -static void s3c_hsotg_phy_disable(struct s3c_hsotg *hsotg) +static void s3c_hsotg_phy_disable(struct dwc2_hsotg *hsotg) { struct platform_device *pdev = to_platform_device(hsotg->dev); - if (hsotg->phy) { - phy_power_off(hsotg->phy); - phy_exit(hsotg->phy); - } else if (hsotg->uphy) + if (hsotg->uphy) usb_phy_shutdown(hsotg->uphy); - else if (hsotg->plat->phy_exit) + else if (hsotg->plat && hsotg->plat->phy_exit) hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type); + else { + phy_power_off(hsotg->phy); + phy_exit(hsotg->phy); + } } /** * s3c_hsotg_init - initalize the usb core * @hsotg: The driver state */ -static void s3c_hsotg_init(struct s3c_hsotg *hsotg) +static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) { /* unmask subset of endpoint interrupts */ @@ -2831,7 +2826,7 @@ static void s3c_hsotg_init(struct s3c_hsotg *hsotg) static int s3c_hsotg_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { - struct s3c_hsotg *hsotg = to_hsotg(gadget); + struct dwc2_hsotg *hsotg = to_hsotg(gadget); int ret; if (!hsotg) { @@ -2859,6 +2854,9 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, hsotg->gadget.dev.of_node = hsotg->dev->of_node; hsotg->gadget.speed = USB_SPEED_UNKNOWN; + if (!IS_ERR(hsotg->clk)) + clk_enable(hsotg->clk); + ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); if (ret) { @@ -2885,7 +2883,7 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, static int s3c_hsotg_udc_stop(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { - struct s3c_hsotg *hsotg = to_hsotg(gadget); + struct dwc2_hsotg *hsotg = to_hsotg(gadget); unsigned long flags = 0; int ep; @@ -2893,13 +2891,11 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget, return -ENODEV; /* all endpoints should be shutdown */ - for (ep = 0; ep < hsotg->num_of_eps; ep++) + for (ep = 1; ep < hsotg->num_of_eps; ep++) s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); spin_lock_irqsave(&hsotg->lock, flags); - s3c_hsotg_phy_disable(hsotg); - if (!driver) hsotg->driver = NULL; @@ -2907,7 +2903,11 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget, spin_unlock_irqrestore(&hsotg->lock, flags); - regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); + regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + + if (!IS_ERR(hsotg->clk)) + clk_disable(hsotg->clk); return 0; } @@ -2932,17 +2932,20 @@ static int s3c_hsotg_gadget_getframe(struct usb_gadget *gadget) */ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) { - struct s3c_hsotg *hsotg = to_hsotg(gadget); + struct dwc2_hsotg *hsotg = to_hsotg(gadget); unsigned long flags = 0; - dev_dbg(hsotg->dev, "%s: is_in: %d\n", __func__, is_on); + dev_dbg(hsotg->dev, "%s: is_on: %d\n", __func__, is_on); spin_lock_irqsave(&hsotg->lock, flags); if (is_on) { s3c_hsotg_phy_enable(hsotg); + if (!IS_ERR(hsotg->clk)) + clk_enable(hsotg->clk); s3c_hsotg_core_init(hsotg); } else { - s3c_hsotg_disconnect(hsotg); + if (!IS_ERR(hsotg->clk)) + clk_disable(hsotg->clk); s3c_hsotg_phy_disable(hsotg); } @@ -2969,11 +2972,10 @@ static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { * creation) to give to the gadget driver. Setup the endpoint name, any * direction information and other state that may be required. */ -static void s3c_hsotg_initep(struct s3c_hsotg *hsotg, +static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, int epnum) { - u32 ptxfifo; char *dir; if (epnum == 0) @@ -3001,15 +3003,6 @@ static void s3c_hsotg_initep(struct s3c_hsotg *hsotg, usb_ep_set_maxpacket_limit(&hs_ep->ep, epnum ? 1024 : EP0_MPS_LIMIT); hs_ep->ep.ops = &s3c_hsotg_ep_ops; - /* - * Read the FIFO size for the Periodic TX FIFO, even if we're - * an OUT endpoint, we may as well do this if in future the - * code is changed to make each endpoint's direction changeable. - */ - - ptxfifo = readl(hsotg->regs + DPTXFSIZN(epnum)); - hs_ep->fifo_size = FIFOSIZE_DEPTH_GET(ptxfifo) * 4; - /* * if we're using dma, we need to set the next-endpoint pointer * to be something valid. @@ -3028,28 +3021,31 @@ static void s3c_hsotg_initep(struct s3c_hsotg *hsotg, * * Read the USB core HW configuration registers */ -static void s3c_hsotg_hw_cfg(struct s3c_hsotg *hsotg) +static void s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) { - u32 cfg2, cfg4; + u32 cfg2, cfg3, cfg4; /* check hardware configuration */ cfg2 = readl(hsotg->regs + 0x48); hsotg->num_of_eps = (cfg2 >> 10) & 0xF; - dev_info(hsotg->dev, "EPs:%d\n", hsotg->num_of_eps); + cfg3 = readl(hsotg->regs + 0x4C); + hsotg->fifo_mem = (cfg3 >> 16); cfg4 = readl(hsotg->regs + 0x50); hsotg->dedicated_fifos = (cfg4 >> 25) & 1; - dev_info(hsotg->dev, "%s fifos\n", - hsotg->dedicated_fifos ? "dedicated" : "shared"); + dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", + hsotg->num_of_eps, + hsotg->dedicated_fifos ? "dedicated" : "shared", + hsotg->fifo_mem); } /** * s3c_hsotg_dump - dump state of the udc * @param: The device state */ -static void s3c_hsotg_dump(struct s3c_hsotg *hsotg) +static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) { #ifdef DEBUG struct device *dev = hsotg->dev; @@ -3108,7 +3104,7 @@ static void s3c_hsotg_dump(struct s3c_hsotg *hsotg) */ static int state_show(struct seq_file *seq, void *v) { - struct s3c_hsotg *hsotg = seq->private; + struct dwc2_hsotg *hsotg = seq->private; void __iomem *regs = hsotg->regs; int idx; @@ -3178,7 +3174,7 @@ static const struct file_operations state_fops = { */ static int fifo_show(struct seq_file *seq, void *v) { - struct s3c_hsotg *hsotg = seq->private; + struct dwc2_hsotg *hsotg = seq->private; void __iomem *regs = hsotg->regs; u32 val; int idx; @@ -3234,7 +3230,7 @@ static const char *decode_direction(int is_in) static int ep_show(struct seq_file *seq, void *v) { struct s3c_hsotg_ep *ep = seq->private; - struct s3c_hsotg *hsotg = ep->parent; + struct dwc2_hsotg *hsotg = ep->parent; struct s3c_hsotg_req *req; void __iomem *regs = hsotg->regs; int index = ep->index; @@ -3311,7 +3307,7 @@ static const struct file_operations ep_fops = { * with the same name as the device itself, in case we end up * with multiple blocks in future systems. */ -static void s3c_hsotg_create_debug(struct s3c_hsotg *hsotg) +static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) { struct dentry *root; unsigned epidx; @@ -3357,7 +3353,7 @@ static void s3c_hsotg_create_debug(struct s3c_hsotg *hsotg) * * Cleanup (remove) the debugfs files for use on module exit. */ -static void s3c_hsotg_delete_debug(struct s3c_hsotg *hsotg) +static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) { unsigned epidx; @@ -3372,94 +3368,63 @@ static void s3c_hsotg_delete_debug(struct s3c_hsotg *hsotg) } /** - * s3c_hsotg_probe - probe function for hsotg driver - * @pdev: The platform information for the driver + * dwc2_gadget_init - init function for gadget + * @dwc2: The data structure for the DWC2 driver. + * @irq: The IRQ number for the controller. */ -static int s3c_hsotg_probe(struct platform_device *pdev) +int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { - struct s3c_hsotg_plat *plat = dev_get_platdata(&pdev->dev); + struct device *dev = hsotg->dev; + struct s3c_hsotg_plat *plat = dev->platform_data; struct phy *phy; struct usb_phy *uphy; - struct device *dev = &pdev->dev; struct s3c_hsotg_ep *eps; - struct s3c_hsotg *hsotg; - struct resource *res; int epnum; int ret; int i; - hsotg = devm_kzalloc(&pdev->dev, sizeof(struct s3c_hsotg), GFP_KERNEL); - if (!hsotg) { - dev_err(dev, "cannot get memory\n"); - return -ENOMEM; - } + /* Set default UTMI width */ + hsotg->phyif = GUSBCFG_PHYIF16; /* * Attempt to find a generic PHY, then look for an old style * USB PHY, finally fall back to pdata */ - phy = devm_phy_get(&pdev->dev, "usb2-phy"); + phy = devm_phy_get(dev, "usb2-phy"); if (IS_ERR(phy)) { uphy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); if (IS_ERR(uphy)) { /* Fallback for pdata */ - plat = dev_get_platdata(&pdev->dev); + plat = dev_get_platdata(dev); if (!plat) { - dev_err(&pdev->dev, + dev_err(dev, "no platform data or transceiver defined\n"); return -EPROBE_DEFER; } hsotg->plat = plat; } else hsotg->uphy = uphy; - } else + } else { hsotg->phy = phy; - - hsotg->dev = dev; - - hsotg->clk = devm_clk_get(&pdev->dev, "otg"); - if (IS_ERR(hsotg->clk)) { - dev_err(dev, "cannot get otg clock\n"); - return PTR_ERR(hsotg->clk); - } - - platform_set_drvdata(pdev, hsotg); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - hsotg->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(hsotg->regs)) { - ret = PTR_ERR(hsotg->regs); - goto err_clk; - } - - ret = platform_get_irq(pdev, 0); - if (ret < 0) { - dev_err(dev, "cannot find IRQ\n"); - goto err_clk; - } - - spin_lock_init(&hsotg->lock); - - hsotg->irq = ret; - - ret = devm_request_irq(&pdev->dev, hsotg->irq, s3c_hsotg_irq, 0, - dev_name(dev), hsotg); - if (ret < 0) { - dev_err(dev, "cannot claim IRQ\n"); - goto err_clk; + /* + * If using the generic PHY framework, check if the PHY bus + * width is 8-bit and set the phyif appropriately. + */ + if (phy_get_bus_width(phy) == 8) + hsotg->phyif = GUSBCFG_PHYIF8; } - dev_info(dev, "regs %p, irq %d\n", hsotg->regs, hsotg->irq); + hsotg->clk = devm_clk_get(dev, "otg"); + if (IS_ERR(hsotg->clk)) + dev_warn(dev, "cannot get otg clock\n"); hsotg->gadget.max_speed = USB_SPEED_HIGH; hsotg->gadget.ops = &s3c_hsotg_gadget_ops; hsotg->gadget.name = dev_name(dev); - /* reset the system */ - - clk_prepare_enable(hsotg->clk); + if (!IS_ERR(hsotg->clk)) + clk_prepare_enable(hsotg->clk); /* regulators */ @@ -3477,29 +3442,28 @@ static int s3c_hsotg_probe(struct platform_device *pdev) hsotg->supplies); if (ret) { - dev_err(hsotg->dev, "failed to enable supplies: %d\n", ret); + dev_err(dev, "failed to enable supplies: %d\n", ret); goto err_supplies; } - /* Set default UTMI width */ - hsotg->phyif = GUSBCFG_PHYIF16; - - /* - * If using the generic PHY framework, check if the PHY bus - * width is 8-bit and set the phyif appropriately. - */ - if (hsotg->phy && (phy_get_bus_width(phy) == 8)) - hsotg->phyif = GUSBCFG_PHYIF8; - - if (hsotg->phy) - phy_init(hsotg->phy); - /* usb phy enable */ s3c_hsotg_phy_enable(hsotg); s3c_hsotg_corereset(hsotg); - s3c_hsotg_init(hsotg); s3c_hsotg_hw_cfg(hsotg); + s3c_hsotg_init(hsotg); + + ret = devm_request_irq(dev, irq, dwc2_handle_common_intr, IRQF_SHARED, + dev_name(dev), hsotg); + if (ret < 0) { + s3c_hsotg_phy_disable(hsotg); + if (!IS_ERR(hsotg->clk)) + clk_disable_unprepare(hsotg->clk); + regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + dev_err(dev, "cannot claim IRQ\n"); + goto err_clk; + } /* hsotg->num_of_eps holds number of EPs other than ep0 */ @@ -3543,13 +3507,13 @@ static int s3c_hsotg_probe(struct platform_device *pdev) ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); if (ret) { - dev_err(hsotg->dev, "failed to disable supplies: %d\n", ret); + dev_err(dev, "failed to disable supplies: %d\n", ret); goto err_ep_mem; } s3c_hsotg_phy_disable(hsotg); - ret = usb_add_gadget_udc(&pdev->dev, &hsotg->gadget); + ret = usb_add_gadget_udc(dev, &hsotg->gadget); if (ret) goto err_ep_mem; @@ -3564,7 +3528,8 @@ static int s3c_hsotg_probe(struct platform_device *pdev) err_supplies: s3c_hsotg_phy_disable(hsotg); err_clk: - clk_disable_unprepare(hsotg->clk); + if (!IS_ERR(hsotg->clk)) + clk_disable_unprepare(hsotg->clk); return ret; } @@ -3573,10 +3538,8 @@ static int s3c_hsotg_probe(struct platform_device *pdev) * s3c_hsotg_remove - remove function for hsotg driver * @pdev: The platform information for the driver */ -static int s3c_hsotg_remove(struct platform_device *pdev) +int s3c_hsotg_remove(struct dwc2_hsotg *hsotg) { - struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); - usb_del_gadget_udc(&hsotg->gadget); s3c_hsotg_delete_debug(hsotg); @@ -3586,17 +3549,14 @@ static int s3c_hsotg_remove(struct platform_device *pdev) usb_gadget_unregister_driver(hsotg->driver); } - s3c_hsotg_phy_disable(hsotg); - if (hsotg->phy) - phy_exit(hsotg->phy); - clk_disable_unprepare(hsotg->clk); + if (!IS_ERR(hsotg->clk)) + clk_disable_unprepare(hsotg->clk); return 0; } -static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state) +int s3c_hsotg_suspend(struct dwc2_hsotg *hsotg) { - struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); unsigned long flags; int ret = 0; @@ -3617,22 +3577,26 @@ static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state) ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); + if (!IS_ERR(hsotg->clk)) + clk_disable(hsotg->clk); } return ret; } -static int s3c_hsotg_resume(struct platform_device *pdev) +int s3c_hsotg_resume(struct dwc2_hsotg *hsotg) { - struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); unsigned long flags; int ret = 0; if (hsotg->driver) { dev_info(hsotg->dev, "resuming usb gadget %s\n", hsotg->driver->driver.name); + + if (!IS_ERR(hsotg->clk)) + clk_enable(hsotg->clk); ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), - hsotg->supplies); + hsotg->supplies); } spin_lock_irqsave(&hsotg->lock, flags); @@ -3643,31 +3607,3 @@ static int s3c_hsotg_resume(struct platform_device *pdev) return ret; } - -#ifdef CONFIG_OF -static const struct of_device_id s3c_hsotg_of_ids[] = { - { .compatible = "samsung,s3c6400-hsotg", }, - { .compatible = "snps,dwc2", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, s3c_hsotg_of_ids); -#endif - -static struct platform_driver s3c_hsotg_driver = { - .driver = { - .name = "s3c-hsotg", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(s3c_hsotg_of_ids), - }, - .probe = s3c_hsotg_probe, - .remove = s3c_hsotg_remove, - .suspend = s3c_hsotg_suspend, - .resume = s3c_hsotg_resume, -}; - -module_platform_driver(s3c_hsotg_driver); - -MODULE_DESCRIPTION("Samsung S3C USB High-speed/OtG device"); -MODULE_AUTHOR("Ben Dooks "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:s3c-hsotg"); diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 4d918ed8d3433..9f3c131a89a80 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -404,7 +404,7 @@ static int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, return 0; spin_lock_irqsave(&hsotg->lock, flags); - tr_type = dwc2_hcd_select_transactions(hsotg); + tr_type = dwc2_hcd_select_transactions(hsotg, mem_flags); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); spin_unlock_irqrestore(&hsotg->lock, flags); @@ -697,8 +697,12 @@ static void *dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg, } static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, - struct dwc2_host_chan *chan, void *bufptr) + struct dwc2_host_chan *chan, + struct dwc2_hcd_urb *urb, void *bufptr, + gfp_t mem_flags) { + struct usb_hcd *hcd; + struct urb *usb_urb; u32 buf_size; if (chan->ep_type != USB_ENDPOINT_XFER_ISOC) @@ -709,17 +713,28 @@ static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, if (!qh->dw_align_buf) { qh->dw_align_buf = dma_alloc_coherent(hsotg->dev, buf_size, &qh->dw_align_buf_dma, - GFP_ATOMIC); + mem_flags); if (!qh->dw_align_buf) return -ENOMEM; } - if (!chan->ep_is_in && chan->xfer_len) { - dma_sync_single_for_cpu(hsotg->dev, chan->xfer_dma, buf_size, - DMA_TO_DEVICE); - memcpy(qh->dw_align_buf, bufptr, chan->xfer_len); - dma_sync_single_for_device(hsotg->dev, chan->xfer_dma, buf_size, - DMA_TO_DEVICE); + if (chan->xfer_len) { + dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); + usb_urb = urb->priv; + + if (usb_urb) { + if (usb_urb->transfer_flags & + (URB_SETUP_MAP_SINGLE | URB_DMA_MAP_SG | + URB_DMA_MAP_PAGE | URB_DMA_MAP_SINGLE)) { + hcd = dwc2_hsotg_to_hcd(hsotg); + usb_hcd_unmap_urb_for_dma(hcd, usb_urb); + } + if (!chan->ep_is_in) + memcpy(qh->dw_align_buf, bufptr, + chan->xfer_len); + } else { + dev_warn(hsotg->dev, "no URB in dwc2_urb\n"); + } } chan->align_buf = qh->dw_align_buf_dma; @@ -735,7 +750,9 @@ static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, * @qh: Transactions from the first QTD for this QH are selected and assigned * to a free host channel */ -static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, + gfp_t mem_flags) { struct dwc2_host_chan *chan; struct dwc2_hcd_urb *urb; @@ -828,7 +845,8 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) /* Non DWORD-aligned buffer case */ if (bufptr) { dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); - if (dwc2_hc_setup_align_buf(hsotg, qh, chan, bufptr)) { + if (dwc2_hc_setup_align_buf(hsotg, qh, chan, urb, bufptr, + mem_flags)) { dev_err(hsotg->dev, "%s: Failed to allocate memory to handle non-dword aligned buffer\n", __func__); @@ -872,7 +890,7 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) * Return: The types of new transactions that were assigned to host channels */ enum dwc2_transaction_type dwc2_hcd_select_transactions( - struct dwc2_hsotg *hsotg) + struct dwc2_hsotg *hsotg, gfp_t mem_flags) { enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE; struct list_head *qh_ptr; @@ -894,8 +912,7 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions( hsotg->available_host_channels--; } qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); - if (dwc2_assign_and_init_hc(hsotg, qh)) - break; + dwc2_assign_and_init_hc(hsotg, qh, mem_flags); /* * Move the QH from the periodic ready schedule to the @@ -921,14 +938,7 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions( if (list_empty(&hsotg->free_hc_list)) break; qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); - if (hsotg->core_params->uframe_sched > 0) { - if (hsotg->available_host_channels < 1) - break; - hsotg->available_host_channels--; - } - - if (dwc2_assign_and_init_hc(hsotg, qh)) - break; + dwc2_assign_and_init_hc(hsotg, qh, mem_flags); /* * Move the QH from the non-periodic inactive schedule to the @@ -1355,6 +1365,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) hsotg->op_state = OTG_STATE_B_PERIPHERAL; dwc2_core_init(hsotg, false, -1); dwc2_enable_global_interrupts(hsotg); + s3c_hsotg_core_init(hsotg); } else { /* A-Device connector (Host Mode) */ dev_dbg(hsotg->dev, "connId A\n"); @@ -2823,7 +2834,6 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq, hcd->has_tt = 1; - spin_lock_init(&hsotg->lock); ((struct wrapper_priv_data *) &hcd->hcd_priv)->hsotg = hsotg; hsotg->priv = hcd; diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index fdc6d489084ae..2d65a10025242 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -459,7 +459,7 @@ extern int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); /* Transaction Execution Functions */ extern enum dwc2_transaction_type dwc2_hcd_select_transactions( - struct dwc2_hsotg *hsotg); + struct dwc2_hsotg *hsotg, gfp_t mem_flags); extern void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, enum dwc2_transaction_type tr_type); @@ -666,9 +666,6 @@ extern irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg); */ extern void dwc2_hcd_stop(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); -extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); - /** * dwc2_hcd_is_b_host() - Returns 1 if core currently is acting as B host, * and 0 otherwise @@ -677,13 +674,6 @@ extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); */ extern int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg); -/** - * dwc2_hcd_get_frame_number() - Returns current frame number - * - * @hsotg: The DWC2 HCD - */ -extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); - /** * dwc2_hcd_dump_state() - Dumps hsotg state * diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c index 3376177e4d3c0..9ead1f1d6c21c 100644 --- a/drivers/usb/dwc2/hcd_ddma.c +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -1199,7 +1199,7 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, } } - tr_type = dwc2_hcd_select_transactions(hsotg); + tr_type = dwc2_hcd_select_transactions(hsotg, GFP_ATOMIC); if (tr_type != DWC2_TRANSACTION_NONE || continue_isoc_xfer) { if (continue_isoc_xfer) { if (tr_type == DWC2_TRANSACTION_NONE) diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c index 47b9eb5389b46..371a2a90d41a7 100644 --- a/drivers/usb/dwc2/hcd_intr.c +++ b/drivers/usb/dwc2/hcd_intr.c @@ -143,7 +143,7 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready); } - tr_type = dwc2_hcd_select_transactions(hsotg); + tr_type = dwc2_hcd_select_transactions(hsotg, GFP_ATOMIC); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); @@ -465,12 +465,8 @@ static int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, /* Non DWORD-aligned buffer case handling */ if (chan->align_buf && xfer_length && chan->ep_is_in) { dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - dma_sync_single_for_cpu(hsotg->dev, urb->dma, urb->length, - DMA_FROM_DEVICE); memcpy(urb->buf + urb->actual_length, chan->qh->dw_align_buf, xfer_length); - dma_sync_single_for_device(hsotg->dev, urb->dma, urb->length, - DMA_FROM_DEVICE); } dev_vdbg(hsotg->dev, "urb->actual_length=%d xfer_length=%d\n", @@ -560,14 +556,9 @@ static enum dwc2_halt_status dwc2_update_isoc_urb_state( chan->ep_is_in) { dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - dma_sync_single_for_cpu(hsotg->dev, urb->dma, - urb->length, DMA_FROM_DEVICE); memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, chan->qh->dw_align_buf, frame_desc->actual_length); - dma_sync_single_for_device(hsotg->dev, urb->dma, - urb->length, - DMA_FROM_DEVICE); } break; case DWC2_HC_XFER_FRAME_OVERRUN: @@ -594,14 +585,9 @@ static enum dwc2_halt_status dwc2_update_isoc_urb_state( chan->ep_is_in) { dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - dma_sync_single_for_cpu(hsotg->dev, urb->dma, - urb->length, DMA_FROM_DEVICE); memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset, chan->qh->dw_align_buf, frame_desc->actual_length); - dma_sync_single_for_device(hsotg->dev, urb->dma, - urb->length, - DMA_FROM_DEVICE); } /* Skip whole frame */ @@ -772,7 +758,7 @@ static void dwc2_release_channel(struct dwc2_hsotg *hsotg, writel(haintmsk, hsotg->regs + HAINTMSK); /* Try to queue more transfers now that there's a free channel */ - tr_type = dwc2_hcd_select_transactions(hsotg); + tr_type = dwc2_hcd_select_transactions(hsotg, GFP_ATOMIC); if (tr_type != DWC2_TRANSACTION_NONE) dwc2_hcd_queue_transactions(hsotg, tr_type); } @@ -937,12 +923,8 @@ static int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, if (chan->align_buf) { dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - dma_sync_single_for_cpu(hsotg->dev, qtd->urb->dma, - qtd->urb->length, DMA_FROM_DEVICE); memcpy(qtd->urb->buf + frame_desc->offset + qtd->isoc_split_offset, chan->qh->dw_align_buf, len); - dma_sync_single_for_device(hsotg->dev, qtd->urb->dma, - qtd->urb->length, DMA_FROM_DEVICE); } qtd->isoc_split_offset += len; @@ -1170,12 +1152,8 @@ static void dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg, /* Non DWORD-aligned buffer case handling */ if (chan->align_buf && xfer_length && chan->ep_is_in) { dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); - dma_sync_single_for_cpu(hsotg->dev, urb->dma, urb->length, - DMA_FROM_DEVICE); memcpy(urb->buf + urb->actual_length, chan->qh->dw_align_buf, xfer_length); - dma_sync_single_for_device(hsotg->dev, urb->dma, urb->length, - DMA_FROM_DEVICE); } urb->actual_length += xfer_length; diff --git a/drivers/usb/dwc2/pci.c b/drivers/usb/dwc2/pci.c index c291fca5d21fc..6d33ecf2e2955 100644 --- a/drivers/usb/dwc2/pci.c +++ b/drivers/usb/dwc2/pci.c @@ -141,6 +141,7 @@ static int dwc2_driver_probe(struct pci_dev *dev, pci_set_master(dev); + spin_lock_init(&hsotg->lock); retval = dwc2_hcd_init(hsotg, dev->irq, &dwc2_module_params); if (retval) { pci_disable_device(dev); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index a10e7a3535768..59265adea53ec 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -42,6 +42,8 @@ #include #include +#include + #include "core.h" #include "hcd.h" @@ -90,7 +92,14 @@ static int dwc2_driver_remove(struct platform_device *dev) { struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); - dwc2_hcd_remove(hsotg); + if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) + s3c_hsotg_remove(hsotg); + else if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) + dwc2_hcd_remove(hsotg); + else { /* dual role */ + dwc2_hcd_remove(hsotg); + s3c_hsotg_remove(hsotg); + } return 0; } @@ -98,6 +107,7 @@ static int dwc2_driver_remove(struct platform_device *dev) static const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, { .compatible = "snps,dwc2", .data = NULL }, + { .compatible = "samsung,s3c6400-hsotg", .data = NULL}, {}, }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); @@ -171,15 +181,52 @@ static int dwc2_driver_probe(struct platform_device *dev) dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", (unsigned long)res->start, hsotg->regs); - retval = dwc2_hcd_init(hsotg, irq, params); - if (retval) - return retval; + hsotg->dr_mode = of_usb_get_dr_mode(dev->dev.of_node); + + spin_lock_init(&hsotg->lock); + if (IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)) { + retval = dwc2_gadget_init(hsotg, irq); + if (retval) + return retval; + retval = dwc2_hcd_init(hsotg, irq, params); + if (retval) + return retval; + } else if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) { + retval = dwc2_hcd_init(hsotg, irq, params); + if (retval) + return retval; + } else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) { + retval = dwc2_gadget_init(hsotg, irq); + if (retval) + return retval; + } platform_set_drvdata(dev, hsotg); return retval; } +static int dwc2_suspend(struct platform_device *dev, pm_message_t state) +{ + struct dwc2_hsotg *dwc2 = platform_get_drvdata(dev); + int ret = 0; + + if (dwc2_is_device_mode(dwc2)) + ret = s3c_hsotg_suspend(dwc2); + return ret; +} + +static int dwc2_resume(struct platform_device *dev) +{ + struct dwc2_hsotg *dwc2 = platform_get_drvdata(dev); + int ret = 0; + + if (dwc2_is_device_mode(dwc2)) + ret = s3c_hsotg_resume(dwc2); + + return ret; +} + static struct platform_driver dwc2_platform_driver = { .driver = { .name = dwc2_driver_name, @@ -187,6 +234,8 @@ static struct platform_driver dwc2_platform_driver = { }, .probe = dwc2_driver_probe, .remove = dwc2_driver_remove, + .suspend = dwc2_suspend, + .resume = dwc2_resume, }; module_platform_driver(dwc2_platform_driver); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 8bf495ffb0207..83ef77310dbcd 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -42,6 +42,16 @@ config VIDEOMODE_HELPERS config HDMI bool +config FB_ALTERA_VIP + tristate "Altera VIP Frame Reader framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This driver supports the Altera Video and Image Processing(VIP) + Frame Reader + if VT source "drivers/video/console/Kconfig" endif diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 9ad3c17d64568..b46be2e76a0d4 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ obj-y += fbdev/ +obj-$(CONFIG_FB_ALTERA_VIP) += altvipfb.o obj-$(CONFIG_VIDEOMODE_HELPERS) += display_timing.o videomode.o ifeq ($(CONFIG_OF),y) diff --git a/drivers/video/altvipfb.c b/drivers/video/altvipfb.c new file mode 100644 index 0000000000000..b247858ba43a7 --- /dev/null +++ b/drivers/video/altvipfb.c @@ -0,0 +1,303 @@ +/* + * altvipfb.c -- Altera Video and Image Processing(VIP) Frame Reader driver + * + * This is based on a driver made by Thomas Chou and + * Walter Goossens This driver supports the Altera VIP + * Frame Reader component. More info on the hardware can be found in + * the Altera Video and Image Processing Suite User Guide at this address + * http://www.altera.com/literature/ug/ug_vip.pdf. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#define PALETTE_SIZE 256 +#define DRIVER_NAME "altvipfb" + +/* control registers */ +#define ALTVIPFB_CONTROL 0 +#define ALTVIPFB_FRAME_SELECT 12 +#define ALTVIPFB_FRAME0_BASE_ADDRESS 16 +#define ALTVIPFB_FRAME0_NUM_WORDS 20 +#define ALTVIPFB_FRAME0_SAMPLES 24 +#define ALTVIPFB_FRAME0_WIDTH 32 +#define ALTVIPFB_FRAME0_HEIGHT 36 +#define ALTVIPFB_FRAME0_INTERLACED 40 + +struct altvipfb_type; + +struct altvipfb_dev { + struct platform_device *pdev; + struct fb_info info; + struct resource *reg_res; + void __iomem *base; + int mem_word_width; + u32 pseudo_palette[PALETTE_SIZE]; +}; + +static int altvipfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + /* + * Set a single color register. The values supplied have a 32 bit + * magnitude. + * Return != 0 for invalid regno. + */ + + if (regno > 255) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + if (regno < 255) { + ((u32 *)info->pseudo_palette)[regno] = + ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255); + } + + return 0; +} + +static struct fb_ops altvipfb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_setcolreg = altvipfb_setcolreg, +}; + +static int altvipfb_of_setup(struct altvipfb_dev *fbdev) +{ + struct device_node *np = fbdev->pdev->dev.of_node; + int ret; + u32 bits_per_color; + + ret = of_property_read_u32(np, "max-width", &fbdev->info.var.xres); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'max-width'"); + return ret; + } + fbdev->info.var.xres_virtual = fbdev->info.var.xres, + + ret = of_property_read_u32(np, "max-height", &fbdev->info.var.yres); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'max-height'"); + return ret; + } + fbdev->info.var.yres_virtual = fbdev->info.var.yres; + + ret = of_property_read_u32(np, "bits-per-color", &bits_per_color); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'bits-per-color'"); + return ret; + } + if (bits_per_color != 8) { + dev_err(&fbdev->pdev->dev, + "bits-per-color is set to %i. Curently only 8 is supported.", + bits_per_color); + return -ENODEV; + } + fbdev->info.var.bits_per_pixel = 32; + + ret = of_property_read_u32(np, "mem-word-width", + &fbdev->mem_word_width); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'mem-word-width'"); + return ret; + } + if (!(fbdev->mem_word_width >= 32 && fbdev->mem_word_width % 32 == 0)) { + dev_err(&fbdev->pdev->dev, + "mem-word-width is set to %i. must be >= 32 and multiple of 32.", + fbdev->mem_word_width); + return -ENODEV; + } + + return 0; +} + +static void altvipfb_start_hw(struct altvipfb_dev *fbdev) +{ + writel(fbdev->info.fix.smem_start, fbdev->base + + ALTVIPFB_FRAME0_BASE_ADDRESS); + writel(fbdev->info.var.xres * fbdev->info.var.yres / + (fbdev->mem_word_width/32), + fbdev->base + ALTVIPFB_FRAME0_NUM_WORDS); + writel(fbdev->info.var.xres * fbdev->info.var.yres, + fbdev->base + ALTVIPFB_FRAME0_SAMPLES); + writel(fbdev->info.var.xres, fbdev->base + ALTVIPFB_FRAME0_WIDTH); + writel(fbdev->info.var.yres, fbdev->base + ALTVIPFB_FRAME0_HEIGHT); + writel(3, fbdev->base + ALTVIPFB_FRAME0_INTERLACED); + writel(0, fbdev->base + ALTVIPFB_FRAME_SELECT); + + /* Finally set the control register to 1 to start streaming */ + writel(1, fbdev->base + ALTVIPFB_CONTROL); +} + +static void altvipfb_disable_hw(struct altvipfb_dev *fbdev) +{ + /* set the control register to 0 to stop streaming */ + writel(0, fbdev->base + ALTVIPFB_CONTROL); +} + + +static int altvipfb_setup_fb_info(struct altvipfb_dev *fbdev) +{ + struct fb_info *info = &fbdev->info; + int ret; + + strcpy(info->fix.id, DRIVER_NAME); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.accel = FB_ACCEL_NONE; + + info->fbops = &altvipfb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.vmode = FB_VMODE_NONINTERLACED; + + ret = altvipfb_of_setup(fbdev); + if (ret) + return ret; + + /* settings for 32bit pixels */ + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.red.msb_right = 0; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->var.blue.msb_right = 0; + + info->fix.line_length = (info->var.xres * + (info->var.bits_per_pixel >> 3)); + info->fix.smem_len = info->fix.line_length * info->var.yres; + + info->pseudo_palette = fbdev->pseudo_palette; + info->flags = FBINFO_FLAG_DEFAULT; + + return 0; +} + +static int altvipfb_probe(struct platform_device *pdev) +{ + int retval; + void *fbmem_virt; + struct altvipfb_dev *fbdev; + + fbdev = devm_kzalloc(&pdev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + fbdev->pdev = pdev; + fbdev->reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!fbdev->reg_res) + return -ENODEV; + + retval = altvipfb_setup_fb_info(fbdev); + + fbmem_virt = dma_alloc_coherent(NULL, + fbdev->info.fix.smem_len, + (void *)&(fbdev->info.fix.smem_start), + GFP_KERNEL); + if (!fbmem_virt) { + dev_err(&pdev->dev, + "altvipfb: unable to allocate %d Bytes fb memory\n", + fbdev->info.fix.smem_len); + return retval; + } + + fbdev->info.screen_base = fbmem_virt; + + retval = fb_alloc_cmap(&fbdev->info.cmap, PALETTE_SIZE, 0); + if (retval < 0) + goto err_dma_free; + + platform_set_drvdata(pdev, fbdev); + + fbdev->base = devm_ioremap_resource(&pdev->dev, fbdev->reg_res); + if (IS_ERR(fbdev->base)) { + dev_err(&pdev->dev, "devm_ioremap_resource failed\n"); + retval = PTR_ERR(fbdev->base); + goto err_dealloc_cmap; + } + + altvipfb_start_hw(fbdev); + + retval = register_framebuffer(&fbdev->info); + if (retval < 0) + goto err_dealloc_cmap; + + dev_info(&pdev->dev, "fb%d: %s frame buffer device at 0x%x+0x%x\n", + fbdev->info.node, fbdev->info.fix.id, + (unsigned)fbdev->info.fix.smem_start, + fbdev->info.fix.smem_len); + + return 0; + +err_dealloc_cmap: + fb_dealloc_cmap(&fbdev->info.cmap); +err_dma_free: + dma_free_coherent(NULL, fbdev->info.fix.smem_len, fbmem_virt, + fbdev->info.fix.smem_start); + return retval; +} + +static int altvipfb_remove(struct platform_device *dev) +{ + struct altvipfb_dev *fbdev = platform_get_drvdata(dev); + + if (fbdev) { + unregister_framebuffer(&fbdev->info); + fb_dealloc_cmap(&fbdev->info.cmap); + dma_free_coherent(NULL, fbdev->info.fix.smem_len, + fbdev->info.screen_base, + fbdev->info.fix.smem_start); + altvipfb_disable_hw(fbdev); + } + return 0; +} + + +static struct of_device_id altvipfb_match[] = { + { .compatible = "altr,vip-frame-reader-1.0" }, + { .compatible = "altr,vip-frame-reader-9.1" }, + {}, +}; +MODULE_DEVICE_TABLE(of, altvipfb_match); + +static struct platform_driver altvipfb_driver = { + .probe = altvipfb_probe, + .remove = altvipfb_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = altvipfb_match, + }, +}; +module_platform_driver(altvipfb_driver); + +MODULE_DESCRIPTION("Altera VIP Frame Reader framebuffer driver"); +MODULE_AUTHOR("Chris Rauer "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/altera_hwmutex.h b/include/linux/altera_hwmutex.h new file mode 100644 index 0000000000000..166502b379f6e --- /dev/null +++ b/include/linux/altera_hwmutex.h @@ -0,0 +1,41 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#ifndef _ALTERA_MUTEX_H +#define _ALTERA_MUTEX_H + +#include +#include + +struct altera_mutex { + struct list_head list; + struct platform_device *pdev; + struct mutex lock; + void __iomem *regs; + bool requested; +}; + +extern struct altera_mutex *altera_mutex_request(struct device_node *mutex_np); +extern int altera_mutex_free(struct altera_mutex *mutex); + +extern int altera_mutex_lock(struct altera_mutex *mutex, u16 owner, u16 value); + +extern int altera_mutex_trylock(struct altera_mutex *mutex, u16 owner, + u16 value); +extern int altera_mutex_unlock(struct altera_mutex *mutex, u16 owner); +extern int altera_mutex_owned(struct altera_mutex *mutex, u16 owner); +extern int altera_mutex_is_locked(struct altera_mutex *mutex); + +#endif /* _ALTERA_MUTEX_H */ diff --git a/include/linux/amba/pl330.h b/include/linux/amba/pl330.h index fe93758e84036..e3408c6b54672 100644 --- a/include/linux/amba/pl330.h +++ b/include/linux/amba/pl330.h @@ -29,6 +29,14 @@ struct dma_pl330_platdata { dma_cap_mask_t cap_mask; /* Bytes to allocate for MC buffer */ unsigned mcbuf_sz; + /* Start of IRQ numbers if support multiple IRQs */ + int irq_start; + /* End of IRQ numbers if support multiple IRQs */ + int irq_end; + /* Platform specific initialization function pointer*/ + int (*init)(struct amba_device *adev); + /* Platform specific exit function pointer*/ + void (*exit)(struct amba_device *adev); }; extern bool pl330_filter(struct dma_chan *chan, void *param); diff --git a/include/linux/fpga.h b/include/linux/fpga.h new file mode 100644 index 0000000000000..37f8e42caa4c1 --- /dev/null +++ b/include/linux/fpga.h @@ -0,0 +1,184 @@ +/* + * FPGA Framework + * + * Copyright (C) 2013 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include + +#ifndef _LINUX_FPGA_H +#define _LINUX_FPGA_H + +struct fpga_manager; +struct fpga_mgr_transport; + +/*---------------------------------------------------------------------------*/ + +struct fpga_mgr_transport { + struct fpga_manager *mgr; + char type[16]; + + u32 (*reg_readl)(struct fpga_mgr_transport *transp, u32 reg_offset); + void (*reg_writel)(struct fpga_mgr_transport *transp, u32 reg_offset, + u32 value); + u32 (*reg_raw_readl)(struct fpga_mgr_transport *transp, u32 reg_offset); + void (*reg_raw_writel)(struct fpga_mgr_transport *transp, u32 reg_offset, + u32 value); + u32 (*data_readl)(struct fpga_mgr_transport *transp); + void (*data_writel)(struct fpga_mgr_transport *transp, u32 value); + + void __iomem *fpga_base_addr; + void __iomem *fpga_data_addr; +}; + +extern int fpga_mgr_attach_transport(struct fpga_manager *mgr); +extern void fpga_mgr_detach_transport(struct fpga_manager *mgr); + +extern int fpga_attach_mmio_transport(struct fpga_manager *mgr); +extern void fpga_detach_mmio_transport(struct fpga_manager *mgr); + +/*---------------------------------------------------------------------------*/ + +/* + * fpga_manager_ops are the low level functions implemented by a specific + * fpga manager driver. Leaving any of these out that aren't needed is fine + * as they are all tested for NULL before being called. + */ +struct fpga_manager_ops { + /* Returns a string of the FPGA's status */ + int (*status)(struct fpga_manager *mgr, char *buf); + + /* Prepare the FPGA for reading its confuration data */ + int (*read_init)(struct fpga_manager *mgr); + + /* Read count bytes configuration data from the FPGA */ + ssize_t (*read)(struct fpga_manager *mgr, char *buf, size_t count); + + /* Return FPGA to a default state after reading is done */ + int (*read_complete)(struct fpga_manager *mgr); + + /* Prepare the FPGA to receive confuration data */ + int (*write_init)(struct fpga_manager *mgr); + + /* Write count bytes of configuration data to the FPGA */ + ssize_t (*write)(struct fpga_manager *mgr, char *buf, size_t count); + + /* Return FPGA to default state after writing is done */ + int (*write_complete)(struct fpga_manager *mgr); + + /* Set FPGA into a specific state during driver remove */ + void (*fpga_remove)(struct fpga_manager *mgr); + + /* FPGA mangager isr */ + irqreturn_t (*isr)(int irq, void *dev_id); +}; + +/* flag bits */ +#define FPGA_MGR_DEV_BUSY 0 + +struct fpga_manager { + struct device_node *np; + struct device *parent; + struct device *dev; + struct cdev cdev; + int irq; + struct completion status_complete; + + int nr; + char name[48]; + unsigned long flags; + struct fpga_manager_ops *mops; + struct fpga_mgr_transport *transp; + + void *priv; +}; + +#if defined(CONFIG_FPGA) || defined(CONFIG_FPGA_MODULE) + +extern int register_fpga_manager(struct platform_device *pdev, + struct fpga_manager_ops *mops, + char *name, void *priv); + +extern void remove_fpga_manager(struct platform_device *pdev); + +/* + * Read/write FPGA manager registers + */ +static inline u32 fpga_mgr_reg_readl(struct fpga_manager *mgr, u32 offset) +{ + struct fpga_mgr_transport *transp = mgr->transp; + return transp->reg_readl(transp, offset); +} + +static inline void fpga_mgr_reg_writel(struct fpga_manager *mgr, u32 offset, + u32 value) +{ + struct fpga_mgr_transport *transp = mgr->transp; + transp->reg_writel(transp, offset, value); +} + +static inline u32 fpga_mgr_reg_raw_readl(struct fpga_manager *mgr, u32 offset) +{ + struct fpga_mgr_transport *transp = mgr->transp; + return transp->reg_raw_readl(transp, offset); +} + +static inline void fpga_mgr_reg_raw_writel(struct fpga_manager *mgr, u32 offset, + u32 value) +{ + struct fpga_mgr_transport *transp = mgr->transp; + transp->reg_raw_writel(transp, offset, value); +} + +static inline void fpga_mgr_reg_set_bitsl(struct fpga_manager *mgr, + u32 offset, u32 bits) +{ + u32 val; + val = fpga_mgr_reg_readl(mgr, offset); + val |= bits; + fpga_mgr_reg_writel(mgr, offset, val); +} + +static inline void fpga_mgr_reg_clr_bitsl(struct fpga_manager *mgr, + u32 offset, u32 bits) +{ + u32 val; + val = fpga_mgr_reg_readl(mgr, offset); + val &= ~bits; + fpga_mgr_reg_writel(mgr, offset, val); +} + +/* + * Read/write FPGA configuration data + */ +static inline u32 fpga_mgr_data_readl(struct fpga_manager *mgr) +{ + struct fpga_mgr_transport *transp = mgr->transp; + return transp->data_readl(transp); +} + +static inline void fpga_mgr_data_writel(struct fpga_manager *mgr, u32 value) +{ + struct fpga_mgr_transport *transp = mgr->transp; + transp->data_writel(transp, value); +} + +#endif /* CONFIG_FPGA */ +#endif /*_LINUX_FPGA_H */ diff --git a/include/linux/mailbox.h b/include/linux/mailbox.h index 5161f63ec1c87..232e2c4c792ef 100644 --- a/include/linux/mailbox.h +++ b/include/linux/mailbox.h @@ -1,17 +1,17 @@ /* - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. */ -int pl320_ipc_transmit(u32 *data); -int pl320_ipc_register_notifier(struct notifier_block *nb); -int pl320_ipc_unregister_notifier(struct notifier_block *nb); +#ifndef __MAILBOX_H +#define __MAILBOX_H + +enum xfer_result { + XFER_OK = 0, + XFER_ERR, +}; + +typedef unsigned request_token_t; + +#endif /* __MAILBOX_H */ diff --git a/include/linux/mailbox_client.h b/include/linux/mailbox_client.h new file mode 100644 index 0000000000000..c43f2c72656a9 --- /dev/null +++ b/include/linux/mailbox_client.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAILBOX_CLIENT_H +#define __MAILBOX_CLIENT_H + +#include + +/** + * struct ipc_client - User of a mailbox + * @chan_name: the "controller:channel" this client wants + * @cl_id: The identity to associate any tx/rx data with + * @rxcb: atomic callback to provide client the data received + * @txcb: atomic callback to tell client of data transmission + * @tx_block: if the ipc_send_message should block until data is transmitted + * @tx_tout: Max block period in ms before TX is assumed failure + * @knows_txdone: if the client could run the TX state machine. Usually if + * the client receives some ACK packet for transmission. Unused if the + * controller already has TX_Done/RTR IRQ. + * @link_data: Optional controller specific parameters during channel request + */ +struct ipc_client { + char *chan_name; + void *cl_id; + void (*rxcb)(void *cl_id, void *mssg); + void (*txcb)(void *cl_id, void *mssg, enum xfer_result r); + bool tx_block; + unsigned long tx_tout; + bool knows_txdone; + void *link_data; +}; + +/** + * The Client specifies its requirements and capabilities while asking for + * a channel/mailbox by name. It can't be called from atomic context. + * The channel is exclusively allocated and can't be used by another + * client before the owner calls ipc_free_channel. + */ +void *ipc_request_channel(struct ipc_client *cl); + +/** + * For client to submit data to the controller destined for a remote + * processor. If the client had set 'tx_block', the call will return + * either when the remote receives the data or when 'tx_tout' millisecs + * run out. + * In non-blocking mode, the requests are buffered by the API and a + * non-zero token is returned for each queued request. If the queue + * was full the returned token will be 0. Upon failure or successful + * TX, the API calls 'txcb' from atomic context, from which the client + * could submit yet another request. + * In blocking mode, 'txcb' is not called, effectively making the + * queue length 1. The returned value is 0 if TX timed out, some + * non-zero value upon success. + */ +request_token_t ipc_send_message(void *channel, void *mssg); + +/** + * The way for a client to run the TX state machine. This works + * only if the client sets 'knows_txdone' and the IPC controller + * doesn't get an IRQ for TX_Done/Remote_RTR. + */ +void ipc_client_txdone(void *channel, enum xfer_result r); + +/** + * The client relinquishes control of a mailbox by this call, + * make it available to other clients. + * The ipc_request/free_channel are light weight calls, so the + * client should avoid holding it when it doesn't need to + * transfer data. + */ +void ipc_free_channel(void *channel); + +/** + * The client make ask the API to be notified when a particular channel + * becomes available to be acquired again. + */ +int ipc_notify_chan_register(const char *name, struct notifier_block *nb); + +/** + * The client is no more interested in acquiring the channel. + */ +void ipc_notify_chan_unregister(const char *name, struct notifier_block *nb); + +#endif /* __MAILBOX_CLIENT_H */ diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h new file mode 100644 index 0000000000000..d4ef764e25412 --- /dev/null +++ b/include/linux/mailbox_controller.h @@ -0,0 +1,102 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAILBOX_CONTROLLER_H +#define __MAILBOX_CONTROLLER_H + +#include + +/** + * struct ipc_link - s/w representation of a communication link + * @link_name: Literal name assigned to the link. Physically + * identical channels may have the same name. + * @api_priv: hook for the API to map its private data on the link + * Controller driver must not touch it. + */ +struct ipc_link { + char link_name[16]; + void *api_priv; +}; + +/** + * struct ipc_link - s/w representation of a communication link + * @send_data: The API asks the IPC controller driver, in atomic + * context try to transmit a message on the bus. Returns 0 if + * data is accepted for transmission, -EBUSY while rejecting + * if the remote hasn't yet read the last data sent. Actual + * transmission of data is reported by the controller via + * ipc_link_txdone (if it has some TX ACK irq). It must not + * block. + * @startup: Called when a client requests the link. The controller + * could ask clients for additional parameters of communication + * to be provided via client's link_data. This call may block. + * After this call the Controller must forward any data received + * on the link by calling ipc_link_received_data (which won't block) + * @shutdown: Called when a client relinquishes control of a link. + * This call may block too. The controller must not forwared + * any received data anymore. + * @is_ready: If the controller sets 'txdone_poll', the API calls + * this to poll status of last TX. The controller must give priority + * to IRQ method over polling and never set both txdone_poll and + * txdone_irq. Only in polling mode 'send_data' is expected to + * return -EBUSY. Used only if txdone_poll:=true && txdone_irq:=false + */ +struct ipc_link_ops { + int (*send_data)(struct ipc_link *link, void *data); + int (*startup)(struct ipc_link *link, void *params); + void (*shutdown)(struct ipc_link *link); + bool (*is_ready)(struct ipc_link *link); +}; + +/** + * struct ipc_controller - Controller of a class of communication links + * @controller_name: Literal name of the controller. + * @ops: Operators that work on each communication link + * @links: Null terminated array of links. + * @txdone_irq: Indicates if the controller can report to API when the + * last transmitted data was read by the remote. Eg, if it has some + * TX ACK irq. + * @txdone_poll: If the controller can read but not report the TX done. + * Eg, is some register shows the TX status but no interrupt rises. + * Ignored if 'txdone_irq' is set. + * @txpoll_period: If 'txdone_poll' is in effect, the API polls for + * last TX's status after these many millisecs + */ +struct ipc_controller { + char controller_name[16]; + struct ipc_link_ops *ops; + struct ipc_link **links; + bool txdone_irq; + bool txdone_poll; + unsigned txpoll_period; +}; + +/** + * The controller driver registers its communication links to the + * global pool managed by the API. + */ +int ipc_links_register(struct ipc_controller *ipc_con); + +/** + * After startup and before shutdown any data received on the link + * is pused to the API via atomic ipc_link_received_data() API. + * The controller should ACK the RX only after this call returns. + */ +void ipc_link_received_data(struct ipc_link *link, void *data); + +/** + * The controller the has IRQ for TX ACK calls this atomic API + * to tick the TX state machine. It works only if txdone_irq + * is set by the controller. + */ +void ipc_link_txdone(struct ipc_link *link, enum xfer_result r); + +/** + * Purge the links from the global pool maintained by the API. + */ +void ipc_links_unregister(struct ipc_controller *ipc_con); + +#endif /* __MAILBOX_CONTROLLER_H */ diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index 53241842a7ab4..e0b7c52c1f6bd 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -34,6 +34,7 @@ #define SPINOR_OP_SE 0xd8 /* Sector erase (usually 64KiB) */ #define SPINOR_OP_RDID 0x9f /* Read JEDEC ID */ #define SPINOR_OP_RDCR 0x35 /* Read configuration register */ +#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */ /* 4-byte address opcodes - used on Spansion and some Macronix flashes. */ #define SPINOR_OP_READ4 0x13 /* Read data bytes (low frequency) */ @@ -66,6 +67,9 @@ #define SR_QUAD_EN_MX 0x40 /* Macronix Quad I/O */ +/* Flag Status Register bits */ +#define FSR_READY 0x80 + /* Configuration Register bits. */ #define CR_QUAD_EN_SPAN 0x2 /* Spansion Quad I/O */ @@ -154,6 +158,8 @@ struct spi_nor { u8 read_opcode; u8 read_dummy; u8 program_opcode; + u32 jedec_id; + enum read_mode flash_read; bool sst_write_second; struct spi_nor_xfer_cfg cfg; @@ -170,6 +176,7 @@ struct spi_nor { int write_enable); const struct spi_device_id *(*read_id)(struct spi_nor *nor); int (*wait_till_ready)(struct spi_nor *nor); + void (*shutdown)(struct spi_nor *nor); int (*read)(struct spi_nor *nor, loff_t from, size_t len, size_t *retlen, u_char *read_buf); diff --git a/include/linux/spi/spi-dw.h b/include/linux/spi/spi-dw.h new file mode 100644 index 0000000000000..fb904f1722592 --- /dev/null +++ b/include/linux/spi/spi-dw.h @@ -0,0 +1,38 @@ +/* linux/include/linux/spi/spi-dw.h + * + * Copyright (C) 2012 Altera Corporation + * + * Maintainer: Lee Booi Lim + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef LINUX_DW_SPI_H +#define LINUX_DW_SPI_H + +#include + + +#define DW_SPI_OF_COMPATIBLE "snps,dw-spi-mmio" +#define MAX_SPI_DEVICES 16 + +enum dw_spi_dma_resources { + DW_SPI_DMA_CH_RX = 0, + DW_SPI_DMA_CH_TX, + DW_SPI_DMA_CH_MAX, +}; + + + +struct dw_spi_pdata { + struct resource dwi_spi_dma_res[DW_SPI_DMA_CH_MAX]; +}; + +#endif /* LINUX_DW_SPI_H */ diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index e713543336f12..42079385b437f 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -101,6 +101,8 @@ struct spi_device { void *controller_data; char modalias[SPI_NAME_SIZE]; int cs_gpio; /* chip select gpio */ + u32 enable_4b_addr; /* Uses 4 bytes address mode */ + u32 addr_width; /* * likely need more hooks for more protocol options affecting how diff --git a/include/uapi/linux/elf-em.h b/include/uapi/linux/elf-em.h index 01529bd964387..326972a094eb8 100644 --- a/include/uapi/linux/elf-em.h +++ b/include/uapi/linux/elf-em.h @@ -33,6 +33,7 @@ #define EM_M32R 88 /* Renesas M32R */ #define EM_MN10300 89 /* Panasonic/MEI MN10300, AM33 */ #define EM_BLACKFIN 106 /* ADI Blackfin Processor */ +#define EM_ALTERA_NIOS2 113 /* Altera Nios II soft-core processor */ #define EM_TI_C6000 140 /* TI C6X DSPs */ #define EM_AARCH64 183 /* ARM 64 bit */ #define EM_FRV 0x5441 /* Fujitsu FR-V */