diff --git a/Documentation/devicetree/bindings/mips/img/pistachio.txt b/Documentation/devicetree/bindings/mips/img/pistachio.txt index a736d889c2b87d..4a068471c12a31 100644 --- a/Documentation/devicetree/bindings/mips/img/pistachio.txt +++ b/Documentation/devicetree/bindings/mips/img/pistachio.txt @@ -12,7 +12,7 @@ A "cpus" node is required. Required properties: - #size-cells: Must be 0. A CPU sub-node is also required for at least CPU 0. Since the topology may be probed via CPS, it is not necessary to specify secondary CPUs. Required -propertis: +properties: - device_type: Must be "cpu". - compatible: Must be "mti,interaptiv". - reg: CPU number. diff --git a/Documentation/devicetree/bindings/misc/img-pdm.txt b/Documentation/devicetree/bindings/misc/img-pdm.txt new file mode 100644 index 00000000000000..96fde23a6cdb7a --- /dev/null +++ b/Documentation/devicetree/bindings/misc/img-pdm.txt @@ -0,0 +1,54 @@ +Imagination Technologies Pulse Density Modulator (PDM) DAC. + +Required properties: +- compatible: Must be "img,pistachio-pdm" +- clocks: phandle to input PDM clock +- clock-names: Must include the following entry: + - pdm: input clock to pdm block. +- img,cr-periph: Must contain a phandle to the peripheral control + syscon node which contains PDM control registers. +- #pdm-cells: Must be 2. +- The first cell is the PDM channel number (valid values: 0, 1, 2, 3) +- The second cell is 12-bit pulse-in value + +Specifying PDM information for devices +====================================== + +1. PDM User nodes + +PDM properties should be named "pdms". The exact meaning of each pdms property +is described above. + + pdm-specifier : array of #pdm-cells specifying the given PDM + (controller specific) + +The following example could be used to describe a PDM-based backlight device: + + pdm: pdm { + #pdm-cells = <2>; + }; + + [...] + + bl: backlight { + pdms = <&pdm 2 0>; + }; + +pdm-specifier typically encodes the chip-relative PDM channel number and the +12-bit pulse-in value. + +2. PDM Controller nodes + +PDM controller nodes must specify the number of cells used for the specifier +using the '#pdm-cells' property. + +An example PDM controller might look like this: + +Example: + pdm: pdm@18148000 { + compatible = "img,pistachio-pdm"; + clocks = <&pdm_clk>; + clk-names = "pdm"; + img,cr-periph = <&cr_periph>; + #pdm-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/misc/img-scratchpad.txt b/Documentation/devicetree/bindings/misc/img-scratchpad.txt new file mode 100644 index 00000000000000..a20b88d061d6be --- /dev/null +++ b/Documentation/devicetree/bindings/misc/img-scratchpad.txt @@ -0,0 +1,21 @@ +Imagination Technologies scratchpad driver + +Provide syfs read/write access to the scratchpad registers. +These registers are soft reset protected registers. + +Required properties: +- compatible: Should be "img,pistachio-scratchpad", "syscon" +- reg: Should contain scratchpad registers location and length +- clocks: Must contain an entry for each entry in clock-names. +- clock-names: Should contain "wdt" and "sys"; +- sysfs-mask: 8 bit value should specify the registers to be exposed via sysfs + +Examples: + +scratchpad@18102120 { + compatible = "img,pistachio-scratchpad", "syscon"; + reg = <0x18102120 0x20>; + clocks = <&clk_periph PERIPH_CLK_WD>, <&cr_periph SYS_CLK_WD>; + clock-names = "wdt", "sys"; + sysfs-mask = /bits/ 8 <0xFE>; /*expose all except the first reg */ +}; diff --git a/Documentation/devicetree/bindings/misc/uboot-bootcount.txt b/Documentation/devicetree/bindings/misc/uboot-bootcount.txt new file mode 100644 index 00000000000000..0037f24f267606 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/uboot-bootcount.txt @@ -0,0 +1,40 @@ +U-Boot bootcount driver + +This driver implements the Linux kernel half of the boot count feature - +the boot counter can only be reset after it is clear that the +application has been started and is running correctly, which usually +can only be determined by the application code itself. Thus the reset +of the boot counter must be done by application code, which thus needs +an appropriate driver. + +Required feature by the Carrier Grade Linux Requirements Definition; +see for example document "Carrier Grade Linux Requirements Definition +Overview V3.0" at + +http://www.linuxfoundation.org/collaborate/workgroups/cgl/requirements#SMM.6.0_Boot_Cycle_Detection + + Description: OSDL CGL specifies that carrier grade Linux + shall provide support for detecting a repeating reboot cycle + due to recurring failures. This detection should happen in + user space before system services are started. + +This driver provides read/write access to the U-Boot bootcounter +through sysfs file. + +Required properties: + + - compatible : should be "uboot,bootcount" + - reg: the address of the bootcounter + - syscon-reg: Specify if reg property is not specified, should specify the syscon reg to be used + +Example: + +bootcount@1c23000 { + compatible = "uboot,bootcount"; + reg = <0x23060 0x1>; +}; + +bootcount { + compatible = "uboot,bootcount"; + syscon-reg = <&scratchpad 0x0>; +}; diff --git a/Documentation/devicetree/bindings/net/ieee802154/cc2520.txt b/Documentation/devicetree/bindings/net/ieee802154/cc2520.txt index fb6d49f184edc5..338a01571f63e0 100644 --- a/Documentation/devicetree/bindings/net/ieee802154/cc2520.txt +++ b/Documentation/devicetree/bindings/net/ieee802154/cc2520.txt @@ -15,7 +15,12 @@ Required properties: - reset-gpio: GPIO spec for the RESET pin Optional properties: - amplified: include if the CC2520 is connected to a CC2591 amplifier - + - #clock-cells from common clock binding; shall be set to 0 + required if extclock-freq is specified + - extclock-freq: frequency setting of external clock generator, should be + between 1000000-16000000 (check datasheet for supported values) + extclock is disabled if extclock-freq = <0>, if not specified + defaults to 1MHz (reset value) Example: cc2520@0 { compatible = "ti,cc2520"; @@ -30,4 +35,6 @@ Example: cca-gpio = <&gpio1 16 0>; vreg-gpio = <&gpio0 31 0>; reset-gpio = <&gpio1 12 0>; + #clock-cells = <0>; + extclock-freq = <16000000>; }; diff --git a/Documentation/devicetree/bindings/phy/pistachio-usb-phy.txt b/Documentation/devicetree/bindings/phy/pistachio-usb-phy.txt index afbc7e24a3de3e..486423d55c1d82 100644 --- a/Documentation/devicetree/bindings/phy/pistachio-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/pistachio-usb-phy.txt @@ -15,6 +15,9 @@ Required properties: Optional properties: -------------------- - phy-supply: USB VBUS supply. Must supply 5.0V. + - enable-vbus-drive: include if USB block should drive the VBUS on mfio86, + this will override the primary function RPU_L_PLL lock signal + available on this pin. Example: -------- @@ -26,4 +29,5 @@ usb_phy: usb-phy { img,refclk = ; img,cr-top = <&cr_top>; #phy-cells = <0>; + enable-vbus-drive; }; diff --git a/Documentation/devicetree/bindings/pinctrl/img,pistachio-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/img,pistachio-pinctrl.txt index 08a4a32c8eb0db..0326154c792548 100644 --- a/Documentation/devicetree/bindings/pinctrl/img,pistachio-pinctrl.txt +++ b/Documentation/devicetree/bindings/pinctrl/img,pistachio-pinctrl.txt @@ -134,12 +134,12 @@ mfio80 ddr_debug, mips_trace_data, mips_debug mfio81 dreq0, mips_trace_data, eth_debug mfio82 dreq1, mips_trace_data, eth_debug mfio83 mips_pll_lock, mips_trace_data, usb_debug -mfio84 sys_pll_lock, mips_trace_data, usb_debug -mfio85 wifi_pll_lock, mips_trace_data, sdhost_debug -mfio86 bt_pll_lock, mips_trace_data, sdhost_debug -mfio87 rpu_v_pll_lock, dreq2, socif_debug -mfio88 rpu_l_pll_lock, dreq3, socif_debug -mfio89 audio_pll_lock, dreq4, dreq5 +mfio84 audio_pll_lock, mips_trace_data, usb_debug +mfio85 rpu_v_pll_lock, mips_trace_data, sdhost_debug +mfio86 rpu_l_pll_lock, mips_trace_data, sdhost_debug +mfio87 sys_pll_lock, dreq2, socif_debug +mfio88 wifi_pll_lock, dreq3, socif_debug +mfio89 bt_pll_lock, dreq4, dreq5 tck trstn tdi diff --git a/Documentation/devicetree/bindings/reset/img,pistachio-reset.txt b/Documentation/devicetree/bindings/reset/img,pistachio-reset.txt new file mode 100644 index 00000000000000..2872fbe4fc2855 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/img,pistachio-reset.txt @@ -0,0 +1,54 @@ +Pistachio Reset Controller +============================================================================= + +This binding describes a reset controller device that is used to enable and +disable individual IP blocks within the Pistachio SoC using "soft reset" +control bits found in the Pistachio SoC top level registers. + +The actual action taken when soft reset is asserted is hardware dependent. +However, when asserted it may not be possible to access the hardware's +registers, and following an assert/deassert sequence the hardware's previous +state may no longer be valid. + +Please refer to Documentation/devicetree/bindings/reset/reset.txt +for common reset controller binding usage. + +Required properties: + +- compatible: Contains "img,pistachio-reset" + +- #reset-cells: Contains 1 + +Example: + cr_periph: clk@18148000 { + compatible = "img,pistachio-cr-periph", "syscon", "simple-bus"; + reg = <0x18148000 0x1000>; + clocks = <&clk_periph PERIPH_CLK_SYS>; + clock-names = "sys"; + #clock-cells = <1>; + + pistachio_reset: reset-controller { + compatible = "img,pistachio-reset"; + #reset-cells = <1>; + }; + }; + +Specifying reset control of devices +======================================= + +Device nodes should specify the reset channel required in their "resets" +property, containing a phandle to the pistachio reset device node and an +index specifying which reset to use, as described in +Documentation/devicetree/bindings/reset/reset.txt. + +Example: + + spdif_out: spdif-out@18100d00 { + ... + resets = <&pistachio_reset PISTACHIO_RESET_SPDIF_OUT>; + reset-names = "rst"; + ... + }; + +Macro definitions for the supported resets can be found in: +include/dt-bindings/reset-controller/pistachio-resets.h diff --git a/Documentation/devicetree/bindings/soc/img/img-efuse.txt b/Documentation/devicetree/bindings/soc/img/img-efuse.txt new file mode 100644 index 00000000000000..1b16aabd2c5a73 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/img/img-efuse.txt @@ -0,0 +1,26 @@ +* Imagination Technologies Generic eFuse controller + +Required properties: +- compatible: Must be "img,pistachio-efuse". +- reg: Must contain the base address and length of the eFuse registers. + +Optional properties: +- clocks: Must contain an entry for each entry in clock-names. + See ../clock/clock-bindings.txt for details. +- clock-names: Must include the following entries: + - osc: External oscillator clock + - sys: eFuse register interface clock + +Example: +efuse: efuse@18149200 { + compatible = "img,pistachio-efuse"; + reg = <0x18149200 0x200>; +}; + +Example with optional clock properties: +efuse: efuse@18149200 { + compatible = "img,pistachio-efuse"; + reg = <0x18149200 0x200>; + clocks = <&osc>, <&system_clk>; + clock-names = "osc", "sys"; +}; diff --git a/Documentation/devicetree/bindings/sound/img,i2s-in.txt b/Documentation/devicetree/bindings/sound/img,i2s-in.txt new file mode 100644 index 00000000000000..76277d4a38076e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,i2s-in.txt @@ -0,0 +1,80 @@ +Imagination Technologies I2S Input Controller + +Required Properties: + + - compatible : Compatible list, must contain "img,i2s-in" + + - #sound-dai-cells : Must be equal to 1 + + - #sound-platform-cells : Must be equal to 1 + + - reg : Offset and length of the register set for the device + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Must include the following entry: + "sys" The system clock + + - dmas: Contains an entry for each entry in dma-names. + + - dma-names: Must include one or both of the following: + "rx" Shared DMA channel + "rxN" for N=0..img,i2s-channels - 1 Per-channel DMA + + - img,i2s-channels : Number of I2S channels instantiated in the I2S in block + + - img,cr-periph : Must contain a phandle to the peripheral control syscon + node which contains the alternate i2s control registers + +Optional Properties: + + - img,clock-master : Contains bitmask for clock master configuration. One + bit per i2s channel for channels 0-3. 4/5 cannot be + modified and always use MFIO47/MFIO48. If omitted, + all channels will use MFIO47/MFIO48 + + 0 -> MFIO47 = bit clock, MFIO48 = left/right clock + 1 -> MFIO11 = bit clock, MFIO12 = left/right clock + + - img,shared-dma : Contains number of channels to combine for use with the + shared DMA channel. If this is not equal to the value + specified in img,i2s-channels, per-channel dma references + must exist for the remaining channels. If this is equal + to zero, the shared dma channel reference can be ommitted. + The combined channels must share the same bit and + left/right clock. If omitted, all channels will combined + for use with the shared DMA channel + + - interrupts : Contains the I2S in interrupts. Depending on + the configuration, there may be no interrupts, one interrupt, + or an interrupt per I2S channel + + - resets: Contains a phandle to the I2S in reset signal + + - reset-names: Contains the reset signal name "rst" + +Example: + +i2s_in: i2s-in@18100800 { + compatible = "img,i2s-in"; + reg = <0x18100800 0x200>; + interrupts = ; + clocks = <&cr_periph SYS_CLK_I2S_IN>; + clock-names = "sys"; + img,i2s-channels = <6>; + img,cr-periph = <&cr_periph>; + img,clock-master = <0xf>; + img,shared-dma = <3>; + dmas = <&mdc 30 0xffffffff 0>, + <&mdc 24 0xffffffff 0>, + <&mdc 25 0xffffffff 0>, + <&mdc 26 0xffffffff 0>, + <&mdc 27 0xffffffff 0>, + <&mdc 28 0xffffffff 0>, + <&mdc 29 0xffffffff 0>; + dma-names = "rx", "rx0", "rx1", "rx2", + "rx3", "rx4", "rx5"; + + #sound-dai-cells = <1>; + #sound-platform-cells = <1>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,i2s-out.txt b/Documentation/devicetree/bindings/sound/img,i2s-out.txt new file mode 100644 index 00000000000000..dc110ce5f81731 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,i2s-out.txt @@ -0,0 +1,49 @@ +Imagination Technologies I2S Output Controller + +Required Properties: + + - compatible : Compatible list, must contain "img,i2s-out" + + - #sound-dai-cells : Must be equal to 0 + + - reg : Offset and length of the register set for the device + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Must include the following entries: + "sys" The system clock + "ref" The reference clock + + - dmas: Contains an entry for each entry in dma-names. + + - dma-names: Must include the following entry: + "tx" Single DMA channel used by all active I2S channels + + - img,i2s-channels : Number of I2S channels instantiated in the I2S out block + + - resets: Contains a phandle to the I2S out reset signal + + - reset-names: Contains the reset signal name "rst" + +Optional Properties: + + - interrupts : Contains the I2S out interrupts. Depending on + the configuration, there may be no interrupts, one interrupt, + or an interrupt per I2S channel + +Example: + +i2s_out: i2s-out@18100A00 { + compatible = "img,i2s-out"; + reg = <0x18100A00 0x200>; + interrupts = ; + dmas = <&mdc 23 0xffffffff 0>; + dma-names = "tx"; + clocks = <&cr_periph SYS_CLK_I2S_OUT>, + <&clk_core CLK_I2S>; + clock-names = "sys", "ref"; + img,i2s-channels = <6>; + resets = <&pistachio_reset PISTACHIO_RESET_I2S_OUT>; + reset-names = "rst"; + #sound-dai-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,parallel-out.txt b/Documentation/devicetree/bindings/sound/img,parallel-out.txt new file mode 100644 index 00000000000000..a3015d2a06e0ee --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,parallel-out.txt @@ -0,0 +1,44 @@ +Imagination Technologies Parallel Output Controller + +Required Properties: + + - compatible : Compatible list, must contain "img,parallel-out". + + - #sound-dai-cells : Must be equal to 0 + + - reg : Offset and length of the register set for the device. + + - dmas: Contains an entry for each entry in dma-names. + + - dma-names: Must include the following entry: + "tx" + + - clocks : Contains an entry for each entry in clock-names. + + - clock-names : Includes the following entries: + "sys" The system clock + "ref" The reference clock + + - resets: Contains a phandle to the parallel out reset signal + + - reset-names: Contains the reset signal name "rst" + +Optional Properties: + + - interrupts : Contains the parallel out interrupt, if present + +Example: + +parallel_out: parallel-out@18100C00 { + compatible = "img,parallel-out"; + reg = <0x18100C00 0x100>; + interrupts = ; + dmas = <&mdc 16 0xffffffff 0>; + dma-names = "tx"; + clocks = <&cr_periph SYS_CLK_PAUD_OUT>, + <&clk_core CLK_AUDIO_DAC>; + clock-names = "sys", "ref"; + resets = <&pistachio_reset PISTACHIO_RESET_PRL_OUT>; + reset-names = "rst"; + #sound-dai-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt b/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt new file mode 100644 index 00000000000000..4cdbf2e579a569 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt @@ -0,0 +1,261 @@ +Imagination Technologies Pistachio Audio Card Driver + +Required properties: + + - compatible : Compatible list, must contain "img,pistachio-audio" + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Includes the following entries: + "audio_pll" The audio PLL + "i2s_mclk" The i2s reference clock + Also connected to i2s_out_0_mclk output + "dac_clk" Dac reference clock. Connected to i2s_dac_clk output + + - img,cr-periph : phandle of the peripheral control syscon + node which contains the i2s loopback control registers + + - img,event-timer : phandle of event timer + +Optional properties: + + - img,widgets : Please refer to widgets.txt + + - img,routing : A list of the connections between audio components. + Each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source + + - img,mute-gpio : phandle of the mute gpio + + - img,hp-det-gpio : phandle of headphone detect gpio + + - img,i2s-clk-loopback : LRCLK+BCLK signals for i2s out and i2s in + controllers are connected internally + +Optional subnodes: + + - spdif-out : Contains spdif out information + + - spdif-in : Contains spdif in information + + - parallel-out : Contains parallel out information + + - i2s-out : Contains i2s out information + + - i2s-in : Contains i2s in information + + - i2s-in-alt : Contains i2s in (alternate interface) information + +Required spdif-out subnode properties: + + - cpu-dai : phandle of spdif out cpu dai + +Required spdif-in subnode properties: + + - cpu-dai : phandle of spdif in cpu dai + +Required parallel-out subnode properties: + + - cpu-dai : phandle of parallel out cpu dai + + - sound-dai : phandle of internal dac + +Optional parallel-out subnode properties: + + - tpa6130a2 : phandle of the tpa6130a2 amplifier + +Required i2s-out subnode properties: + + - cpu-dai : phandle of i2s out cpu dai + + - format : I2S out format. "i2s" and "left_j" are supported by + the Pistachio iteration of the i2s out controller + +Optional i2s-out subnode properties: + + - bitclock-inversion : i2s out BCLK inversion + + - frame-inversion : i2s out LRCLK inversion + + - continuous-clock : i2s out BCLK and LRCLK always active + +Optional i2s-out subnodes: + + - : Contains codec information. will be used as + the prefix for the codec. This name must be unique for + each individual codec (unique codec device node), and + cannot be equal to "internal-dac" or "tpa6130a2". The name + should be short to avoid control name truncation + +Required i2s-in, i2s-in-alt subnode properties: + + - cpu-dais : array of phandles of i2s in cpu dais + + - format : i2s in format. "i2s" and "left_j" are supported by + the Pistachio iteration of the i2s in controller + +Optional i2s-in, i2s-in-alt subnode properties: + + - bitclock-inversion : i2s out BCLK inversion + + - frame-inversion : i2s out LRCLK inversion + + - continuous-clock : i2s out BCLK and LRCLK always active + +Optional i2s-in, i2s-in-alt subnodes: + + - : Contains codec information. will be used as + the prefix for the codec. This name must be unique for + each individual codec (unique codec device node), and + cannot be equal to "internal-dac" or "tpa6130a2". The name + should be short to avoid control name truncation + +Required subnode properties: + + - mclk : Contains the mclk (master clock) used by the DAC/ADC. + Valid identifiers (dt-bindings/sound/pistachio-audio.h): + + PISTACHIO_MCLK_NONE No mclk is required, or mclk is + provided externally with no software + intervention required to compenstate + for differing sample rates + + PISTACHIO_MCLK_I2S mclk is provided by the i2s_out_0_mclk + output from pistachio SoC. This clock + is shared with the internal i2s out + controller + + PISTACHIO_MCLK_DAC_CLK mclk is provided by the i2s_dac_clk + output from pistachio SoC + +Required subnode properties if mclk is not PISTACHIO_MCLK_NONE: + + - mclk-fs : Contains the set of fs ratios the DAC/ADC accepts (Nfs for + sample rate r specifies the master clock input to the DAC is + N times r). It is assumed this set of ratios is applicable to + any given sample rate where this does not lead to a violation + of the minimum/maximum frequencies specified by mclk-min-freq + and mclk-max-freq respectively + + - mclk-min-freq : Contains the minimum frequency the DAC/ADC accepts for + its master clock input + + - mclk-max-freq : Contains the maximum frequency the DAC/ADC accepts for + its master clock input + +Optional subnode properties: + + - sound-dai : phandle of the codec. If the codec does not accept/require + software configuration, this can be omitted + + - mclk-index : Index of the mclk, used for snd_soc_dai_set_sysclk call. + 0 is used if this property is omitted + + - frame-master : Indicates this codec is the LRCLK master + + - bitclock-master : Indicates this codec is the BCLK master + + frame-master and bitclock-master cannot exist in more than one of the codec + subnodes + + frame-master and bitclock-master cannot be used within i2s out codec subnodes + as the Pistachio iteration of the i2s out controller accepts master mode + only + + frame-master and bitclock-master can be omitted if img,i2s-clk-loopback is + used, or if LRCLK/BCLK generation does not require software intervention (eg + a codec operating in hardware-mode) + +Example 1 (Pistachio Bring-Up Board With Codec Daughterboard Inserted): + +All audio components present on board. 2x pcm3168a codecs provide 3 i2s in +and out channels each. dac_clk provides the single master clock to both +codecs. The Pistachio i2s out controller is the LRCLK+BCLK master for the DAC +path. The second pcm3168a codec is the LRCLK+BCLK master for the ADC path + +pistachio_audio_card { + compatible = "img,pistachio-audio"; + + clocks = <&clk_core CLK_AUDIO_PLL>, + <&clk_core CLK_I2S_DIV>, + <&clk_core CLK_AUDIO>; + clock-names = "audio_pll", "i2s_mclk", "dac_clk"; + + img,cr-periph = <&cr_periph>; + img,event-timer = <&event_timer>; + + img,mute-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>; + img,hp-det-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>; + + img,widgets = "Headphone", "Headphones", + "Speaker", "Speakers", + "Line", "RCA Out"; + + img,routing = "Headphones", "tpa OUTL", + "Headphones", "tpa OUTR", + "Speakers", "tpa OUTL", + "Speakers", "tpa OUTR", + "RCA Out", "internal-dac AOUTL", + "RCA Out", "internal-dac AOUTR", + "tpa INL", "internal-dac AOUTL", + "tpa INR", "internal-dac AOUTR"; + + spdif-out { + cpu-dai = <&spdif_out>; + }; + + spdif-in { + cpu-dai = <&spdif_in>; + }; + + parallel-out { + cpu-dai = <¶llel_out>; + sound-dai = <&internal_dac>; + tpa6130a2 = <&tpa6130a2>; + }; + + i2s-out { + cpu-dai = <&i2s_out>; + format = "i2s"; + + pcm3168a-1 { + mclk = ; + mclk-fs = <128 192 256 384 512 768>; + mclk-min-freq = <2048000>; + mclk-max-freq = <36864000>; + sound-dai = <&pcm3168a_1 0>; + }; + + pcm3168a-2 { + mclk = ; + mclk-fs = <128 192 256 384 512 768>; + mclk-min-freq = <2048000>; + mclk-max-freq = <36864000>; + sound-dai = <&pcm3168a_2 0>; + }; + }; + + i2s-in { + cpu-dai = <&i2s_in>; + format = "i2s"; + + pcm3168a-1 { + mclk = ; + mclk-fs = <256 384 512 768>; + mclk-min-freq = <2048000>; + mclk-max-freq = <36864000>; + sound-dai = <&pcm3168a_1 1>; + }; + + pcm3168a-2 { + mclk = ; + mclk-fs = <256 384 512 768>; + mclk-min-freq = <2048000>; + mclk-max-freq = <36864000>; + sound-dai = <&pcm3168a_2 1>; + frame-master; + bitclock-master; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/sound/img,pistachio-event-timer.txt b/Documentation/devicetree/bindings/sound/img,pistachio-event-timer.txt new file mode 100644 index 00000000000000..2e5e4932532c29 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,pistachio-event-timer.txt @@ -0,0 +1,63 @@ +Imagination Technologies Pistachio Event Timer + +Required properties: + + - compatible : Compatible list, must contain "img,pistachio-event-timer" + + - reg : Offset and length of the register set for the device + + - interrupts : Must contain an entry for each of the three interrupts below. + Order must also match the below: + + Internal interrupt 1 + Internal interrupt 2 + Internal interrupt 3 + Event trigger 0 + Event trigger 1 + + - #clock-cells : Must be 0 + + - clocks : Must contain an entry for each entry in clock-names + See ../clock/clock-bindings.txt for details + + - clock-names : Must include the following entries: + "sys" The system clock + "ref0" Reference clock 0 + "ref1" Reference clock 1 + "pll" Audio PLL + + - img,clk-select : Reference select + + - img,cr-periph : phandle of the peripheral control syscon node which + contains the event timer external source bank select + register + + - img,ext-src-bank : GPIO bank selection for external source. For this source + to function correctly, no other input gpios must be used + within the selected GPIO bank + +Optional properties: + + - img,clk-rate : Initial internal clock rate + +Example: + +event_timer: event_timer@18102300 { + compatible = "img,pistachio-event-timer"; + reg = <0x18102300 0x400>; + assigned-clocks = <&clk_core CLK_EVENT_TIMER_MUX>; + assigned-clock-parents = <&clk_core CLK_AUDIO_PLL_MUX>; + interrupts = , + , + , + , + ; + #clock-cells = <0>; + clocks = <&clk_core SYS_CLK_EVENT_TIMER>, + <&clk_core CLK_AUDIO>, + <&clk_core CLK_EVENT_TIMER>, + <&clk_core CLK_AUDIO_PLL>; + clock-names = "sys","ref0","ref1", "pll"; + img,clk-select = <1>; + img,clk-rate = <12288000>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,pistachio-internal-dac.txt b/Documentation/devicetree/bindings/sound/img,pistachio-internal-dac.txt new file mode 100644 index 00000000000000..78e0c4c46d96d4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,pistachio-internal-dac.txt @@ -0,0 +1,20 @@ +Pistachio internal DAC DT bindings + +Required properties: + + - compatible: "img,pistachio-internal-dac" + + - img,cr-top : Must contain a phandle to the top level control syscon + node which contains the internal dac control registers + + - img,voltage-select : Contains the voltage provided to the internal DAC: + 0 -> 1.8V + 1 -> 3.3V + +Examples: + +internal_dac: internal-dac { + compatible = "img,pistachio-internal-dac"; + img,cr-top = <&cr_top>; + img,voltage-select = <0>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,spdif-in.txt b/Documentation/devicetree/bindings/sound/img,spdif-in.txt new file mode 100644 index 00000000000000..aab9a81f7e1373 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,spdif-in.txt @@ -0,0 +1,41 @@ +Imagination Technologies SPDIF Input Controller + +Required Properties: + + - compatible : Compatible list, must contain "img,spdif-in" + + - #sound-dai-cells : Must be equal to 0 + + - reg : Offset and length of the register set for the device + + - dmas: Contains an entry for each entry in dma-names. + + - dma-names: Must include the following entry: + "rx" + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Includes the following entries: + "sys" The system clock + +Optional Properties: + + - resets: Should contain a phandle to the spdif in reset signal, if any + + - reset-names: Should contain the reset signal name "rst", if a + reset phandle is given + + - interrupts : Contains the spdif in interrupt, if present + +Example: + +spdif_in: spdif-in@18100E00 { + compatible = "img,spdif-in"; + reg = <0x18100E00 0x100>; + interrupts = ; + dmas = <&mdc 15 0xffffffff 0>; + dma-names = "rx"; + clocks = <&cr_periph SYS_CLK_SPDIF_IN>; + clock-names = "sys"; + #sound-dai-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/sound/img,spdif-out.txt b/Documentation/devicetree/bindings/sound/img,spdif-out.txt new file mode 100644 index 00000000000000..470a5191e1017a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/img,spdif-out.txt @@ -0,0 +1,44 @@ +Imagination Technologies SPDIF Output Controller + +Required Properties: + + - compatible : Compatible list, must contain "img,spdif-out" + + - #sound-dai-cells : Must be equal to 0 + + - reg : Offset and length of the register set for the device + + - dmas: Contains an entry for each entry in dma-names. + + - dma-names: Must include the following entry: + "tx" + + - clocks : Contains an entry for each entry in clock-names. + + - clock-names : Includes the following entries: + "sys" The system clock + "ref" The reference clock + + - resets: Contains a phandle to the spdif out reset signal + + - reset-names: Contains the reset signal name "rst" + +Optional Properties: + + - interrupts : Contains the parallel out interrupt, if present + +Example: + +spdif_out: spdif-out@18100D00 { + compatible = "img,spdif-out"; + reg = <0x18100D00 0x100>; + interrupts = ; + dmas = <&mdc 14 0xffffffff 0>; + dma-names = "tx"; + clocks = <&cr_periph SYS_CLK_SPDIF_OUT>, + <&clk_core CLK_SPDIF>; + clock-names = "sys", "ref"; + resets = <&pistachio_reset PISTACHIO_RESET_SPDIF_OUT>; + reset-names = "rst"; + #sound-dai-cells = <0>; +}; diff --git a/Documentation/devicetree/bindings/sound/ti,pcm3060.txt b/Documentation/devicetree/bindings/sound/ti,pcm3060.txt new file mode 100644 index 00000000000000..d0a0b1317a4d7d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ti,pcm3060.txt @@ -0,0 +1,38 @@ +Texas Instruments pcm3060 DT bindings + +This driver supports both SPI and I2C bus access for this codec + +Required properties: + + - compatible: "ti,pcm3060" + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Includes the following entries: + "sckid" The DAC system clock + "sckia" The ADC system clock + + - VDD-supply : Digital power supply regulator (+3.3V) + + - VCC-supply : Analogue power supply regulator (+5V) + +For required properties on SPI/I2C, consult SPI/I2C device tree documentation + +Examples: + +i2c0: i2c0@0 { + + ... + + pcm3060: audio-codec@46 { + compatible = "ti,pcm3060"; + reg = <0x46>; + clocks = <&clk_core CLK_I2S>, + <&clk_core CLK_I2S>; + clock-names = "sckid", "sckia"; + VCC-supply = <&supply5v0>; + VDD-supply = <&supply3v3>; + pinctrl-names = "default"; + pinctrl-0 = <&i2s_mclk_pin>; + }; +}; diff --git a/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt b/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt new file mode 100644 index 00000000000000..5d9cb84c661d84 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ti,pcm3168a.txt @@ -0,0 +1,48 @@ +Texas Instruments pcm3168a DT bindings + +This driver supports both SPI and I2C bus access for this codec + +Required properties: + + - compatible: "ti,pcm3168a" + + - clocks : Contains an entry for each entry in clock-names + + - clock-names : Includes the following entries: + "scki" The system clock + + - VDD1-supply : Digital power supply regulator 1 (+3.3V) + + - VDD2-supply : Digital power supply regulator 2 (+3.3V) + + - VCCAD1-supply : ADC power supply regulator 1 (+5V) + + - VCCAD2-supply : ADC power supply regulator 2 (+5V) + + - VCCDA1-supply : DAC power supply regulator 1 (+5V) + + - VCCDA2-supply : DAC power supply regulator 2 (+5V) + +For required properties on SPI/I2C, consult SPI/I2C device tree documentation + +Examples: + +i2c0: i2c0@0 { + + ... + + pcm3168a: audio-codec@44 { + compatible = "ti,pcm3168a"; + reg = <0x44>; + clocks = <&clk_core CLK_AUDIO>; + clock-names = "scki"; + VDD1-supply = <&supply3v3>; + VDD2-supply = <&supply3v3>; + VCCAD1-supply = <&supply5v0>; + VCCAD2-supply = <&supply5v0>; + VCCDA1-supply = <&supply5v0>; + VCCDA2-supply = <&supply5v0>; + pinctrl-names = "default"; + pinctrl-0 = <&dac_clk_pin>; + }; +}; diff --git a/Documentation/devicetree/bindings/uccp/uccp-img-pistachio.txt b/Documentation/devicetree/bindings/uccp/uccp-img-pistachio.txt new file mode 100644 index 00000000000000..9c9e72196406d9 --- /dev/null +++ b/Documentation/devicetree/bindings/uccp/uccp-img-pistachio.txt @@ -0,0 +1,73 @@ +IMG UCCP420 driver properties + +Required Properties: +- compatible: "img,pistachio-uccp" +- reg: RPU registers +- reg-names : Should containt the names of the registers used by the driver +- interrupts: Interrupt number used by the RPU +- clocks : Should contain a clock specifier for each entry in clock-names +- clock-names : Should contain the clock names used by the driver +- io-channels : Names of the channels used by the driver + +Example: + +wifi: uccp@18480000 { + status = "disabled"; + compatible = "img,pistachio-uccp"; + reg = <0x18480000 0x38000>, <0x184BC000 0x2800>, <0x1a000000 0x00066cc0>; + reg-names = "uccp_core_base", "uccp_slave_base" , "uccp_pkd_gram_base"; + interrupts = ; + interrupt-names = "uccpirq"; + clocks = <&clk_core CLK_RPU_CORE>, + <&clk_core CLK_RPU_CORE_DIV>, + <&clk_core CLK_RPU_V>, + <&clk_core CLK_RPU_L>, + <&clk_core CLK_RPU_SLEEP>, + <&clk_core CLK_WIFI_PLL>, + <&clk_core CLK_WIFI_ADC>, + <&clk_core CLK_WIFI_DAC>, + <&clk_core CLK_EVENT_TIMER>, + <&cr_periph SYS_CLK_EVENT_TIMER>, + <&clk_core CLK_AUX_ADC>, + <&clk_core CLK_AUX_ADC_INTERNAL>; + clock-names = "rpu_core", "rpu_core_div", "rpu_v", "rpu_l", "rpu_sleep", + "wifi_pll", "wifi_adc", "wifi_dac", "event_timer", + "sys_event_timer", "aux_adc", "aux_adc_internal"; + assigned-clocks = <&clk_core CLK_RPU_L_DIV>, + <&clk_core CLK_RPU_L_MUX>, + <&clk_core CLK_RPU_L_PLL_MUX>, + <&clk_core CLK_RPU_V_DIV>, + <&clk_core CLK_RPU_V_PLL_MUX>, + <&clk_core CLK_RPU_CORE_DIV>, + <&clk_core CLK_WIFI_PLL_MUX>, + <&clk_core CLK_WIFI_DIV4_MUX>, + <&clk_core CLK_WIFI_DIV8_MUX>, + <&clk_core CLK_RPU_SLEEP_DIV>, + <&clk_core CLK_WIFI_PLL>, + <&clk_core CLK_RPU_CORE>; + assigned-clock-parents = <0>, + <&clk_core CLK_RPU_L_PLL_MUX>, + <&clk_core CLK_RPU_L_PLL>, + <0>, + <&clk_core CLK_RPU_V_PLL>, + <0>, + <&clk_core CLK_WIFI_PLL>, + <&clk_core CLK_WIFI_DIV4>, + <&clk_core CLK_WIFI_DIV8>, + <0>, + <0>, + <0>; + assigned-clock-rates = <559000000>, + <0>, + <0>, + <598000000>, + <0>, + <320000000>, + <0>, + <0>, + <0>, + <52000>, + <320000000>, + <320000000>; + io-channels = <&adc 4>, <&adc 5>; + }; diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index db459612de4485..1c58731e2230b3 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -384,7 +384,7 @@ config MACH_PISTACHIO select CLKSRC_MIPS_GIC select COMMON_CLK select CSRC_R4K - select DMA_MAYBE_COHERENT + select DMA_NONCOHERENT select IRQ_MIPS_CPU select LIBFDT select MFD_SYSCON @@ -392,6 +392,7 @@ config MACH_PISTACHIO select MIPS_GIC select PINCTRL select REGULATOR + select CPU_SUPPORTS_CPUFREQ select SYS_HAS_CPU_MIPS32_R2 select SYS_SUPPORTS_32BIT_KERNEL select SYS_SUPPORTS_LITTLE_ENDIAN diff --git a/arch/mips/configs/pistachio_defconfig b/arch/mips/configs/pistachio_defconfig index 8b7429127a1d18..136ef7d0d867c2 100644 --- a/arch/mips/configs/pistachio_defconfig +++ b/arch/mips/configs/pistachio_defconfig @@ -190,6 +190,7 @@ CONFIG_SERIAL_8250_DW=y CONFIG_SERIAL_OF_PLATFORM=y CONFIG_HW_RANDOM=y CONFIG_TCG_TPM=y +CONFIG_TCG_TIS_I2C_INFINEON=y CONFIG_I2C=y CONFIG_I2C_CHARDEV=m CONFIG_I2C_IMG=y @@ -260,6 +261,7 @@ CONFIG_MMC_DW=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PISTACHIO=y CONFIG_DMADEVICES=y CONFIG_IMG_MDC_DMA=y CONFIG_STAGING=y diff --git a/arch/mips/include/asm/cacheflush.h b/arch/mips/include/asm/cacheflush.h index 723229f4cf278f..34ed22ec6c33e7 100644 --- a/arch/mips/include/asm/cacheflush.h +++ b/arch/mips/include/asm/cacheflush.h @@ -51,7 +51,6 @@ 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 page, unsigned long pfn); extern void __flush_dcache_page(struct page *page); -extern void __flush_icache_page(struct vm_area_struct *vma, struct page *page); #define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1 static inline void flush_dcache_page(struct page *page) @@ -77,11 +76,6 @@ static inline void flush_anon_page(struct vm_area_struct *vma, static inline void flush_icache_page(struct vm_area_struct *vma, struct page *page) { - if (!cpu_has_ic_fills_f_dc && (vma->vm_flags & VM_EXEC) && - Page_dcache_dirty(page)) { - __flush_icache_page(vma, page); - ClearPageDcacheDirty(page); - } } extern void (*flush_icache_range)(unsigned long start, unsigned long end); @@ -132,6 +126,7 @@ static inline void kunmap_noncoherent(void) static inline void flush_kernel_dcache_page(struct page *page) { BUG_ON(cpu_has_dc_aliases && PageHighMem(page)); + flush_dcache_page(page); } /* diff --git a/arch/mips/include/asm/pgtable.h b/arch/mips/include/asm/pgtable.h index 18826aa15a7cec..4e68c644acc5a2 100644 --- a/arch/mips/include/asm/pgtable.h +++ b/arch/mips/include/asm/pgtable.h @@ -127,10 +127,14 @@ do { \ } \ } while(0) +static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, pte_t pteval); + #if defined(CONFIG_PHYS_ADDR_T_64BIT) && defined(CONFIG_CPU_MIPS32) #define pte_none(pte) (!(((pte).pte_high) & ~_PAGE_GLOBAL)) #define pte_present(pte) ((pte).pte_low & _PAGE_PRESENT) +#define pte_no_exec(pte) ((pte).pte_low & _PAGE_NO_EXEC) static inline void set_pte(pte_t *ptep, pte_t pte) { @@ -148,7 +152,6 @@ static inline void set_pte(pte_t *ptep, pte_t pte) buddy->pte_high |= _PAGE_GLOBAL; } } -#define set_pte_at(mm, addr, ptep, pteval) set_pte(ptep, pteval) static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep) { @@ -166,6 +169,7 @@ static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *pt #define pte_none(pte) (!(pte_val(pte) & ~_PAGE_GLOBAL)) #define pte_present(pte) (pte_val(pte) & _PAGE_PRESENT) +#define pte_no_exec(pte) (pte_val(pte) & _PAGE_NO_EXEC) /* * Certain architectures need to do special things when pte's @@ -218,7 +222,6 @@ static inline void set_pte(pte_t *ptep, pte_t pteval) } #endif } -#define set_pte_at(mm, addr, ptep, pteval) set_pte(ptep, pteval) static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *ptep) { @@ -234,6 +237,22 @@ static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *pt } #endif +static inline void set_pte_at(struct mm_struct *mm, unsigned long addr, + pte_t *ptep, pte_t pteval) +{ + extern void __update_cache(unsigned long address, pte_t pte); + + if (!pte_present(pteval)) + goto cache_sync_done; + + if (pte_present(*ptep) && (pte_pfn(*ptep) == pte_pfn(pteval))) + goto cache_sync_done; + + __update_cache(addr, pteval); +cache_sync_done: + set_pte(ptep, pteval); +} + /* * (pmds are folded into puds so this doesn't get actually called, * but the define is needed for a generic inline function.) @@ -430,15 +449,12 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) extern void __update_tlb(struct vm_area_struct *vma, unsigned long address, pte_t pte); -extern void __update_cache(struct vm_area_struct *vma, unsigned long address, - pte_t pte); static inline void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { pte_t pte = *ptep; __update_tlb(vma, address, pte); - __update_cache(vma, address, pte); } static inline void update_mmu_cache_pmd(struct vm_area_struct *vma, diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index f2975d4d1e449c..503cce6893aee9 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -582,11 +582,19 @@ int mips_get_process_fp_mode(struct task_struct *task) return value; } +static void prepare_for_fp_mode_switch(void *info) +{ + struct mm_struct *mm = info; + + if (current->mm == mm) + lose_fpu(1); +} + int mips_set_process_fp_mode(struct task_struct *task, unsigned int value) { const unsigned int known_bits = PR_FP_MODE_FR | PR_FP_MODE_FRE; - unsigned long switch_count; struct task_struct *t; + int max_users; /* Check the value is valid */ if (value & ~known_bits) @@ -603,6 +611,9 @@ int mips_set_process_fp_mode(struct task_struct *task, unsigned int value) if (!(value & PR_FP_MODE_FR) && cpu_has_fpu && cpu_has_mips_r6) return -EOPNOTSUPP; + /* Proceed with the mode switch */ + preempt_disable(); + /* Save FP & vector context, then disable FPU & MSA */ if (task->signal == current->signal) lose_fpu(1); @@ -612,31 +623,17 @@ int mips_set_process_fp_mode(struct task_struct *task, unsigned int value) smp_mb__after_atomic(); /* - * If there are multiple online CPUs then wait until all threads whose - * FP mode is about to change have been context switched. This approach - * allows us to only worry about whether an FP mode switch is in - * progress when FP is first used in a tasks time slice. Pretty much all - * of the mode switch overhead can thus be confined to cases where mode - * switches are actually occuring. That is, to here. However for the - * thread performing the mode switch it may take a while... + * If there are multiple online CPUs then force any which are running + * threads in this process to lose their FPU context, which they can't + * regain until fp_mode_switching is cleared later. */ if (num_online_cpus() > 1) { - spin_lock_irq(&task->sighand->siglock); - - for_each_thread(task, t) { - if (t == current) - continue; - - switch_count = t->nvcsw + t->nivcsw; + /* No need to send an IPI for the local CPU */ + max_users = (task->mm == current->mm) ? 1 : 0; - do { - spin_unlock_irq(&task->sighand->siglock); - cond_resched(); - spin_lock_irq(&task->sighand->siglock); - } while ((t->nvcsw + t->nivcsw) == switch_count); - } - - spin_unlock_irq(&task->sighand->siglock); + if (atomic_read(¤t->mm->mm_users) > max_users) + smp_call_function(prepare_for_fp_mode_switch, + (void *)current->mm, 1); } /* @@ -661,6 +658,7 @@ int mips_set_process_fp_mode(struct task_struct *task, unsigned int value) /* Allow threads to use FP again */ atomic_set(&task->mm->context.fp_mode_switching, 0); + preempt_enable(); return 0; } diff --git a/arch/mips/mm/cache.c b/arch/mips/mm/cache.c index aab218c36e0d3e..e87bccd6e0aae7 100644 --- a/arch/mips/mm/cache.c +++ b/arch/mips/mm/cache.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -83,8 +84,6 @@ void __flush_dcache_page(struct page *page) struct address_space *mapping = page_mapping(page); unsigned long addr; - if (PageHighMem(page)) - return; if (mapping && !mapping_mapped(mapping)) { SetPageDcacheDirty(page); return; @@ -95,8 +94,15 @@ void __flush_dcache_page(struct page *page) * case is for exec env/arg pages and those are %99 certainly going to * get faulted into the tlb (and thus flushed) anyways. */ - addr = (unsigned long) page_address(page); + if (PageHighMem(page)) + addr = (unsigned long)kmap_atomic(page); + else + addr = (unsigned long)page_address(page); + flush_data_cache_page(addr); + + if (PageHighMem(page)) + __kunmap_atomic((void *)addr); } EXPORT_SYMBOL(__flush_dcache_page); @@ -119,33 +125,28 @@ void __flush_anon_page(struct page *page, unsigned long vmaddr) EXPORT_SYMBOL(__flush_anon_page); -void __flush_icache_page(struct vm_area_struct *vma, struct page *page) -{ - unsigned long addr; - - if (PageHighMem(page)) - return; - - addr = (unsigned long) page_address(page); - flush_data_cache_page(addr); -} -EXPORT_SYMBOL_GPL(__flush_icache_page); - -void __update_cache(struct vm_area_struct *vma, unsigned long address, - pte_t pte) +void __update_cache(unsigned long address, pte_t pte) { struct page *page; unsigned long pfn, addr; - int exec = (vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc; + int exec = !pte_no_exec(pte) && !cpu_has_ic_fills_f_dc; pfn = pte_pfn(pte); if (unlikely(!pfn_valid(pfn))) return; page = pfn_to_page(pfn); - if (page_mapping(page) && Page_dcache_dirty(page)) { - addr = (unsigned long) page_address(page); + if (Page_dcache_dirty(page)) { + if (PageHighMem(page)) + addr = (unsigned long)kmap_atomic(page); + else + addr = (unsigned long)page_address(page); + if (exec || pages_do_alias(addr, address & PAGE_MASK)) flush_data_cache_page(addr); + + if (PageHighMem(page)) + __kunmap_atomic((void *)addr); + ClearPageDcacheDirty(page); } } diff --git a/arch/mips/pistachio/Makefile b/arch/mips/pistachio/Makefile index 32189c6ebea50e..9e73114b22c3bd 100644 --- a/arch/mips/pistachio/Makefile +++ b/arch/mips/pistachio/Makefile @@ -1 +1,3 @@ obj-y += init.o irq.o time.o + +obj-$(CONFIG_SUSPEND) += irq_wake.o pm.o suspend.o diff --git a/arch/mips/pistachio/init.c b/arch/mips/pistachio/init.c index 96ba2cc9ad3e34..5aefb064de85ac 100644 --- a/arch/mips/pistachio/init.c +++ b/arch/mips/pistachio/init.c @@ -2,6 +2,7 @@ * Pistachio platform setup * * Copyright (C) 2014 Google, Inc. + * Copyright (C) 2016 Imagination Technologies * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -9,10 +10,12 @@ */ #include +#include #include #include #include #include +#include #include #include @@ -24,32 +27,38 @@ #include #include +/* + * Core revision register decoding + * Bits 23 to 20: Major rev + * Bits 15 to 8: Minor rev + * Bits 7 to 0: Maintenance rev + */ +#define PISTACHIO_CORE_REV_REG 0xB81483D0 +#define PISTACHIO_CORE_REV_A1 0x00100006 +#define PISTACHIO_CORE_REV_B0 0x00100106 + const char *get_system_type(void) { - return "IMG Pistachio SoC"; -} + u32 core_rev; + const char *sys_type; -static void __init plat_setup_iocoherency(void) -{ - /* - * Kernel has been configured with software coherency - * but we might choose to turn it off and use hardware - * coherency instead. - */ - if (mips_cm_numiocu() != 0) { - /* Nothing special needs to be done to enable coherency */ - pr_info("CMP IOCU detected\n"); - hw_coherentio = 1; - if (coherentio == 0) - pr_info("Hardware DMA cache coherency disabled\n"); - else - pr_info("Hardware DMA cache coherency enabled\n"); - } else { - if (coherentio == 1) - pr_info("Hardware DMA cache coherency unsupported, but enabled from command line!\n"); - else - pr_info("Software DMA cache coherency enabled\n"); + core_rev = __raw_readl((const void *)PISTACHIO_CORE_REV_REG); + + switch (core_rev) { + case PISTACHIO_CORE_REV_B0: + sys_type = "IMG Pistachio SoC (B0)"; + break; + + case PISTACHIO_CORE_REV_A1: + sys_type = "IMG Pistachio SoC (A1)"; + break; + + default: + sys_type = "IMG Pistachio SoC"; + break; } + + return sys_type; } void __init plat_mem_setup(void) @@ -58,8 +67,6 @@ void __init plat_mem_setup(void) panic("Device-tree not present"); __dt_setup_arch((void *)fw_arg1); - - plat_setup_iocoherency(); } #define DEFAULT_CPC_BASE_ADDR 0x1bde0000 @@ -109,6 +116,8 @@ void __init prom_init(void) mips_cm_probe(); mips_cpc_probe(); register_cps_smp_ops(); + + pr_info("SoC Type: %s\n", get_system_type()); } void __init prom_free_prom_memory(void) @@ -131,6 +140,8 @@ static int __init plat_of_setup(void) if (of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)) panic("Failed to populate DT"); + platform_device_register_simple("cpufreq-dt", -1, NULL, 0); + return 0; } arch_initcall(plat_of_setup); diff --git a/arch/mips/pistachio/irq_wake.c b/arch/mips/pistachio/irq_wake.c new file mode 100644 index 00000000000000..416488bcdec46b --- /dev/null +++ b/arch/mips/pistachio/irq_wake.c @@ -0,0 +1,112 @@ +/* + * Irq wake driver for Pistachio Platform. + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define PISTACHIO_CLK_WAKEUP 0x010C +#define PISTACHIO_DEFAULT_WAKEIRQ_START 2 +#define PISTACHIO_DEFAULT_WAKEIRQ_END 95 +/* Max number of wake registers available in Pistachio platform */ +#define PISTACHIO_WAKEREGS_NUM 3 + +static struct regmap *pistachio_clk_regs; +static int wakeirq_start, wakeirq_end; + +bool pistachio_wakeirq_set(void) +{ + int ret = false; + int val[PISTACHIO_WAKEREGS_NUM], i, wake_irq = 0; + + regmap_bulk_read(pistachio_clk_regs, PISTACHIO_CLK_WAKEUP, &val, + PISTACHIO_WAKEREGS_NUM); + + for (i = 0 ; i < PISTACHIO_WAKEREGS_NUM; i++) + wake_irq |= val[i]; + + if (wake_irq) + ret = true; + + return ret; +} + +static int pistachio_irq_wake(struct irq_data *data, unsigned int on) +{ + unsigned int irq = GIC_HWIRQ_TO_SHARED(data->hwirq); + unsigned int wake_irq_off = GIC_INTR_OFS(irq); + unsigned int wake_irq_bit = irq - wakeirq_start; + + unsigned int offset = PISTACHIO_CLK_WAKEUP + wake_irq_off; + unsigned int mask = BIT(wake_irq_bit); + unsigned int val = 0; + + if (on) + val = mask; + + regmap_update_bits(pistachio_clk_regs, offset, mask, val); + + return 0; +} + +static int pistachio_irqwake_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret = 0; + int i = 0; + + pistachio_clk_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,clk_core"); + if (IS_ERR(pistachio_clk_regs)) { + dev_err(&pdev->dev, "[%s]: pistachio-clk_core lookup \ + failure irq-wake will be non-functional\n", __func__); + return PTR_ERR(pistachio_clk_regs); + } + + if (of_property_read_u32(np, "irq-start", &wakeirq_start)) + wakeirq_start = PISTACHIO_DEFAULT_WAKEIRQ_START; + + if (of_property_read_u32(np, "irq-end", &wakeirq_end)) + wakeirq_start = PISTACHIO_DEFAULT_WAKEIRQ_END; + + for (i = wakeirq_start; i < wakeirq_end; i++) { + struct irq_chip *chip = irq_get_chip(i); + chip->irq_set_wake = pistachio_irq_wake; + } + + return ret; +} + +static int pistachio_irqwake_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id pistachio_irqwake_match[] = { + { .compatible = "pistachio,irq-wake" }, + {} +}; + +static struct platform_driver pistachio_irqwake_driver = { + .probe = pistachio_irqwake_probe, + .remove = pistachio_irqwake_remove, + .driver = { + .name = "pistachio-irq-wake", + .of_match_table = of_match_ptr(pistachio_irqwake_match), + } +}; +module_platform_driver(pistachio_irqwake_driver); diff --git a/arch/mips/pistachio/pm.c b/arch/mips/pistachio/pm.c new file mode 100644 index 00000000000000..77625da698607c --- /dev/null +++ b/arch/mips/pistachio/pm.c @@ -0,0 +1,144 @@ +/* + * PM Suspend to memory driver for Pistachio Platform. + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +/* MIPS TOP level Gate reg */ +#define CR_TOP_MIPS_CLOCK_GATE KSEG1ADDR(0x18144104) +#define CR_TOP_MIPSCLKOUT_MIPS_BIT 0 + +static void (*pistachio_suspend_in_sram_fn)(void); +extern void pistachio_sram_suspend(void); +extern bool pistachio_wakeirq_set(void); +extern unsigned int pistachio_sram_suspend_sz; + +static int pistachio_pm_prepare_late(void) +{ + /* + * Deny system suspend if any of the wakeup sources + * are not enabled. If we enter suspend with no wakeup + * sources system is in unusable state. + */ + if (!pistachio_wakeirq_set()) { + pr_warn("[%s]: No wakeup sources set cannot suspend system\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int pistachio_pm_enter(suspend_state_t state) +{ + local_flush_tlb_all(); + + /* AUDIO Workaround: Gate audio clocks. */ + clear_bit(2, (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + clear_bit(22, (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + + if (!pistachio_suspend_in_sram_fn) { + /* + * Disable MIPS clock, this is only clock gating not power gating. + * so MIPS state will not be lost and we can resume from gated + * state. + */ + + clear_bit(CR_TOP_MIPSCLKOUT_MIPS_BIT, + (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + + /* + * Enable MIPS clock back after wakeup, + * PC will start resuming execution from gated state + * for a small time cycle, So enable MIPS clocks immediately + * after resume. + * Note: Only Wake irq mask can wakeup the system. + */ + + set_bit(CR_TOP_MIPSCLKOUT_MIPS_BIT, + (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + + } else { + /* + * call low level suspend function in sram, + * as we need to put DDR to self refresh mode. + */ + pistachio_suspend_in_sram_fn(); + } + + /* AUDIO Workaround: Enable Audio clks. */ + set_bit(2, (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + set_bit(22, (void __iomem *)CR_TOP_MIPS_CLOCK_GATE); + + return 0; +} + +static const struct platform_suspend_ops pistachio_pm_ops = { + .valid = suspend_valid_only_mem, + .prepare_late = pistachio_pm_prepare_late, + .enter = pistachio_pm_enter, +}; + +static int __init pistachio_pm_init(void) +{ + phys_addr_t sram_pbase; + struct device_node *node; + struct platform_device *pdev; + + struct gen_pool *sram_pool; + unsigned long sram_vbase; + int ret = 0; + void __iomem *suspend_sram_base; + + suspend_set_ops(&pistachio_pm_ops); + + node = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!node) { + pr_warn("%s: failed to find sram node!\n", __func__); + return -ENODEV; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_warn("%s: failed to find sram device!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + sram_pool = gen_pool_get(&pdev->dev, NULL); + if (!sram_pool) { + pr_warn("%s: sram pool unavailable!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + sram_vbase = gen_pool_alloc(sram_pool, pistachio_sram_suspend_sz); + if (!sram_vbase) { + pr_warn("%s: unable to alloc sram!\n", __func__); + ret = -ENOMEM; + goto put_node; + } + + sram_pbase = gen_pool_virt_to_phys(sram_pool, sram_vbase); + suspend_sram_base = ioremap(sram_pbase, pistachio_sram_suspend_sz); + memcpy(suspend_sram_base, (void *)pistachio_sram_suspend, + pistachio_sram_suspend_sz); + pistachio_suspend_in_sram_fn = (void *)suspend_sram_base; + +put_node: + of_node_put(node); + + return ret; +} +late_initcall(pistachio_pm_init); diff --git a/arch/mips/pistachio/suspend.S b/arch/mips/pistachio/suspend.S new file mode 100644 index 00000000000000..22b005609a92e7 --- /dev/null +++ b/arch/mips/pistachio/suspend.S @@ -0,0 +1,236 @@ +/* + * PM Suspend to sram memory routine for pistachio Platform. + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include +#include +#include + +/* DDR Register Maps */ +#define DDR_PCTL_SCTL 0xB8180004 +#define DDR_PCTL_SCTL_SLEEP_CMD 0x00000003 +#define DDR_PCTL_SCTL_WKUP_CMD 0x00000004 +#define DDR_PCTL_STAT 0xB8180008 +#define DDR_PCTL_STAT_STL_LOW_PWR 0x00000005 +#define DDR_PCTL_STAT_STL_ACCESS 0x00000003 + +/* SYS-PLL Top level register */ +#define CR_SYS_PLL_STATUS 0xB8144038 +#define CR_SYS_PLL_CTRL1 0xB814403C +#define CR_SYS_PLL_FBDIV_MASK (0xFFF << 6) +#define CR_SYS_PLL_LOCK 0x00000001 +#define CR_SYS_PLL_MINVCO_FB100 (100 << 6) +#define CR_SYS_PLL_VCO_FB350 (350 << 6) + +/* MIPS TOP level Gate reg */ +#define CR_MIPS_PLL_STATUS 0xB8144000 +#define CR_MIPS_PLL_CTRL1 0xB8144004 +#define CR_MIPS_PLL_FBDIV_MASK (0xFFF << 6) +#define CR_MIPS_PLL_FBDIV_MASK_SET_40 (40 << 6) +#define CR_MIPS_PLL_FBDIV_MASK_SET_80 (80 << 6) +#define CR_MIPS_PLL_LOCK 0x00000001 + +#define CR_TOP_MIPS_CLOCK_GATE 0xB8144104 +#define MIPS_CLOCK_GATE_MASK 0x00000001 + +/* Top level system register */ +#define CR_DDR_CTRL 0xB8149020 +#define CR_DDR_CLK_MASK 0x00000002 + +/* External timer dividers */ +#define CR_TOP_COUNTERSLOWDIV1_CTRL 0xB8144914 +#define COUNTERSLOW_DIV1_32K_DIV_VAL 0x0000006F +#define COUNTERSLOW_DIV1_32K_SCALED_DIV_VAL 0x0000001F + +#define CR_TOP_IRDIV1_CTRL 0xB814491C +#define IR_DIV1_32K_DIV_VAL 0x00000057 +#define IR_DIV1_DIV1_32K_SCALED_DIV_VAL 0x00000018 + +#define CR_TOP_ENETPHYCLKOUT_CTRL 0xB8144230 +#define ENET_PHYCLKOUT_50M_DIV_VAL 0x00000006 +#define ENET_PHYCLKOUT_50M_SCALED_DIV_VAL 0x00000001 + +.text +FEXPORT(pistachio_sram_suspend) +LEAF(pistachio_sram_suspend) + +enter_ddr_self_refresh: + /* request uPCTL Low pwer state from access state */ + PTR_LI t0, DDR_PCTL_SCTL + PTR_LI t1, DDR_PCTL_SCTL_SLEEP_CMD + PTR_S t1, (t0) + + /* + * Poll to check if we transitioned + * to Low power state + */ + + PTR_LI t0, DDR_PCTL_STAT +lower_power_poll: + PTR_L t1, (t0) + and t2, t1, DDR_PCTL_STAT_STL_LOW_PWR + bne t2, DDR_PCTL_STAT_STL_LOW_PWR, lower_power_poll + +ddr_clk_gate: + PTR_LI t0, CR_DDR_CTRL + PTR_L t1, (t0) + and t2, t1, ~CR_DDR_CLK_MASK + PTR_S t2, (t0) + + /* + * We are scaling syspll so we need to scale mips pll as well. + * Scaling MIPS to 208Mhz. + */ +mips_pll_scale: + /* modify FBDIV 40 => 208MHz */ + PTR_LI t0, CR_MIPS_PLL_CTRL1 + PTR_L t1, (t0) + and t2, t1, ~CR_MIPS_PLL_FBDIV_MASK + or t2, CR_MIPS_PLL_FBDIV_MASK_SET_40 + PTR_S t2, (t0) + +mips_pll_loop: + PTR_LI t0, CR_MIPS_PLL_STATUS + PTR_L t1, (t0) + bne t1, CR_MIPS_PLL_LOCK, mips_pll_loop + +sys_pll_scale: + /* + * Scale down sys_pll to 100Mhz from 350Mhz. + * Set feedback to 100 to scale. + */ + PTR_LI t0, CR_SYS_PLL_CTRL1 + PTR_L t1, (t0) + and t2, t1, ~CR_SYS_PLL_FBDIV_MASK + /* FOUTVCO-MIN 400Mhz fb 100 */ + PTR_LI t3, CR_SYS_PLL_MINVCO_FB100 + and t3, t3, CR_SYS_PLL_FBDIV_MASK + or v0, t3, t2 + PTR_S v0, (t0) + +slow_timer_clk_adj: + /* + * Adjust external slow clock since its on sys_pll + * slow_counter prediv runs at 3.125Mhz so using new divider + * 0x1F (val-1) (count => 32) we can derive 3.125Mhz from 100Mhz. + */ + PTR_LI t0, CR_TOP_COUNTERSLOWDIV1_CTRL + PTR_LI t1, COUNTERSLOW_DIV1_32K_SCALED_DIV_VAL + PTR_S t1, (t0) + +ir_adj: + PTR_LI t0, CR_TOP_IRDIV1_CTRL + PTR_LI t1, IR_DIV1_DIV1_32K_SCALED_DIV_VAL + PTR_S t1, (t0) + + /* to support wake-on-lan */ +enet_adj: + PTR_LI t0, CR_TOP_ENETPHYCLKOUT_CTRL + PTR_LI t1, ENET_PHYCLKOUT_50M_SCALED_DIV_VAL + PTR_S t1, (t0) + +pll_lock_loop1: + PTR_LI t0, CR_SYS_PLL_STATUS + PTR_L t1, (t0) + bne t1, CR_SYS_PLL_LOCK, pll_lock_loop1 + +suspend: + PTR_LI t0, CR_TOP_MIPS_CLOCK_GATE + PTR_L t1, (t0) + PTR_LI t2, ~MIPS_CLOCK_GATE_MASK + and v0, t1, t2 + PTR_S v0, (t0) + +resume: + PTR_LI t0, CR_TOP_MIPS_CLOCK_GATE + PTR_L t1, (t0) + PTR_LI t2, MIPS_CLOCK_GATE_MASK + or v0, t1, t2 + PTR_S v0, (t0) + +sys_pll_setup: + /* + * Configure sys_pll at 350 Mhz. + * Set feedback to 350 => VCO 1400Mhz + */ + PTR_LI t0, CR_SYS_PLL_CTRL1 + PTR_L t1, (t0) + and t2, t1, ~CR_SYS_PLL_FBDIV_MASK + PTR_LI t3, CR_SYS_PLL_VCO_FB350 + and t3, t3, CR_SYS_PLL_FBDIV_MASK + or v0, t3, t2 + PTR_S v0, (t0) + +slow_timer_setup: + PTR_LI t0, CR_TOP_COUNTERSLOWDIV1_CTRL + PTR_LI t1, COUNTERSLOW_DIV1_32K_DIV_VAL + PTR_S t1, (t0) + +ir_setup: + PTR_LI t0, CR_TOP_IRDIV1_CTRL + PTR_LI t1, IR_DIV1_32K_DIV_VAL + PTR_S t1, (t0) + +enet_setup: + PTR_LI t0, CR_TOP_ENETPHYCLKOUT_CTRL + PTR_LI t1, ENET_PHYCLKOUT_50M_DIV_VAL + PTR_S t1, (t0) + +pll_lock_loop: + PTR_LI t0, CR_SYS_PLL_STATUS + PTR_L t1, (t0) + bne t1, CR_SYS_PLL_LOCK, pll_lock_loop + + /* + * Setup mips pll back to original VCO. + * Setting up MIPS to 416 Mhz */ +mips_pll_setup: + /* Modify FBDIV 80 => 416MHz */ + PTR_LI t0, CR_MIPS_PLL_CTRL1 + PTR_L t1, (t0) + and t2, t1, ~CR_MIPS_PLL_FBDIV_MASK + or t2, CR_MIPS_PLL_FBDIV_MASK_SET_80 + PTR_S t2, (t0) + +mips_pll_loop1: + PTR_LI t0, CR_MIPS_PLL_STATUS + PTR_L t1, (t0) + bne t1, CR_MIPS_PLL_LOCK, mips_pll_loop1 + +ddr_clk_ungate: + PTR_LI t0, CR_DDR_CTRL + PTR_L t1, (t0) + or t2, t1, CR_DDR_CLK_MASK + PTR_S t2, (t0) + +exit_ddr_self_refresh: + /* Request DDR uPCTL to access state from low power state */ + PTR_LI t0, DDR_PCTL_SCTL + PTR_LI t1, DDR_PCTL_SCTL_WKUP_CMD + PTR_S t1, (t0) + + /* + * Poll to check if we transitioned + * to access state. + */ + PTR_LI t0, DDR_PCTL_STAT +ddr_access_poll: + PTR_L t1, (t0) + and t2, t1, DDR_PCTL_STAT_STL_ACCESS + bne t2, DDR_PCTL_STAT_STL_ACCESS, ddr_access_poll + + /* start executing from DDR */ + jr ra + +END(pistachio_sram_suspend) + +pistachio_sram_suspend_sz: + EXPORT(pistachio_sram_suspend_sz) + PTR .-pistachio_sram_suspend + .size pistachio_sram_suspend_sz, PTRSIZE diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index ec6af15950622e..2f7ba6560d8d93 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -320,4 +320,13 @@ config BT_WILINK Say Y here to compile support for Texas Instrument's WiLink7 driver into the kernel or say M to compile it as module (btwilink). +config BT_IMG + tristate "Imagination Technologies Bluetooth driver" + depends on BT_HCIUART && BT_HCIUART_H4 && IMG_HOSTPORT + help + Imagination Technologies Bluetooth driver which exposes UCCP bluetooth + subsystem as a serial device. + + Say Y here to compile support for Imagination Technologies' Bluetooth + driver into the kernel or say M to compile it as module (img-bt). endmenu diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 07c9cf381e5aeb..fedbddc1e68481 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -2,6 +2,8 @@ # Makefile for the Linux Bluetooth HCI device drivers. # +obj-$(CONFIG_BT_IMG) += pistachio/ + obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o obj-$(CONFIG_BT_HCIUART) += hci_uart.o obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o diff --git a/drivers/bluetooth/pistachio/Makefile b/drivers/bluetooth/pistachio/Makefile new file mode 100644 index 00000000000000..230ea47f391bfc --- /dev/null +++ b/drivers/bluetooth/pistachio/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_BT_IMG) += img-bt.o +img-bt-objs := img-bt-main.o img-bt-chardev.o payload.o etrace.o + +CFLAGS_etrace.o = -I$(src) diff --git a/drivers/bluetooth/pistachio/circ-buf-ext.h b/drivers/bluetooth/pistachio/circ-buf-ext.h new file mode 100644 index 00000000000000..908d2a9b00cda5 --- /dev/null +++ b/drivers/bluetooth/pistachio/circ-buf-ext.h @@ -0,0 +1,98 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : circ_buf_ext.h + *** + *** File Description: + *** This file contains interface and implementation of an extension to + *** the Linux circ_buf module + *** + ****************************************************************************** + *END*************************************************************************/ + +#ifndef __CIRC_BUF_EXT_H__ +#define __CIRC_BUF_EXT_H__ 1 + +#include + +#define DEFINE_CIRC_BUF_EXT_TYPE(mem_type, name) \ + struct name {\ + mem_type *base;\ + int tail;\ + int head;\ + int size;\ + }; + +DEFINE_CIRC_BUF_EXT_TYPE(u8, circ_buf_ext); + +#define circ_buf_ext_scroll(field, buf_ptr, by) \ + ((buf_ptr)->field = ((buf_ptr)->field + (by)) & ((buf_ptr)->size - 1)) +#define circ_buf_ext_take(buf, n) \ + circ_buf_ext_scroll(head, buf, n) +#define circ_buf_ext_give(buf, n) \ + circ_buf_ext_scroll(tail, buf, n) +#define circ_buf_ext_read_offset(buf, idx) \ + ((buf)->base + (((buf)->tail + (idx)) & ((buf)->size - 1))) +#define circ_buf_ext_write_offset(buf, idx) \ + ((buf)->base + (((buf)->head + (idx)) & ((buf)->size - 1))) + +static inline void circ_buf_ext_io_to_krn( + struct circ_buf_ext *to, + struct circ_buf_ext *from, + unsigned int n) +{ + unsigned int idx; + u8 tmp; + for (idx = 0; idx < n; idx++) { + tmp = ioread8((u8 __iomem *)circ_buf_ext_read_offset(from, + idx)); + *circ_buf_ext_write_offset(to, idx) = tmp; + } + circ_buf_ext_take(to, n); + circ_buf_ext_give(from, n); +} + +static inline void circ_buf_ext_krn_to_io( + struct circ_buf_ext *to, + struct circ_buf_ext *from, + unsigned int n) +{ + unsigned int idx; + u8 tmp; + for (idx = 0; idx < n; idx++) { + tmp = *circ_buf_ext_read_offset(from, idx); + iowrite8(tmp, (u8 __iomem *)circ_buf_ext_write_offset(to, idx)); + } + circ_buf_ext_take(to, n); + circ_buf_ext_give(from, n); +} + +static inline int circ_buf_ext_space(struct circ_buf_ext *buf) +{ + return CIRC_SPACE(buf->head, buf->tail, buf->size); +} + +static inline int circ_buf_ext_count(struct circ_buf_ext *buf) +{ + return CIRC_CNT(buf->head, buf->tail, buf->size); +} + +#endif diff --git a/drivers/bluetooth/pistachio/circ-buf-oneway.h b/drivers/bluetooth/pistachio/circ-buf-oneway.h new file mode 100644 index 00000000000000..7ba6b8885836b5 --- /dev/null +++ b/drivers/bluetooth/pistachio/circ-buf-oneway.h @@ -0,0 +1,134 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : circ_buf_oneway.h + *** + *** File Description: + *** This file contains interface and implementation of circular buffers + *** which only maintain one read/write pointer and therefore use their + *** full capacity. + *** + ****************************************************************************** + *END*************************************************************************/ + +#ifndef __CIRC_BUF_ONEWAY_H__ +#define __CIRC_BUF_ONEWAY_H__ 1 + +#include + +#include + +#define CIRC_BUF_CORE \ + u8 *base;\ + atomic_t cur;\ + size_t size + +/* + * Routines common for both types of buffer + */ + +static inline size_t __pure offset_from_cur(size_t cur, size_t off, + size_t buf_size) +{ + return (cur + off) & (buf_size - 1); +} + +/* + * Buffer for incoming data - no need to track available space. + */ + +struct circ_buf_in { + CIRC_BUF_CORE; +}; + +static inline int circ_buf_in_init(struct circ_buf_in *buf, + u8 *base, + size_t size) +{ + + if (!is_power_of_2(size)) + return -1; + buf->base = base; + buf->size = size; + atomic_set(&buf->cur, 0); + return 0; +} + +static inline void circ_buf_in_read_done(struct circ_buf_in *buf, size_t n) +{ + atomic_add(n, &buf->cur); +} + +static inline u8 *circ_buf_in_read_offset(struct circ_buf_in *buf, size_t off) +{ + return buf->base + offset_from_cur(atomic_read(&buf->cur), off, + buf->size); +} + +/* + * Buffer for outgoing data - tracking where to write next and how much can + * be written. + */ + +struct circ_buf_out { + CIRC_BUF_CORE; + atomic_t avail; +}; + +static inline int circ_buf_out_init(struct circ_buf_out *buf, + u8 *base, + size_t size) +{ + if (!is_power_of_2(size)) + return -1; + buf->base = base; + buf->size = size; + atomic_set(&buf->cur, 0); + atomic_set(&buf->avail, size); + return 0; +} + +static inline void circ_buf_out_write_done(struct circ_buf_out *buf, + size_t n) +{ + atomic_add(n, &buf->cur); + atomic_sub(n, &buf->avail); +} + +static inline void circ_buf_out_write_rcvd(struct circ_buf_out *buf, + size_t n) +{ + atomic_add(n, &buf->avail); +} + +static inline u8 *circ_buf_out_write_offset(const struct circ_buf_out *buf, + size_t off) +{ + return buf-> base + offset_from_cur(atomic_read(&buf->cur), off, + buf->size); +} + +static inline size_t circ_buf_out_space(const struct circ_buf_out *buf) +{ + return atomic_read(&buf->avail); +} + +#endif diff --git a/drivers/bluetooth/pistachio/etrace.c b/drivers/bluetooth/pistachio/etrace.c new file mode 100644 index 00000000000000..ca8a25fa5c9a6a --- /dev/null +++ b/drivers/bluetooth/pistachio/etrace.c @@ -0,0 +1,31 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : etrace.c + *** + *** File Description: + *** Tracepoint exports for Imagination Bluetooth driver + *** + ****************************************************************************** + *END**************************************************************************/ + +#define CREATE_TRACE_POINTS +#include "etrace.h" diff --git a/drivers/bluetooth/pistachio/etrace.h b/drivers/bluetooth/pistachio/etrace.h new file mode 100644 index 00000000000000..77f63ff3b4a65f --- /dev/null +++ b/drivers/bluetooth/pistachio/etrace.h @@ -0,0 +1,138 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : etrace.h + *** + *** File Description: + *** Declaration of trace events + *** + ****************************************************************************** + *END**************************************************************************/ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM img_bt + +#if !defined(__IMG_BT_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __IMG_BT_TRACE_H__ + +#include + +#define DEFINE_HOSTPORT_EVENT(name) \ + DEFINE_EVENT(hostport_event_template, name,\ + TP_PROTO(unsigned long id, unsigned long size),\ + TP_ARGS(id, size)) + +DECLARE_EVENT_CLASS(hostport_event_template, + TP_PROTO(unsigned long id, unsigned long size), + TP_ARGS(id, size), + TP_STRUCT__entry( + __field( unsigned long, time ) + __field( unsigned int, id ) + __field( unsigned int, size ) + ), + TP_fast_assign( + __entry->time = jiffies; + __entry->id = id; + __entry->size = size; + ), + TP_printk("time=%lu id=%u size=%u", + __entry->time, + __entry->id, + __entry->size) + ); + +DEFINE_HOSTPORT_EVENT(ctl_ack_sched); +DEFINE_HOSTPORT_EVENT(ctl_ack_execd); + +DEFINE_HOSTPORT_EVENT(ctl_req_sched); +DEFINE_HOSTPORT_EVENT(ctl_req_execd); + +DEFINE_HOSTPORT_EVENT(hst_ack_sched); +DEFINE_HOSTPORT_EVENT(hst_ack_execd); + +DEFINE_HOSTPORT_EVENT(hst_req_sched); +DEFINE_HOSTPORT_EVENT(hst_req_execd_sent); +DEFINE_HOSTPORT_EVENT(hst_req_execd_delayed); +DEFINE_HOSTPORT_EVENT(hst_req_execd_catchup); + +#define DEFINE_CHARDEV_EVENT(name) \ + DEFINE_EVENT(chardev_event_template, name,\ + TP_PROTO(int dummy),\ + TP_ARGS(dummy)) + +DECLARE_EVENT_CLASS(chardev_event_template, + TP_PROTO(int dummy), + TP_ARGS(dummy), + TP_STRUCT__entry( + __field(unsigned long, time) + __field(int, dummy) + ), + TP_fast_assign( + __entry->time = jiffies; + __entry->dummy = dummy; + ), + TP_printk("time=%lu data=%d", + __entry->time, __entry->dummy) + ); + +DEFINE_CHARDEV_EVENT(tty_flip_depleted); +DEFINE_CHARDEV_EVENT(tty_stop_rx_requested); +DEFINE_CHARDEV_EVENT(tty_request_port); +DEFINE_CHARDEV_EVENT(tty_release_port); +DEFINE_CHARDEV_EVENT(tty_start_tx); +DEFINE_CHARDEV_EVENT(tty_stop_tx); +DEFINE_CHARDEV_EVENT(tty_shutdown_port); +DEFINE_CHARDEV_EVENT(tty_startup_port); + +#define DEFINE_PAYLOAD_EVENT(name) \ + DEFINE_EVENT(payload_event_template, name,\ + TP_PROTO(unsigned int size, const char *type),\ + TP_ARGS(size, type)) + +DECLARE_EVENT_CLASS(payload_event_template, + TP_PROTO(unsigned int size, const char *type), + TP_ARGS(size, type), + TP_STRUCT__entry( + __field(unsigned int, size) + __field(const char *, type) + ), + TP_fast_assign( + __entry->size = size; + __entry->type = type; + ), + TP_printk("size=%u type=%s", + __entry->size, __entry->type) + ); + +DEFINE_PAYLOAD_EVENT(header_detected); +DEFINE_PAYLOAD_EVENT(header_parsed); +DEFINE_PAYLOAD_EVENT(length_detected); +DEFINE_PAYLOAD_EVENT(length_parsed); +DEFINE_PAYLOAD_EVENT(data_detected); +DEFINE_PAYLOAD_EVENT(data_parsed); + +#endif /* __IMG_BT_TRACE_H__ */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE etrace +#include + diff --git a/drivers/bluetooth/pistachio/gateway.h b/drivers/bluetooth/pistachio/gateway.h new file mode 100644 index 00000000000000..2b157426642a3a --- /dev/null +++ b/drivers/bluetooth/pistachio/gateway.h @@ -0,0 +1,44 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : gateway.h + *** + *** File Description: + *** This file contains interface declarations for gateway to the userspace + *** + ****************************************************************************** + *END**************************************************************************/ + +#ifndef __GATEWAY_H__ +#define __GATEWAY_H__ + +#include +#include + +struct payload; + +typedef void (*push_message)(struct payload *pld); + +int gateway_init(push_message, struct device *pdev); +void gateway_exit(void); +int gateway_send(struct payload *pld); + +#endif /* __GATEWAY_H__ */ diff --git a/drivers/bluetooth/pistachio/img-bt-chardev.c b/drivers/bluetooth/pistachio/img-bt-chardev.c new file mode 100644 index 00000000000000..b8d2a47ddf68fe --- /dev/null +++ b/drivers/bluetooth/pistachio/img-bt-chardev.c @@ -0,0 +1,224 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-bt-chardev.h + *** + *** File Description: + *** This file contains concrete implementation of gateway interface - + *** - serial device. + *** + ****************************************************************************** + *END**************************************************************************/ + +#include +#include +#include +#include +#include + +#include "circ-buf-ext.h" +#include "etrace.h" +#include "gateway.h" +#include "payload.h" + +static const char *client_name = "img-bt"; +#define dbg(format, ...) pr_debug("%s: " format, client_name, ## __VA_ARGS__) +#define err(format, ...) pr_err("%s: " format, client_name, ## __VA_ARGS__) +#define dbgn(format, ...) dbg(format "\n", ## __VA_ARGS__) +#define errn(format, ...) err(format "\n", ## __VA_ARGS__) + +/* + * *** Private storage *** + */ + +static struct { + push_message push_client_msg; + struct uart_port port; +} gateway; + +static struct uart_driver img_bt_uart_driver = { + .driver_name = "img-bt-uart", + .dev_name = "ttyHS", + .nr = 1, +}; + +/* + * *** Private procs *** + */ + +static unsigned char next_char(void *port, unsigned int idx) +{ + unsigned char c; + struct uart_port *uport = (struct uart_port *)port; + struct circ_buf *xmit = &uport->state->xmit; + (void)idx; + + c = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + uport->icount.tx++; + return c; +} + +static unsigned int img_bt_tx_empty(struct uart_port *port) { return 0; } +static void img_bt_set_mctrl(struct uart_port *port, unsigned int mctrl) {} +static unsigned int img_bt_get_mctrl(struct uart_port *port) { return 0; } +static void img_bt_break_ctl(struct uart_port *port, int break_state) {} +static void img_bt_enable_ms(struct uart_port *port) {} +static void img_bt_release_port(struct uart_port *port) {trace_tty_release_port(0);} +static int img_bt_request_port(struct uart_port *port) { trace_tty_request_port(0);return 0; } +static void img_bt_config_port(struct uart_port *port, int flags) {} +static int img_bt_verify_port(struct uart_port *port, + struct serial_struct *ser) { return 0; } +static void img_bt_set_termios(struct uart_port *port, struct ktermios *new, + struct ktermios *old) {} + +static const char *img_bt_type(struct uart_port *port) +{ + return client_name; +} + +static void img_bt_stop_rx(struct uart_port *port) +{ + trace_tty_stop_rx_requested(0); +} + +static void img_bt_start_tx(struct uart_port *port) +{ + struct payload *pld; + /* + * As this driver is intended to be used with the HCI line + * discipline, whenever this is called it means + * that some data has arrived from the top. Effectively, + * this proc plays the role of a TX interrupt in a proper + * UART driver. + */ + + /* + * Author doesn't like this. State structure is supposed to be private + * to serial_core. TODO: find another way. + */ + struct circ_buf *xmit = &port->state->xmit; + + trace_tty_start_tx(CIRC_CNT(xmit->head, xmit->tail, UART_XMIT_SIZE)); + + if (uart_circ_empty(xmit)) + return; + + /* + * next_char is guaranteed not to be called + * if this function returns error + */ + pld = payload_from_string(uart_circ_chars_pending(xmit), + next_char, + port); + if (IS_ERR_OR_NULL(pld)) + return; + + gateway.push_client_msg(pld); + +} + +static void img_bt_stop_tx(struct uart_port *port) +{ + trace_tty_stop_tx(0); +} + +static void img_bt_shutdown(struct uart_port *port) +{ + trace_tty_shutdown_port(0); +} + +static int img_bt_startup(struct uart_port *port) +{ + trace_tty_startup_port(0); + return 0; +} + +/* serial core callbacks */ +static struct uart_ops img_bt_ops = { + .tx_empty = img_bt_tx_empty, + .get_mctrl = img_bt_get_mctrl, + .set_mctrl = img_bt_set_mctrl, + .start_tx = img_bt_start_tx, + .stop_tx = img_bt_stop_tx, + .stop_rx = img_bt_stop_rx, + .enable_ms = img_bt_enable_ms, + .break_ctl = img_bt_break_ctl, + .startup = img_bt_startup, + .shutdown = img_bt_shutdown, + .set_termios = img_bt_set_termios, + .type = img_bt_type, + .release_port = img_bt_release_port, + .request_port = img_bt_request_port, + .config_port = img_bt_config_port, + .verify_port = img_bt_verify_port, +}; + +/* + * *** Public API *** + */ + +int gateway_init(push_message push_f, struct device *pdev) +{ + int ret; + + ret = uart_register_driver(&img_bt_uart_driver); + if (ret) { + errn("failed to register serial driver : errno %d", ret); + goto uart_register_driver_failed; + } + + gateway.push_client_msg = push_f; + + memset(&gateway.port, 0, sizeof(gateway.port)); + gateway.port.dev = pdev; + gateway.port.ops = &img_bt_ops; + gateway.port.type = PORT_HOSTPORT; + ret = uart_add_one_port(&img_bt_uart_driver, &gateway.port); + if (ret) { + errn("adding uart port failed"); + goto uart_add_one_port_failed; + } + return 0; + +uart_add_one_port_failed: + uart_unregister_driver(&img_bt_uart_driver); +uart_register_driver_failed: + return ret; +} + +void gateway_exit(void) +{ + uart_remove_one_port(&img_bt_uart_driver, &gateway.port); + uart_unregister_driver(&img_bt_uart_driver); +} + +int gateway_send(struct payload *pld) +{ + trace_tty_flip_depleted(tty_buffer_space_avail(&gateway.port.state->port)); + tty_insert_flip_string(&gateway.port.state->port, + payload_raw(pld), + payload_length(pld)); + tty_flip_buffer_push(&gateway.port.state->port); + payload_delete(pld); + + return 0; +} diff --git a/drivers/bluetooth/pistachio/img-bt-main.c b/drivers/bluetooth/pistachio/img-bt-main.c new file mode 100644 index 00000000000000..fbdf35fb795393 --- /dev/null +++ b/drivers/bluetooth/pistachio/img-bt-main.c @@ -0,0 +1,538 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-bt-main.c + *** + *** File Description: + *** This file contains the implementation of the IMG Bluetooth + *** transport protocol. + *** + ****************************************************************************** + *END**************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "circ-buf-oneway.h" +#include "etrace.h" +#include "gateway.h" +#include "payload.h" + +static const char *client_name = "img-bt"; +#define dbg(format, ...) pr_debug("%s: " format, client_name, ## __VA_ARGS__) +#define err(format, ...) pr_err("%s: " format, client_name, ## __VA_ARGS__) +#define dbgn(format, ...) dbg(format "\n", ## __VA_ARGS__) +#define errn(format, ...) err(format "\n", ## __VA_ARGS__) +#define diagerrn(format, ...) \ + errn("%s : %d : " format, __func__, __LINE__, ## __VA_ARGS__) +#define diagdbgn(format, ...) \ + dbgn("%s : %d : " format, __func__, __LINE__, ## __VA_ARGS__) + +#define ACK 1 +#define REQUEST 0 +#define CONTENT_MASK 0x7fff +#define CONTENT(msg) (msg & CONTENT_MASK) +#define TYPE_MASK 0x8000 +#define TYPE(msg) ((msg & TYPE_MASK) >> 15) +#define RPU_ACK(length) ((u16)(length | 0x8000)) +#define RPU_REQ(length) ((u16)(length & 0x7FFF)) + +#define BLUETOOTH_ID 1 + +typedef void __iomem *ioaddr_t; + +/* + * TODO: parameterize buffer length through module params + */ +static const resource_size_t buffer_length = 0x800; + +static struct workqueue_struct *img_bt_workqueue; + +static struct { + struct circ_buf_out tx; + struct circ_buf_in rx; + phys_addr_t phys_base; + ioaddr_t virt_base; + resource_size_t length; +} xmit_buffers; + +/* + * *** Work structures depot *** + * + * TODO: measure how many work_structs are actually needed + * with ACLs, because for any other command there should + * only be one msg in that ring buffer at any given time. + * + */ + +#define WORK_DEPOT_SIZE (1<<5) +#define TX_BACKLOG_SIZE (1<<5) +struct message_xfer { + union { + struct payload *pld; + u16 req_length; + }; + struct work_struct tbd; +}; +static struct message_xfer work_store[WORK_DEPOT_SIZE]; +static DEFINE_KFIFO(work_depot, struct message_xfer*, WORK_DEPOT_SIZE); +static DEFINE_KFIFO(tx_backlog, struct payload*, TX_BACKLOG_SIZE); + +static int work_depot_init(void) +{ + int i = 0; + for (i = 0; i < WORK_DEPOT_SIZE; i++) { + kfifo_put(&work_depot, work_store + i); + } + return 0; +} + +static struct message_xfer *prepare_work(work_func_t todo, unsigned long data) +{ + struct message_xfer *work; + if (!kfifo_get(&work_depot, &work)) { + errn("no free work structures"); + return NULL; + } + INIT_WORK(&work->tbd, todo); + return work; +} + +static void return_work(struct message_xfer *work) +{ + kfifo_put(&work_depot, work); +} + +static unsigned char next_char(void *buffer, unsigned idx) +{ + struct circ_buf_in *rx; + u8 retval; + + rx = (struct circ_buf_in *)buffer; + + retval = (u8)ioread8((void __iomem *)circ_buf_in_read_offset(rx, idx)); + + return retval; +} + +static void payload_to_circ_buf_out( + const struct payload *pld, + struct circ_buf_out *buf) +{ + char c; + int i; + int length = payload_length(pld); + + for (i = 0; i < length; i++) { + c = payload_at(pld, i); + iowrite8(c, (void __iomem *)circ_buf_out_write_offset(buf, i)); + } + circ_buf_out_write_done(buf, length); +} + +static void do_tx_backlog(void) +{ + struct payload *pld; + int dummy, length_sum = 0; + + if (kfifo_is_empty(&tx_backlog)) + return; + + while (kfifo_peek(&tx_backlog, &pld) && + circ_buf_out_space(&xmit_buffers.tx) >= payload_length(pld)) { + + length_sum += payload_length(pld); + /* + * The following call must succeed because we checked + * kfifo_peek and the fifo is managed only by this + * background thread. + * + * Dummy read just to make __must_check_helper satisfied + */ + dummy = kfifo_get(&tx_backlog, &pld); + payload_to_circ_buf_out(pld, &xmit_buffers.tx); + payload_delete(pld); + } + + if (length_sum > 0) { + img_transport_notify(RPU_REQ((u16)length_sum), BLUETOOTH_ID); + trace_hst_req_execd_catchup(BLUETOOTH_ID, length_sum); + } +} + +/* + * *** Worker procs *** + */ + +static void ack_from_controller(struct work_struct *tbd) +{ + struct message_xfer *work = container_of(tbd, struct message_xfer, tbd); + u16 payload_length = work->req_length; + + circ_buf_out_write_rcvd(&xmit_buffers.tx, payload_length); + + return_work(work); + + do_tx_backlog(); + + trace_ctl_ack_execd(BLUETOOTH_ID, payload_length); +} + +static void req_from_controller(struct work_struct *tbd) +{ + u16 user_data_length; + struct payload *pld; + + struct message_xfer *work = container_of(tbd, struct message_xfer, tbd); + /* + * This is the length of the data that has just arrived + */ + user_data_length = work->req_length; + if (0 == user_data_length) + goto exit; + + do_tx_backlog(); + /* + * Push messages going from the controller + */ + pld = payload_from_string(user_data_length, next_char, + &xmit_buffers.rx); + /* TODO: service this call's failure */ + gateway_send(pld); + + circ_buf_in_read_done(&xmit_buffers.rx, user_data_length); + img_transport_notify(RPU_ACK(user_data_length), BLUETOOTH_ID); + +exit: + return_work(work); + trace_ctl_req_execd(BLUETOOTH_ID, user_data_length); +} + +static void req_to_controller(struct work_struct *tbd) +{ + int space_needed, space_available; + struct payload *pld; + + struct message_xfer *work = container_of(tbd, struct message_xfer, tbd); + pld = work->pld; + if (IS_ERR_OR_NULL(pld)) { + diagerrn("payload is not a valid pointer"); + goto exit; + } + + space_needed = payload_length(pld); + space_available = circ_buf_out_space(&xmit_buffers.tx); + if (space_needed <= space_available) { + /* + * Process message going to the controller + */ + payload_to_circ_buf_out(pld, &xmit_buffers.tx); + payload_delete(pld); + img_transport_notify(RPU_REQ(space_needed), BLUETOOTH_ID); + trace_hst_req_execd_sent(BLUETOOTH_ID, space_needed); + } else { + /* + * Save for backlog processing, which should be fired on every + * poke confirmation and controller ACK + */ + if (!kfifo_put(&tx_backlog, pld)) { + diagerrn("no space in backlog, dropping payload"); + payload_delete(pld); + } + trace_hst_req_execd_delayed(BLUETOOTH_ID, space_needed); + } + +exit: + return_work(work); +} + +/* + * *** Message handlers *** + */ +static void handle_gateway_message(struct payload *pld) +{ + struct message_xfer *work = prepare_work(req_to_controller, + (unsigned long)pld); + if (NULL == work) { + diagerrn( + "no more free work structures, payload dropped"); + payload_delete(pld); + return; + } + trace_hst_req_sched(BLUETOOTH_ID, payload_length(pld)); + work->pld = pld; + if (!queue_work(img_bt_workqueue, &work->tbd)) { + diagerrn("bug : work already scheduled"); + } +} + +static void handle_controller_message(u16 user_data) +{ + struct message_xfer *work; + unsigned int content; + content = CONTENT(user_data); + switch (TYPE(user_data)) { + case ACK: + /* An acknowledgment has been received */ + work = prepare_work(ack_from_controller, content); + work->req_length = content; + /* Process whatever may be pending in the TX backlog */ + if (NULL == work) + diagerrn("no more free work structures"); + trace_ctl_ack_sched(BLUETOOTH_ID, work->req_length); + queue_work(img_bt_workqueue, &work->tbd); + break; + case REQUEST: + /* A data request has arrived */ + work = prepare_work(req_from_controller, content); + work->req_length = content; + trace_ctl_req_sched(BLUETOOTH_ID, work->req_length); + queue_work(img_bt_workqueue, &work->tbd); + break; + default: + errn("received unknown message type from controller"); + } +} + +/* + * *** Platform API *** + */ + +static int img_bt_pltfr_memsetup(void) +{ + img_bt_workqueue = create_singlethread_workqueue("img_bt_workqueue"); + if (IS_ERR_OR_NULL(img_bt_workqueue)) + return PTR_ERR(img_bt_workqueue); + + return 0; +} + +static void img_bt_pltfr_memsetup_rollback(void) +{ + memset(&xmit_buffers, 0 , sizeof(xmit_buffers)); + return; +} + +static int img_bt_pltfr_dtsetup(struct platform_device *pdev) +{ + const struct resource *buffers_area = platform_get_resource(pdev, + IORESOURCE_MEM, 0); + if (NULL == buffers_area) { + errn("no DTS entry for buffers base address"); + return -ENOENT; + } + xmit_buffers.phys_base = + (phys_addr_t)buffers_area->start; + xmit_buffers.length = + (resource_size_t)(buffers_area->end - buffers_area->start + 1); + + return 0; +} + +static void img_bt_pltfr_dtsetup_rollback(void) +{ + return; +} + +static int img_bt_pltfr_bufsetup(void) +{ + ioaddr_t tx_base, rx_base; + int result = 0; + + if (NULL == request_mem_region(xmit_buffers.phys_base, + xmit_buffers.length, client_name)) { + err("could not request memory region : %p - %p\n", + (ioaddr_t)xmit_buffers.phys_base, + (ioaddr_t)(xmit_buffers.phys_base + + xmit_buffers.length - 1)); + result = -ENOMEM; + goto request_failed; + } + + xmit_buffers.virt_base = + ioremap(xmit_buffers.phys_base, xmit_buffers.length); + + if (NULL == xmit_buffers.virt_base) { + errn("could not remap memory region : %p + %x", + (ioaddr_t)xmit_buffers.phys_base, + xmit_buffers.length); + result = -ENOMEM; + goto remap_failed; + } + + /* + * TODO: this assumes contiguous placement + */ + rx_base = (ioaddr_t)((resource_size_t)xmit_buffers.virt_base + 0); + tx_base = (ioaddr_t)((resource_size_t)xmit_buffers.virt_base + + buffer_length); + dbgn("tx buffer at : 0x%p", tx_base); + dbgn("rx buffer at : 0x%p", rx_base); + if (circ_buf_out_init(&xmit_buffers.tx, tx_base, buffer_length)) { + errn(" circular buffer init failed, size is not " + "a power of 2: %d", buffer_length); + goto buffer_alloc_failed; + } + if (circ_buf_in_init(&xmit_buffers.rx, rx_base, buffer_length)) { + errn(" circular buffer init failed, size is not " + "a power of 2: %d", buffer_length); + goto buffer_alloc_failed; + } + + result = work_depot_init(); + if (result) { + errn("workqueue init failed"); + goto work_depot_init_failed; + } + + return result; + +work_depot_init_failed: + (void)0; +buffer_alloc_failed: + (void)0; +remap_failed: + release_mem_region(xmit_buffers.phys_base, xmit_buffers.length); +request_failed: + return result; +} + +static void img_bt_pltfr_bufsetup_rollback(void) +{ + iounmap(xmit_buffers.virt_base); + release_mem_region(xmit_buffers.phys_base, xmit_buffers.length); +} + +static int img_bt_pltfr_reg_handler(unsigned int client_id) +{ + return img_transport_register_callback(handle_controller_message, + client_id); +} + +static void img_bt_pltfr_reg_handler_rollback(unsigned int client_id) +{ + img_transport_remove_callback(client_id); +} + +static int __init img_bt_pltfr_probe(struct platform_device *pdev) +{ + int result = 0; + + result = img_bt_pltfr_memsetup(); + if (result) { + err("memory setup failed\n"); + goto memsetup_failed; + } + + result = img_bt_pltfr_dtsetup(pdev); + if (result) { + err("DT setup failed\n"); + goto dtsetup_failed; + } + + result = img_bt_pltfr_bufsetup(); + if (result) { + err("buffer setup failed\n"); + goto bufsetup_failed; + } + + result = img_bt_pltfr_reg_handler(BLUETOOTH_ID); + if (result) { + err("failed to install callback in the transport interface\n"); + goto callback_regist_failed; + } + + result = gateway_init(handle_gateway_message, &pdev->dev); + if (result) { + errn("could not initialize gateway"); + goto gateway_init_failed; + } + + return result; + +gateway_init_failed: + img_bt_pltfr_reg_handler_rollback(0); +callback_regist_failed: + img_bt_pltfr_bufsetup_rollback(); +bufsetup_failed: + img_bt_pltfr_dtsetup_rollback(); +dtsetup_failed: + img_bt_pltfr_memsetup_rollback(); +memsetup_failed: + return result; +} + +static int img_bt_pltfr_remove(struct platform_device *pdev) +{ + gateway_exit(); + destroy_workqueue(img_bt_workqueue); + img_bt_pltfr_reg_handler_rollback(0); + img_bt_pltfr_bufsetup_rollback(); + img_bt_pltfr_dtsetup_rollback(); + img_bt_pltfr_memsetup_rollback(); + + return 0; +} + +static const struct of_device_id img_bt_dt_ids[] = { + { .compatible = "img,pistachio-uccp-bt" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, img_bt_dt_ids); + +struct platform_driver img_bt_driver = { + .remove = img_bt_pltfr_remove, + .driver = { + .name = "img-bt", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(img_bt_dt_ids), + }, +}; + +/* + * *** Entry and exit points *** + */ + +static int __init img_bt_init(void) +{ + return platform_driver_probe(&img_bt_driver, img_bt_pltfr_probe); +} + +static void __exit img_bt_exit(void) +{ + platform_driver_unregister(&img_bt_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bartosz Flis "); +MODULE_DESCRIPTION("Imagination Technologies Bluetooth driver - www.imgtec.com"); + +module_init(img_bt_init); +module_exit(img_bt_exit); diff --git a/drivers/bluetooth/pistachio/payload.c b/drivers/bluetooth/pistachio/payload.c new file mode 100644 index 00000000000000..c754d2fba21c87 --- /dev/null +++ b/drivers/bluetooth/pistachio/payload.c @@ -0,0 +1,162 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : payload.c + *** + *** File Description: + *** This file contains implementation of payload module, which represents + *** packets transferred between IMG Bluetooth device and Linux userspace. + *** + ****************************************************************************** + *END**************************************************************************/ + +#include +#include +#include +#include +#include + +#include "payload.h" + +#define GUARDED_ALLOC(var, alloc_f, fail_action) \ + (var = ({\ + typeof(var) ptr;\ + ptr = (alloc_f);\ + if (IS_ERR_OR_NULL(ptr)) {\ + (void)(fail_action);\ + return ptr;\ + };\ + ptr;\ + })) + +/* + * *** Private storage *** + */ +struct payload { + u8 *data; + size_t length; +}; + +/* + * *** Private API *** + */ +static struct payload *get_payload_obj(size_t length) +{ + struct payload *pld; + void *blob_start; + + blob_start = kzalloc(length + sizeof(struct payload), GFP_KERNEL); + if (IS_ERR_OR_NULL(blob_start)) + return ERR_PTR(-ENOMEM); + + pld = blob_start; + pld->data = blob_start + sizeof(*pld); + pld->length = length; + return pld; +} + +static void dispose_of_payload_obj(struct payload *pld) +{ + /* + * Note: there is no need to worry about the 'data' + * pointer, because it is allocated as a single blob, + * whose starting address is stored as pld + */ + kfree(pld); +} + +/* + * *** Public API *** + */ +struct payload *payload_from_io(size_t length, const void __iomem *data) +{ + struct payload *pld; + GUARDED_ALLOC(pld, get_payload_obj(length), NULL); + memcpy_fromio(pld->data, data, pld->length); + return pld; +} + +struct payload *payload_from_user(size_t length, const void __user *data) +{ + struct payload *pld; + GUARDED_ALLOC(pld, get_payload_obj(length), NULL); + if (copy_from_user(pld->data, data, pld->length)) { + /* + * That means that some bytes could not be copied + * and buffer had to be zero padded. + */ + dispose_of_payload_obj(pld); + return ERR_PTR(-EFAULT); + } + return pld; +} + +struct payload *payload_from_string( + size_t length, + unsigned char (*one_char)(void *, unsigned int), + void *arg) +{ + size_t p; + struct payload *pld; + if (IS_ERR_OR_NULL(one_char)) + return 0; + GUARDED_ALLOC(pld, get_payload_obj(length), + pr_err("failed to allocate payload obj\n")); + for (p = 0; p < length; p++) + pld->data[p] = one_char(arg, p); + + return pld; +} + +void payload_to_io(struct payload *pld, void __iomem *data) +{ + memcpy_toio(data, pld->data, pld->length); + dispose_of_payload_obj(pld); +} + +int payload_to_user(struct payload *pld, void __user *data) +{ + if (copy_to_user(data, pld->data, pld->length)) + return -EFAULT; + + dispose_of_payload_obj(pld); + return 0; +} + +void payload_delete(struct payload *pld) +{ + dispose_of_payload_obj(pld); +} + +const u8 *payload_raw(const struct payload *pld) +{ + return pld->data; +} + +size_t payload_length(const struct payload *pld) +{ + return pld->length; +} + +unsigned char payload_at(const struct payload *pld, unsigned int idx) +{ + return pld->data[idx]; +} diff --git a/drivers/bluetooth/pistachio/payload.h b/drivers/bluetooth/pistachio/payload.h new file mode 100644 index 00000000000000..77482f666782a2 --- /dev/null +++ b/drivers/bluetooth/pistachio/payload.h @@ -0,0 +1,58 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : payload.h + *** + *** File Description: + *** This file contains interface definition of payload module, which represents + *** packets transferred between IMG Bluetooth device and Linux userspace. + *** + ****************************************************************************** + *END**************************************************************************/ + +#ifndef __PAYLOAD_H__ +#define __PAYLOAD_H__ 1 + +#include + +struct payload; + +/* + * *** Create/teardown *** + */ +struct payload *payload_from_io(size_t length, + const void __iomem *data); +struct payload *payload_from_user(size_t length, const void __user *data); +struct payload *payload_from_raw(size_t length, const u8 *data); +struct payload *payload_from_string(size_t length, + unsigned char (*one_char)(void *, unsigned int), void *arg); +void payload_to_io(struct payload *pld, void __iomem *data); +int payload_to_user(struct payload *pld, void __user *data); +void payload_delete(struct payload *pld); + +/* + * *** Data access *** + */ +const u8 *payload_raw(const struct payload *pld); +size_t payload_length(const struct payload *pld); +unsigned char payload_at(const struct payload *pld, unsigned int idx); + +#endif /* __PAYLOAD_H__ */ diff --git a/drivers/clk/pistachio/clk-pistachio.c b/drivers/clk/pistachio/clk-pistachio.c index c4ceb5eaf46c16..7271c4eba75f71 100644 --- a/drivers/clk/pistachio/clk-pistachio.c +++ b/drivers/clk/pistachio/clk-pistachio.c @@ -44,7 +44,7 @@ static struct pistachio_gate pistachio_gates[] __initdata = { GATE(CLK_AUX_ADC_INTERNAL, "aux_adc_internal", "sys_internal_div", 0x104, 22), GATE(CLK_AUX_ADC, "aux_adc", "aux_adc_div", 0x104, 23), - GATE(CLK_SD_HOST, "sd_host", "sd_host_div", 0x104, 24), + GATE(CLK_SD_HOST, "sd_host", "sd_host_div4", 0x104, 24), GATE(CLK_BT, "bt", "bt_div", 0x104, 25), GATE(CLK_BT_DIV4, "bt_div4", "bt_div4_div", 0x104, 26), GATE(CLK_BT_DIV8, "bt_div8", "bt_div8_div", 0x104, 27), @@ -54,34 +54,36 @@ static struct pistachio_gate pistachio_gates[] __initdata = { static struct pistachio_fixed_factor pistachio_ffs[] __initdata = { FIXED_FACTOR(CLK_WIFI_DIV4, "wifi_div4", "wifi_pll", 4), FIXED_FACTOR(CLK_WIFI_DIV8, "wifi_div8", "wifi_pll", 8), + FIXED_FACTOR(CLK_SDHOST_DIV4, "sd_host_div4", "sd_host_div", 4), }; static struct pistachio_div pistachio_divs[] __initdata = { - DIV(CLK_MIPS_INTERNAL_DIV, "mips_internal_div", "mips_pll_mux", - 0x204, 2), - DIV(CLK_MIPS_DIV, "mips_div", "mips_internal_div", 0x208, 8), + DIV_F(CLK_MIPS_INTERNAL_DIV, "mips_internal_div", "mips_pll_mux", + 0x204, 2, CLK_SET_RATE_PARENT, CLK_DIVIDER_READ_ONLY), + DIV_F(CLK_MIPS_DIV, "mips_div", "mips_internal_div", + 0x208, 8, CLK_SET_RATE_PARENT, CLK_DIVIDER_READ_ONLY), DIV_F(CLK_AUDIO_DIV, "audio_div", "audio_mux", - 0x20c, 8, CLK_DIVIDER_ROUND_CLOSEST), + 0x20c, 8, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_I2S_DIV, "i2s_div", "audio_pll_mux", - 0x210, 8, CLK_DIVIDER_ROUND_CLOSEST), + 0x210, 8, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_SPDIF_DIV, "spdif_div", "audio_pll_mux", - 0x214, 8, CLK_DIVIDER_ROUND_CLOSEST), + 0x214, 8, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_AUDIO_DAC_DIV, "audio_dac_div", "audio_pll_mux", - 0x218, 8, CLK_DIVIDER_ROUND_CLOSEST), + 0x218, 8, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV(CLK_RPU_V_DIV, "rpu_v_div", "rpu_v_pll_mux", 0x21c, 2), DIV(CLK_RPU_L_DIV, "rpu_l_div", "rpu_l_mux", 0x220, 2), DIV(CLK_RPU_SLEEP_DIV, "rpu_sleep_div", "xtal", 0x224, 10), - DIV(CLK_RPU_CORE_DIV, "rpu_core_div", "rpu_core_mux", 0x228, 3), + DIV(CLK_RPU_CORE_DIV, "rpu_core_div", "rpu_core_mux_1", 0x228, 3), DIV(CLK_USB_PHY_DIV, "usb_phy_div", "sys_internal_div", 0x22c, 6), DIV(CLK_ENET_DIV, "enet_div", "enet_mux", 0x230, 6), DIV_F(CLK_UART0_INTERNAL_DIV, "uart0_internal_div", "sys_pll_mux", - 0x234, 3, CLK_DIVIDER_ROUND_CLOSEST), + 0x234, 3, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_UART0_DIV, "uart0_div", "uart0_internal_div", 0x238, 10, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_UART1_INTERNAL_DIV, "uart1_internal_div", "sys_pll_mux", - 0x23c, 3, CLK_DIVIDER_ROUND_CLOSEST), + 0x23c, 3, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(CLK_UART1_DIV, "uart1_div", "uart1_internal_div", 0x240, 10, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV(CLK_SYS_INTERNAL_DIV, "sys_internal_div", "sys_pll_mux", 0x244, 3), DIV(CLK_SPI0_INTERNAL_DIV, "spi0_internal_div", "sys_pll_mux", 0x248, 3), @@ -91,8 +93,8 @@ static struct pistachio_div pistachio_divs[] __initdata = { DIV(CLK_SPI1_DIV, "spi1_div", "spi1_internal_div", 0x254, 7), DIV(CLK_EVENT_TIMER_INTERNAL_DIV, "event_timer_internal_div", "event_timer_mux", 0x258, 3), - DIV(CLK_EVENT_TIMER_DIV, "event_timer_div", "event_timer_internal_div", - 0x25c, 12), + DIV_F(CLK_EVENT_TIMER_DIV, "event_timer_div", "event_timer_internal_div", + 0x25c, 12, CLK_SET_RATE_PARENT, 0), DIV(CLK_AUX_ADC_INTERNAL_DIV, "aux_adc_internal_div", "aux_adc_internal", 0x260, 3), DIV(CLK_AUX_ADC_DIV, "aux_adc_div", "aux_adc_internal_div", 0x264, 10), @@ -117,6 +119,7 @@ PNAME(mux_xtal_wifi_div4) = { "xtal", "wifi_div4" }; PNAME(mux_xtal_wifi_div8) = { "xtal", "wifi_div8" }; PNAME(mux_wifi_div4_rpu_l) = { "wifi_pll_gate", "wifi_div4_mux", "rpu_l_pll_mux" }; +PNAME(mux_rpu_core_1) = { "rpu_core_mux", "rpu_l_pll_mux"}; PNAME(mux_xtal_sys) = { "xtal", "sys_pll" }; PNAME(mux_sys_enet) = { "sys_internal_div", "enet_in" }; PNAME(mux_audio_sys) = { "audio_pll_mux", "sys_internal_div" }; @@ -126,7 +129,8 @@ PNAME(mux_xtal_bt) = { "xtal", "bt_pll" }; static struct pistachio_mux pistachio_muxes[] __initdata = { MUX(CLK_AUDIO_REF_MUX, "audio_refclk_mux", mux_xtal_audio_refclk, 0x200, 0), - MUX(CLK_MIPS_PLL_MUX, "mips_pll_mux", mux_xtal_mips, 0x200, 1), + MUX_F(CLK_MIPS_PLL_MUX, "mips_pll_mux", mux_xtal_mips, + 0x200, 1, CLK_SET_RATE_PARENT), MUX(CLK_AUDIO_PLL_MUX, "audio_pll_mux", mux_xtal_audio, 0x200, 2), MUX(CLK_AUDIO_MUX, "audio_mux", mux_audio_debug, 0x200, 4), MUX(CLK_RPU_V_PLL_MUX, "rpu_v_pll_mux", mux_xtal_rpu_v, 0x200, 5), @@ -136,6 +140,7 @@ static struct pistachio_mux pistachio_muxes[] __initdata = { MUX(CLK_WIFI_DIV4_MUX, "wifi_div4_mux", mux_xtal_wifi_div4, 0x200, 9), MUX(CLK_WIFI_DIV8_MUX, "wifi_div8_mux", mux_xtal_wifi_div8, 0x200, 10), MUX(CLK_RPU_CORE_MUX, "rpu_core_mux", mux_wifi_div4_rpu_l, 0x200, 11), + MUX(CLK_RPU_CORE_MUX_1, "rpu_core_mux_1", mux_rpu_core_1, 0x200, 12), MUX(CLK_SYS_PLL_MUX, "sys_pll_mux", mux_xtal_sys, 0x200, 13), MUX(CLK_ENET_MUX, "enet_mux", mux_sys_enet, 0x200, 14), MUX(CLK_EVENT_TIMER_MUX, "event_timer_mux", mux_audio_sys, 0x200, 15), @@ -143,14 +148,69 @@ static struct pistachio_mux pistachio_muxes[] __initdata = { MUX(CLK_BT_PLL_MUX, "bt_pll_mux", mux_xtal_bt, 0x200, 17), }; +static struct pistachio_pll_rate_table mips_pll_rates[] = { + INT_PLL_RATES(52000000, 208000000, 5, 40, 2, 1), + INT_PLL_RATES(52000000, 234000000, 5, 45, 2, 1), + INT_PLL_RATES(52000000, 260000000, 5, 50, 2, 1), + INT_PLL_RATES(52000000, 286000000, 5, 55, 2, 1), + INT_PLL_RATES(52000000, 312000000, 5, 60, 2, 1), + INT_PLL_RATES(52000000, 338000000, 5, 65, 2, 1), + INT_PLL_RATES(52000000, 364000000, 5, 70, 2, 1), + INT_PLL_RATES(52000000, 390000000, 5, 75, 2, 1), + INT_PLL_RATES(52000000, 416000000, 5, 80, 2, 1), + INT_PLL_RATES(52000000, 442000000, 5, 85, 2, 1), + INT_PLL_RATES(52000000, 468000000, 5, 90, 2, 1), + INT_PLL_RATES(52000000, 494000000, 5, 95, 2, 1), + INT_PLL_RATES(52000000, 520000000, 5, 100, 2, 1), + INT_PLL_RATES(52000000, 546000000, 5, 105, 2, 1), +}; + +static struct pistachio_pll_rate_table wifi_pll_rates[] = { + { + .fref = 52000000, + .fout = 320000000, + .refdiv = 0x1, + .fbdiv = 0x18, + .frac = 0x9d89d9, + .postdiv1 = 0x4, + .postdiv2 = 0x1, + }, +}; + +static struct pistachio_pll_rate_table audio_pll_rates[] = { + { + .fref = 52000000, + .fout_min = 146250000, + .fout = 147456000, + .fout_max = 149499999, + .refdiv = 0x1, + .fbdiv = 0x2d, + .frac = 0x5efee6, + .postdiv1 = 0x4, + .postdiv2 = 0x4, + }, { + .fref = 52000000, + .fout_min = 133250000, + .fout = 135475200, + .fout_max = 136500000, + .refdiv = 0x1, + .fbdiv = 0x29, + .frac = 0xaf46fd, + .postdiv1 = 0x4, + .postdiv2 = 0x4, + }, +}; + static struct pistachio_pll pistachio_plls[] __initdata = { - PLL_FIXED(CLK_MIPS_PLL, "mips_pll", "xtal", PLL_GF40LP_LAINT, 0x0), - PLL_FIXED(CLK_AUDIO_PLL, "audio_pll", "audio_refclk_mux", - PLL_GF40LP_FRAC, 0xc), + PLL(CLK_MIPS_PLL, "mips_pll", "xtal", PLL_GF40LP_LAINT, 0x0, + mips_pll_rates), + PLL(CLK_AUDIO_PLL, "audio_pll", "audio_refclk_mux", PLL_GF40LP_FRAC, + 0xc, audio_pll_rates), PLL_FIXED(CLK_RPU_V_PLL, "rpu_v_pll", "xtal", PLL_GF40LP_LAINT, 0x20), PLL_FIXED(CLK_RPU_L_PLL, "rpu_l_pll", "xtal", PLL_GF40LP_LAINT, 0x2c), PLL_FIXED(CLK_SYS_PLL, "sys_pll", "xtal", PLL_GF40LP_FRAC, 0x38), - PLL_FIXED(CLK_WIFI_PLL, "wifi_pll", "xtal", PLL_GF40LP_FRAC, 0x4c), + PLL(CLK_WIFI_PLL, "wifi_pll", "xtal", PLL_GF40LP_FRAC, + 0x4c, wifi_pll_rates), PLL_FIXED(CLK_BT_PLL, "bt_pll", "xtal", PLL_GF40LP_LAINT, 0x60), }; @@ -227,18 +287,18 @@ static struct pistachio_div pistachio_periph_divs[] __initdata = { DIV(PERIPH_CLK_ROM_DIV, "rom_div", "periph_sys", 0x10c, 7), DIV(PERIPH_CLK_COUNTER_FAST_DIV, "counter_fast_div", "periph_sys", 0x110, 7), - DIV(PERIPH_CLK_COUNTER_SLOW_PRE_DIV, "counter_slow_pre_div", - "periph_sys", 0x114, 7), - DIV(PERIPH_CLK_COUNTER_SLOW_DIV, "counter_slow_div", - "counter_slow_pre_div", 0x118, 7), + DIV_F(PERIPH_CLK_COUNTER_SLOW_PRE_DIV, "counter_slow_pre_div", + "periph_sys", 0x114, 7, 0, CLK_DIVIDER_ROUND_CLOSEST), + DIV_F(PERIPH_CLK_COUNTER_SLOW_DIV, "counter_slow_div", + "counter_slow_pre_div", 0x118, 7, 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(PERIPH_CLK_IR_PRE_DIV, "ir_pre_div", "periph_sys", 0x11c, 7, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(PERIPH_CLK_IR_DIV, "ir_div", "ir_pre_div", 0x120, 7, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(PERIPH_CLK_WD_PRE_DIV, "wd_pre_div", "periph_sys", 0x124, 7, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV_F(PERIPH_CLK_WD_DIV, "wd_div", "wd_pre_div", 0x128, 7, - CLK_DIVIDER_ROUND_CLOSEST), + 0, CLK_DIVIDER_ROUND_CLOSEST), DIV(PERIPH_CLK_PDM_PRE_DIV, "pdm_pre_div", "periph_sys", 0x12c, 7), DIV(PERIPH_CLK_PDM_DIV, "pdm_div", "pdm_pre_div", 0x130, 7), DIV(PERIPH_CLK_PWM_PRE_DIV, "pwm_pre_div", "periph_sys", 0x134, 7), diff --git a/drivers/clk/pistachio/clk-pll.c b/drivers/clk/pistachio/clk-pll.c index 7e8daab9025bd3..001ec27f96f75a 100644 --- a/drivers/clk/pistachio/clk-pll.c +++ b/drivers/clk/pistachio/clk-pll.c @@ -66,10 +66,8 @@ #define MAX_OUTPUT_FRAC 1600000000UL /* Fractional PLL operating modes */ -enum pll_mode { - PLL_MODE_FRAC, - PLL_MODE_INT, -}; +#define PLL_MODE_INT 1 +#define PLL_MODE_FRAC 0 struct pistachio_clk_pll { struct clk_hw hw; @@ -105,7 +103,7 @@ static inline struct pistachio_clk_pll *to_pistachio_pll(struct clk_hw *hw) return container_of(hw, struct pistachio_clk_pll, hw); } -static inline enum pll_mode pll_frac_get_mode(struct clk_hw *hw) +static inline u32 pll_frac_get_mode(struct clk_hw *hw) { struct pistachio_clk_pll *pll = to_pistachio_pll(hw); u32 val; @@ -114,7 +112,7 @@ static inline enum pll_mode pll_frac_get_mode(struct clk_hw *hw) return val ? PLL_MODE_INT : PLL_MODE_FRAC; } -static inline void pll_frac_set_mode(struct clk_hw *hw, enum pll_mode mode) +static inline void pll_frac_set_mode(struct clk_hw *hw, u32 mode) { struct pistachio_clk_pll *pll = to_pistachio_pll(hw); u32 val; @@ -132,29 +130,44 @@ static struct pistachio_pll_rate_table * pll_get_params(struct pistachio_clk_pll *pll, unsigned long fref, unsigned long fout) { - unsigned int i; + unsigned int i, best; + unsigned long err, best_err = ~0; for (i = 0; i < pll->nr_rates; i++) { - if (pll->rates[i].fref == fref && pll->rates[i].fout == fout) - return &pll->rates[i]; + err = abs(pll->rates[i].fout - fout); + if (pll->rates[i].fref == fref && err < best_err) { + best = i; + best_err = err; + } } - return NULL; + return &pll->rates[best]; } static long pll_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct pistachio_clk_pll *pll = to_pistachio_pll(hw); - unsigned int i; + unsigned int i, best; + unsigned long err, best_err = ~0; for (i = 0; i < pll->nr_rates; i++) { - if (i > 0 && pll->rates[i].fref == *parent_rate && - pll->rates[i].fout <= rate) - return pll->rates[i - 1].fout; + err = abs(pll->rates[i].fout - rate); + if (pll->rates[i].fref == *parent_rate && err < best_err) { + best = i; + best_err = err; + } } - return pll->rates[0].fout; + /* + * If the chosen rate is within the maximum allowed PLL adjustment + * then we accept it. + * Otherwise, just return the closest nominal table rate. + */ + if (rate <= pll->rates[best].fout_max && + rate >= pll->rates[best].fout_min) + return rate; + return pll->rates[best].fout; } static int pll_gf40lp_frac_enable(struct clk_hw *hw) @@ -199,7 +212,7 @@ static int pll_gf40lp_frac_set_rate(struct clk_hw *hw, unsigned long rate, struct pistachio_clk_pll *pll = to_pistachio_pll(hw); struct pistachio_pll_rate_table *params; int enabled = pll_gf40lp_frac_is_enabled(hw); - u64 val, vco, old_postdiv1, old_postdiv2; + u64 val, vco, old_postdiv1, old_postdiv2, frac; const char *name = clk_hw_get_name(hw); if (rate < MIN_OUTPUT_FRAC || rate > MAX_OUTPUT_FRAC) @@ -226,6 +239,11 @@ static int pll_gf40lp_frac_set_rate(struct clk_hw *hw, unsigned long rate, pr_warn("%s: PFD %llu is too high (max %llu)\n", name, val, vco / 16); + /* Calculate the frac parameter */ + frac = rate * params->refdiv * params->postdiv1 * params->postdiv2; + frac -= (params->fbdiv * parent_rate); + frac = do_div_round_closest(frac << 24, parent_rate); + val = pll_readl(pll, PLL_CTRL1); val &= ~((PLL_CTRL1_REFDIV_MASK << PLL_CTRL1_REFDIV_SHIFT) | (PLL_CTRL1_FBDIV_MASK << PLL_CTRL1_FBDIV_SHIFT)); @@ -252,7 +270,7 @@ static int pll_gf40lp_frac_set_rate(struct clk_hw *hw, unsigned long rate, PLL_FRAC_CTRL2_POSTDIV1_SHIFT) | (PLL_FRAC_CTRL2_POSTDIV2_MASK << PLL_FRAC_CTRL2_POSTDIV2_SHIFT)); - val |= (params->frac << PLL_FRAC_CTRL2_FRAC_SHIFT) | + val |= (frac << PLL_FRAC_CTRL2_FRAC_SHIFT) | (params->postdiv1 << PLL_FRAC_CTRL2_POSTDIV1_SHIFT) | (params->postdiv2 << PLL_FRAC_CTRL2_POSTDIV2_SHIFT); pll_writel(pll, val, PLL_CTRL2); diff --git a/drivers/clk/pistachio/clk.c b/drivers/clk/pistachio/clk.c index 698cad4f509e70..1b75db6aca6221 100644 --- a/drivers/clk/pistachio/clk.c +++ b/drivers/clk/pistachio/clk.c @@ -83,7 +83,7 @@ void pistachio_clk_register_mux(struct pistachio_clk_provider *p, for (i = 0; i < num; i++) { clk = clk_register_mux(NULL, mux[i].name, mux[i].parents, mux[i].num_parents, - CLK_SET_RATE_NO_REPARENT, + mux[i].clk_flags, p->base + mux[i].reg, mux[i].shift, get_count_order(mux[i].num_parents), 0, NULL); @@ -100,7 +100,8 @@ void pistachio_clk_register_div(struct pistachio_clk_provider *p, for (i = 0; i < num; i++) { clk = clk_register_divider(NULL, div[i].name, div[i].parent, - 0, p->base + div[i].reg, 0, + div[i].clk_flags, + p->base + div[i].reg, 0, div[i].width, div[i].div_flags, NULL); p->clk_data.clks[div[i].id] = clk; diff --git a/drivers/clk/pistachio/clk.h b/drivers/clk/pistachio/clk.h index 8d45178dbde3e1..1ae53c2aa349fe 100644 --- a/drivers/clk/pistachio/clk.h +++ b/drivers/clk/pistachio/clk.h @@ -32,6 +32,7 @@ struct pistachio_mux { unsigned int id; unsigned long reg; unsigned int shift; + unsigned int clk_flags; unsigned int num_parents; const char *name; const char **parents; @@ -44,16 +45,28 @@ struct pistachio_mux { .id = _id, \ .reg = _reg, \ .shift = _shift, \ + .clk_flags = CLK_SET_RATE_NO_REPARENT, \ .name = _name, \ .parents = _pnames, \ .num_parents = ARRAY_SIZE(_pnames) \ } +#define MUX_F(_id, _name, _pnames, _reg, _shift, _clkf) \ + { \ + .id = _id, \ + .reg = _reg, \ + .shift = _shift, \ + .name = _name, \ + .parents = _pnames, \ + .num_parents = ARRAY_SIZE(_pnames), \ + .clk_flags = _clkf, \ + } struct pistachio_div { unsigned int id; unsigned long reg; unsigned int width; + unsigned int clk_flags; unsigned int div_flags; const char *name; const char *parent; @@ -64,17 +77,19 @@ struct pistachio_div { .id = _id, \ .reg = _reg, \ .width = _width, \ + .clk_flags = 0, \ .div_flags = 0, \ .name = _name, \ .parent = _pname, \ } -#define DIV_F(_id, _name, _pname, _reg, _width, _div_flags) \ +#define DIV_F(_id, _name, _pname, _reg, _width, _clkf, _divf) \ { \ .id = _id, \ .reg = _reg, \ .width = _width, \ - .div_flags = _div_flags, \ + .clk_flags = _clkf, \ + .div_flags = _divf, \ .name = _name, \ .parent = _pname, \ } @@ -94,9 +109,15 @@ struct pistachio_fixed_factor { .parent = _pname, \ } +/* + * in order to avoid u32 multiplication overflow, declare all + * members of this structure as u64 + */ struct pistachio_pll_rate_table { unsigned long long fref; unsigned long long fout; + unsigned long long fout_min; + unsigned long long fout_max; unsigned long long refdiv; unsigned long long fbdiv; unsigned long long postdiv1; @@ -104,6 +125,20 @@ struct pistachio_pll_rate_table { unsigned long long frac; }; +#define INT_PLL_RATES(_fref, _fout, _refdiv, _fbdiv, \ + _postdiv1, _postdiv2) \ + { \ + .fref = _fref, \ + .fout = _fout, \ + .fout_min = _fout, \ + .fout_max = _fout, \ + .refdiv = _refdiv, \ + .fbdiv = _fbdiv, \ + .postdiv1 = _postdiv1, \ + .postdiv2 = _postdiv2, \ + .frac = 0, \ + } + enum pistachio_pll_type { PLL_GF40LP_LAINT, PLL_GF40LP_FRAC, diff --git a/drivers/clocksource/time-pistachio.c b/drivers/clocksource/time-pistachio.c index bba6799000541d..d4a74269297cae 100644 --- a/drivers/clocksource/time-pistachio.c +++ b/drivers/clocksource/time-pistachio.c @@ -27,12 +27,12 @@ /* Top level reg */ #define CR_TIMER_CTRL_CFG 0x00 -#define TIMER_ME_GLOBAL BIT(0) +#define TIMER_ME_GLOBAL BIT(0) #define CR_TIMER_REV 0x10 /* Timer specific registers */ #define TIMER_CFG 0x20 -#define TIMER_ME_LOCAL BIT(0) +#define TIMER_ME_LOCAL BIT(0) #define TIMER_RELOAD_VALUE 0x24 #define TIMER_CURRENT_VALUE 0x28 #define TIMER_CURRENT_OVERFLOW_VALUE 0x2C @@ -143,8 +143,7 @@ static struct pistachio_clocksource pcs_gpt = { .disable = pistachio_clocksource_disable, .read = pistachio_clocksource_read_cycles, .mask = CLOCKSOURCE_MASK(32), - .flags = CLOCK_SOURCE_IS_CONTINUOUS | - CLOCK_SOURCE_SUSPEND_NONSTOP, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, }, }; @@ -170,7 +169,7 @@ static void __init pistachio_clksrc_of_init(struct device_node *node) /* Switch to using the fast counter clock */ ret = regmap_update_bits(periph_regs, PERIP_TIMER_CONTROL, - 0xf, 0x0); + 0x1, 0x0); if (ret) return; diff --git a/drivers/cpufreq/cpufreq_conservative.c b/drivers/cpufreq/cpufreq_conservative.c index 1fa1deb6e91fcb..1cc62c882c991c 100644 --- a/drivers/cpufreq/cpufreq_conservative.c +++ b/drivers/cpufreq/cpufreq_conservative.c @@ -17,7 +17,7 @@ /* Conservative governor macros */ #define DEF_FREQUENCY_UP_THRESHOLD (80) #define DEF_FREQUENCY_DOWN_THRESHOLD (20) -#define DEF_FREQUENCY_STEP (5) +#define DEF_FREQUENCY_STEP (4) #define DEF_SAMPLING_DOWN_FACTOR (1) #define MAX_SAMPLING_DOWN_FACTOR (10) diff --git a/drivers/crypto/img-hash.c b/drivers/crypto/img-hash.c index 68e8aa90fe01cb..f90fdf354197c6 100644 --- a/drivers/crypto/img-hash.c +++ b/drivers/crypto/img-hash.c @@ -71,6 +71,7 @@ #define DRIVER_FLAGS_MD5 BIT(21) #define IMG_HASH_QUEUE_LENGTH 20 +#define IMG_HASH_DMA_BURST 4 #define IMG_HASH_DMA_THRESHOLD 64 #ifdef __LITTLE_ENDIAN @@ -102,8 +103,10 @@ struct img_hash_request_ctx { unsigned long op; size_t bufcnt; - u8 buffer[0] __aligned(sizeof(u32)); struct ahash_request fallback_req; + + /* Zero length buffer must remain last member of struct */ + u8 buffer[0] __aligned(sizeof(u32)); }; struct img_hash_ctx { @@ -340,7 +343,7 @@ static int img_hash_dma_init(struct img_hash_dev *hdev) dma_conf.direction = DMA_MEM_TO_DEV; dma_conf.dst_addr = hdev->bus_addr; dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - dma_conf.dst_maxburst = 16; + dma_conf.dst_maxburst = IMG_HASH_DMA_BURST; dma_conf.device_fc = false; err = dmaengine_slave_config(hdev->dma_lch, &dma_conf); @@ -361,7 +364,7 @@ static void img_hash_dma_task(unsigned long d) size_t nbytes, bleft, wsend, len, tbc; struct scatterlist tsg; - if (!ctx->sg) + if (!ctx || !ctx->sg) return; addr = sg_virt(ctx->sg); @@ -587,6 +590,32 @@ static int img_hash_finup(struct ahash_request *req) return crypto_ahash_finup(&rctx->fallback_req); } +static int img_hash_import(struct ahash_request *req, const void *in) +{ + struct img_hash_request_ctx *rctx = ahash_request_ctx(req); + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct img_hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ahash_request_set_tfm(&rctx->fallback_req, ctx->fallback); + rctx->fallback_req.base.flags = req->base.flags + & CRYPTO_TFM_REQ_MAY_SLEEP; + + return crypto_ahash_import(&rctx->fallback_req, in); +} + +static int img_hash_export(struct ahash_request *req, void *out) +{ + struct img_hash_request_ctx *rctx = ahash_request_ctx(req); + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct img_hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ahash_request_set_tfm(&rctx->fallback_req, ctx->fallback); + rctx->fallback_req.base.flags = req->base.flags + & CRYPTO_TFM_REQ_MAY_SLEEP; + + return crypto_ahash_export(&rctx->fallback_req, out); +} + static int img_hash_digest(struct ahash_request *req) { struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); @@ -711,9 +740,12 @@ static struct ahash_alg img_algs[] = { .update = img_hash_update, .final = img_hash_final, .finup = img_hash_finup, + .export = img_hash_export, + .import = img_hash_import, .digest = img_hash_digest, .halg = { .digestsize = MD5_DIGEST_SIZE, + .statesize = sizeof(struct md5_state), .base = { .cra_name = "md5", .cra_driver_name = "img-md5", @@ -734,9 +766,12 @@ static struct ahash_alg img_algs[] = { .update = img_hash_update, .final = img_hash_final, .finup = img_hash_finup, + .export = img_hash_export, + .import = img_hash_import, .digest = img_hash_digest, .halg = { .digestsize = SHA1_DIGEST_SIZE, + .statesize = sizeof(struct sha1_state), .base = { .cra_name = "sha1", .cra_driver_name = "img-sha1", @@ -757,9 +792,12 @@ static struct ahash_alg img_algs[] = { .update = img_hash_update, .final = img_hash_final, .finup = img_hash_finup, + .export = img_hash_export, + .import = img_hash_import, .digest = img_hash_digest, .halg = { .digestsize = SHA224_DIGEST_SIZE, + .statesize = sizeof(struct sha256_state), .base = { .cra_name = "sha224", .cra_driver_name = "img-sha224", @@ -780,9 +818,12 @@ static struct ahash_alg img_algs[] = { .update = img_hash_update, .final = img_hash_final, .finup = img_hash_finup, + .export = img_hash_export, + .import = img_hash_import, .digest = img_hash_digest, .halg = { .digestsize = SHA256_DIGEST_SIZE, + .statesize = sizeof(struct sha256_state), .base = { .cra_name = "sha256", .cra_driver_name = "img-sha256", @@ -971,7 +1012,7 @@ static int img_hash_probe(struct platform_device *pdev) err = img_register_algs(hdev); if (err) goto err_algs; - dev_dbg(dev, "Img MD5/SHA1/SHA224/SHA256 Hardware accelerator initialized\n"); + dev_info(dev, "Img MD5/SHA1/SHA224/SHA256 Hardware accelerator initialized\n"); return 0; @@ -1013,11 +1054,38 @@ static int img_hash_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int img_hash_suspend(struct device *dev) +{ + struct img_hash_dev *hdev = dev_get_drvdata(dev); + + clk_disable_unprepare(hdev->hash_clk); + clk_disable_unprepare(hdev->sys_clk); + + return 0; +} + +static int img_hash_resume(struct device *dev) +{ + struct img_hash_dev *hdev = dev_get_drvdata(dev); + + clk_prepare_enable(hdev->hash_clk); + clk_prepare_enable(hdev->sys_clk); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops img_hash_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(img_hash_suspend, img_hash_resume) +}; + static struct platform_driver img_hash_driver = { .probe = img_hash_probe, .remove = img_hash_remove, .driver = { .name = "img-hash-accelerator", + .pm = &img_hash_pm_ops, .of_match_table = of_match_ptr(img_hash_match), } }; diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c index 9ca56830cc63d6..d9d9916cfad80e 100644 --- a/drivers/dma/img-mdc-dma.c +++ b/drivers/dma/img-mdc-dma.c @@ -623,25 +623,33 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan, (MDC_CMDS_PROCESSED_CMDS_DONE_MASK + 1); /* - * If the command loaded event hasn't been processed yet, then - * the difference above includes an extra command. + * If the first node has not yet been read from memory, + * the residue register value is undefined */ - if (!mdesc->cmd_loaded) - cmds--; - else - cmds += mdesc->list_cmds_done; - - bytes = mdesc->list_xfer_size; - ldesc = mdesc->list; - for (i = 0; i < cmds; i++) { - bytes -= ldesc->xfer_size + 1; - ldesc = ldesc->next_desc; - } - if (ldesc) { - if (residue != MDC_TRANSFER_SIZE_MASK) - bytes -= ldesc->xfer_size - residue; + if (!mdesc->cmd_loaded && !cmds) { + bytes = mdesc->list_xfer_size; + } else { + /* + * If the command loaded event hasn't been processed yet, then + * the difference above includes an extra command. + */ + if (!mdesc->cmd_loaded) + cmds--; else + cmds += mdesc->list_cmds_done; + + bytes = mdesc->list_xfer_size; + ldesc = mdesc->list; + for (i = 0; i < cmds; i++) { bytes -= ldesc->xfer_size + 1; + ldesc = ldesc->next_desc; + } + if (ldesc) { + if (residue != MDC_TRANSFER_SIZE_MASK) + bytes -= ldesc->xfer_size - residue; + else + bytes -= ldesc->xfer_size + 1; + } } } spin_unlock_irqrestore(&mchan->vc.lock, flags); @@ -651,6 +659,42 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan, return ret; } +static unsigned int mdc_get_new_events(struct mdc_chan *mchan) +{ + u32 val, processed, done1, done2; + unsigned int ret; + + val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); + processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) & + MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK; + /* + * CMDS_DONE may have incremented between reading CMDS_PROCESSED + * and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we + * didn't miss a command completion. + */ + do { + val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); + done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & + MDC_CMDS_PROCESSED_CMDS_DONE_MASK; + val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK << + MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) | + MDC_CMDS_PROCESSED_INT_ACTIVE); + val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT; + mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED); + val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); + done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & + MDC_CMDS_PROCESSED_CMDS_DONE_MASK; + } while (done1 != done2); + + if (done1 >= processed) + ret = done1 - processed; + else + ret = ((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1) - + processed) + done1; + + return ret; +} + static int mdc_terminate_all(struct dma_chan *chan) { struct mdc_chan *mchan = to_mdc_chan(chan); @@ -667,6 +711,8 @@ static int mdc_terminate_all(struct dma_chan *chan) mchan->desc = NULL; vchan_get_all_descriptors(&mchan->vc, &head); + mdc_get_new_events(mchan); + spin_unlock_irqrestore(&mchan->vc.lock, flags); if (mdesc) @@ -703,35 +749,17 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) { struct mdc_chan *mchan = (struct mdc_chan *)dev_id; struct mdc_tx_desc *mdesc; - u32 val, processed, done1, done2; - unsigned int i; + unsigned int i, new_events; spin_lock(&mchan->vc.lock); - val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); - processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) & - MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK; - /* - * CMDS_DONE may have incremented between reading CMDS_PROCESSED - * and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we - * didn't miss a command completion. - */ - do { - val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); - done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & - MDC_CMDS_PROCESSED_CMDS_DONE_MASK; - val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK << - MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) | - MDC_CMDS_PROCESSED_INT_ACTIVE); - val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT; - mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED); - val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); - done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & - MDC_CMDS_PROCESSED_CMDS_DONE_MASK; - } while (done1 != done2); - dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr); + new_events = mdc_get_new_events(mchan); + + if (!new_events) + goto out; + mdesc = mchan->desc; if (!mdesc) { dev_warn(mdma2dev(mchan->mdma), @@ -740,8 +768,7 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) goto out; } - for (i = processed; i != done1; - i = (i + 1) % (MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1)) { + for (i = 0; i < new_events; i++) { /* * The first interrupt in a transfer indicates that the * command list has been loaded, not that a command has @@ -990,9 +1017,35 @@ static int mdc_dma_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int img_mdc_suspend(struct device *dev) +{ + struct mdc_dma *mdma = dev_get_drvdata(dev); + + clk_disable_unprepare(mdma->clk); + + return 0; +} + +static int img_mdc_resume(struct device *dev) +{ + struct mdc_dma *mdma = dev_get_drvdata(dev); + int ret = 0; + + ret = clk_prepare_enable(mdma->clk); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops img_mdc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(img_mdc_suspend, img_mdc_resume) +}; + static struct platform_driver mdc_dma_driver = { .driver = { .name = "img-mdc-dma", + .pm = &img_mdc_pm_ops, .of_match_table = of_match_ptr(mdc_dma_of_match), }, .probe = mdc_dma_probe, diff --git a/drivers/i2c/busses/i2c-img-scb.c b/drivers/i2c/busses/i2c-img-scb.c index 3795fe130ef27e..810efccd5b5532 100644 --- a/drivers/i2c/busses/i2c-img-scb.c +++ b/drivers/i2c/busses/i2c-img-scb.c @@ -151,10 +151,11 @@ #define INT_FIFO_EMPTYING BIT(12) #define INT_TRANSACTION_DONE BIT(15) #define INT_SLAVE_EVENT BIT(16) +#define INT_MASTER_HALTED BIT(17) #define INT_TIMING BIT(18) +#define INT_STOP_DETECTED BIT(19) #define INT_FIFO_FULL_FILLING (INT_FIFO_FULL | INT_FIFO_FILLING) -#define INT_FIFO_EMPTY_EMPTYING (INT_FIFO_EMPTY | INT_FIFO_EMPTYING) /* Level interrupts need clearing after handling instead of before */ #define INT_LEVEL 0x01e00 @@ -177,7 +178,8 @@ INT_FIFO_FULL | \ INT_FIFO_FILLING | \ INT_FIFO_EMPTY | \ - INT_FIFO_EMPTYING) + INT_MASTER_HALTED | \ + INT_STOP_DETECTED) #define INT_ENABLE_MASK_WAITSTOP (INT_SLAVE_EVENT | \ INT_ADDR_ACK_ERR | \ @@ -511,7 +513,17 @@ static void img_i2c_soft_reset(struct img_i2c *i2c) SCB_CONTROL_CLK_ENABLE | SCB_CONTROL_SOFT_RESET); } -/* enable or release transaction halt for control of repeated starts */ +/* + * Enable or release transaction halt for control of repeated starts. + * In version 3.3 of the IP when transaction halt is set, an interrupt + * will be generated after each byte of a transfer instead of after + * every transfer but before the stop bit. + * Due to this behaviour we have to be careful that every time we + * release the transaction halt we have to re-enable it straight away + * so that we only process a single byte, not doing so will result in + * all remaining bytes been processed and a stop bit being issued, + * which will prevent us having a repeated start. + */ static void img_i2c_transaction_halt(struct img_i2c *i2c, bool t_halt) { u32 val; @@ -580,7 +592,6 @@ static void img_i2c_read(struct img_i2c *i2c) img_i2c_writel(i2c, SCB_READ_ADDR_REG, i2c->msg.addr); img_i2c_writel(i2c, SCB_READ_COUNT_REG, i2c->msg.len); - img_i2c_transaction_halt(i2c, false); mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); } @@ -594,7 +605,6 @@ static void img_i2c_write(struct img_i2c *i2c) img_i2c_writel(i2c, SCB_WRITE_ADDR_REG, i2c->msg.addr); img_i2c_writel(i2c, SCB_WRITE_COUNT_REG, i2c->msg.len); - img_i2c_transaction_halt(i2c, false); mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); img_i2c_write_fifo(i2c); @@ -750,7 +760,9 @@ static unsigned int img_i2c_atomic(struct img_i2c *i2c, next_cmd = CMD_RET_ACK; break; case CMD_RET_ACK: - if (i2c->line_status & LINESTAT_ACK_DET) { + if (i2c->line_status & LINESTAT_ACK_DET || + (i2c->line_status & LINESTAT_NACK_DET && + i2c->msg.flags & I2C_M_IGNORE_NAK)) { if (i2c->msg.len == 0) { next_cmd = CMD_GEN_STOP; } else if (i2c->msg.flags & I2C_M_RD) { @@ -858,34 +870,42 @@ static unsigned int img_i2c_auto(struct img_i2c *i2c, /* Enable transaction halt on start bit */ if (!i2c->last_msg && line_status & LINESTAT_START_BIT_DET) { - img_i2c_transaction_halt(i2c, true); + img_i2c_transaction_halt(i2c, !i2c->last_msg); /* we're no longer interested in the slave event */ i2c->int_enable &= ~INT_SLAVE_EVENT; } mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); + if (int_status & INT_STOP_DETECTED) { + /* Drain remaining data in FIFO and complete transaction */ + if (i2c->msg.flags & I2C_M_RD) + img_i2c_read_fifo(i2c); + return ISR_COMPLETE(0); + } + if (i2c->msg.flags & I2C_M_RD) { - if (int_status & INT_FIFO_FULL_FILLING) { + if (int_status & (INT_FIFO_FULL_FILLING | INT_MASTER_HALTED)) { img_i2c_read_fifo(i2c); if (i2c->msg.len == 0) return ISR_WAITSTOP; } } else { - if (int_status & INT_FIFO_EMPTY_EMPTYING) { - /* - * The write fifo empty indicates that we're in the - * last byte so it's safe to start a new write - * transaction without losing any bytes from the - * previous one. - * see 2.3.7 Repeated Start Transactions. - */ + if (int_status & (INT_FIFO_EMPTY | INT_MASTER_HALTED)) { if ((int_status & INT_FIFO_EMPTY) && i2c->msg.len == 0) return ISR_WAITSTOP; img_i2c_write_fifo(i2c); } } + if (int_status & INT_MASTER_HALTED) { + /* + * Release and then enable transaction halt, to + * allow only a single byte to proceed. + */ + img_i2c_transaction_halt(i2c, false); + img_i2c_transaction_halt(i2c, !i2c->last_msg); + } return 0; } @@ -1017,20 +1037,23 @@ static int img_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, return -EIO; for (i = 0; i < num; i++) { - if (likely(msgs[i].len)) - continue; /* * 0 byte reads are not possible because the slave could try * and pull the data line low, preventing a stop bit. */ - if (unlikely(msgs[i].flags & I2C_M_RD)) + if (!msgs[i].len && msgs[i].flags & I2C_M_RD) return -EIO; /* * 0 byte writes are possible and used for probing, but we * cannot do them in automatic mode, so use atomic mode * instead. + * + * Also, the I2C_M_IGNORE_NAK mode can only be implemented + * in atomic mode. */ - atomic = true; + if (!msgs[i].len || + (msgs[i].flags & I2C_M_IGNORE_NAK)) + atomic = true; } ret = clk_prepare_enable(i2c->scb_clk); @@ -1069,12 +1092,31 @@ static int img_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, img_i2c_writel(i2c, SCB_INT_CLEAR_REG, ~0); img_i2c_writel(i2c, SCB_CLEAR_REG, ~0); - if (atomic) + if (atomic) { img_i2c_atomic_start(i2c); - else if (msg->flags & I2C_M_RD) - img_i2c_read(i2c); - else - img_i2c_write(i2c); + } else { + /* + * Enable transaction halt if not the last message in + * the queue so that we can control repeated starts. + */ + img_i2c_transaction_halt(i2c, !i2c->last_msg); + + if (msg->flags & I2C_M_RD) + img_i2c_read(i2c); + else + img_i2c_write(i2c); + + /* + * Release and then enable transaction halt, to + * allow only a single byte to proceed. + * This doesn't have an effect on the initial transfer + * but will allow the following transfers to start + * processing if the previous transfer was marked as + * complete while the i2c block was halted. + */ + img_i2c_transaction_halt(i2c, false); + img_i2c_transaction_halt(i2c, !i2c->last_msg); + } spin_unlock_irqrestore(&i2c->lock, flags); time_left = wait_for_completion_timeout(&i2c->msg_complete, @@ -1126,7 +1168,7 @@ static int img_i2c_init(struct img_i2c *i2c) clk_disable_unprepare(i2c->scb_clk); return -EINVAL; } - + /* Fencing enabled by default. */ i2c->need_wr_rd_fence = true; diff --git a/drivers/iio/adc/cc10001_adc.c b/drivers/iio/adc/cc10001_adc.c index 8254f529b2a9a0..a3b9fec432cc7d 100644 --- a/drivers/iio/adc/cc10001_adc.c +++ b/drivers/iio/adc/cc10001_adc.c @@ -426,6 +426,33 @@ static int cc10001_adc_remove(struct platform_device *pdev) return 0; } + +#ifdef CONFIG_PM_SLEEP +static int cc10001_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + clk_disable(adc_dev->adc_clk); + + return 0; +} + +static int cc10001_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + clk_enable(adc_dev->adc_clk); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops cc10001_adc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cc10001_adc_suspend, cc10001_adc_resume) +}; + static const struct of_device_id cc10001_adc_dt_ids[] = { { .compatible = "cosmic,10001-adc", }, { } @@ -435,6 +462,7 @@ MODULE_DEVICE_TABLE(of, cc10001_adc_dt_ids); static struct platform_driver cc10001_adc_driver = { .driver = { .name = "cc10001-adc", + .pm = &cc10001_adc_pm_ops, .of_match_table = cc10001_adc_dt_ids, }, .probe = cc10001_adc_probe, diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 22892c701c63b8..3da412e9959ef4 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -525,6 +525,36 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config IMG_PDM + tristate "Imagination Technologies PDM driver" + depends on HAS_IOMEM + depends on MFD_SYSCON + depends on COMMON_CLK + depends on MIPS || COMPILE_TEST + help + PDM driver for Imagination Technologies PDM block which supports 4 + channels. + + To compile this driver as a module, choose M here: the module will + be called img-pdm. + +config UBOOT_BOOTCOUNT + tristate "U-Boot Bootcount driver" + depends on OF + help + The U-Boot Bootcount driver allows to access the + bootcounter through sysfs file. + +config IMG_SCRATCHPAD + tristate "Imagination Technologies scratchpad driver" + depends on OF + depends on HAS_IOMEM + depends on MFD_SYSCON + depends on COMMON_CLK + depends on MIPS || COMPILE_TEST + help + Provide sysfs read/write access to scratchpad registers. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -537,4 +567,5 @@ source "drivers/misc/mic/Kconfig" source "drivers/misc/genwqe/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" +source "drivers/misc/atu/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3b78da9a..bbf498ed404e1b 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o +obj-$(CONFIG_UBOOT_BOOTCOUNT) += uboot_bootcount.o obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ @@ -56,3 +57,6 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_ATU) += atu/ +obj-$(CONFIG_IMG_PDM) += img-pdm.o +obj-$(CONFIG_IMG_SCRATCHPAD) += img-scratchpad.o diff --git a/drivers/misc/atu/Kconfig b/drivers/misc/atu/Kconfig new file mode 100644 index 00000000000000..90833038f6c3f0 --- /dev/null +++ b/drivers/misc/atu/Kconfig @@ -0,0 +1,5 @@ +config ATU + tristate "ATU Clock IP" + ---help--- + Say Y here if you want to use ATU Clock IP. + If you select to build as a module it will be called atu_clk_driver. diff --git a/drivers/misc/atu/Makefile b/drivers/misc/atu/Makefile new file mode 100644 index 00000000000000..8f0ceadaec0aa7 --- /dev/null +++ b/drivers/misc/atu/Makefile @@ -0,0 +1,2 @@ +atu_clk_driver-objs := atu_clk_maintainer.o atu_clk_ntp.o +obj-$(CONFIG_ATU) += atu_clk_driver.o diff --git a/drivers/misc/atu/atu_clk_maintainer.c b/drivers/misc/atu/atu_clk_maintainer.c new file mode 100644 index 00000000000000..060cce78050655 --- /dev/null +++ b/drivers/misc/atu/atu_clk_maintainer.c @@ -0,0 +1,1024 @@ +/* + * Atu Clock Maintainer + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "atu_clk_maintainer.h" +#include "atu_clk_ntp.h" + +static int atu_rate_changed; +#define ATU_UPDATE_TIMER_INTRVL 1 +#define ATU_MODE_ON_THE_FLY 0x80000000 +#define EVENT_TIMER_RATE_TOLERANCE 512000 + +/* Structure holding internal clk managing values. */ +struct atu_clk_maintainer { + struct timespec atu_time; + struct timecounter atu_timecntr; + int shift; + u32 mult; + cycle_t clk_cycles_per_ntp_cycle; + cycle_t shifted_ns_per_ntp_cycle; + s64 shifted_remain_ns_per_ntp_cycle; + /* NTP_SCALE_SHIFT bit shfted error */ + s64 tm_error; + /* + * Error shift with respect to current clock's + * shift i.e (NTP_SCALE_SHIFT - clock shift) + */ + int tm_error_shift; + /* Char device which holds IOCTL handler */ + struct miscdevice miscdev; + /* + * The timer structure used to update the elapsed ticks count + * at regular interval + */ + struct timer_list atu_timer; + unsigned long atu_timer_data; + struct atu_clk_ntp atu_ntp; + struct clk *clk_atu; + spinlock_t atu_clk_lock; + int event_timer_rate; + struct notifier_block atu_clk_notifier; + atomic_t last_ppb; +}; + +static void atu_time_update(void); +static int atu_adjtimex(struct timex *txc); + +static struct atu_clk_maintainer *patu_clk_mtner; + +static u64 do_div_round_closest(u64 numerator, u32 denominator) +{ + u64 result = 0; + + result = denominator >> 1; + result += numerator; + do_div(result, denominator); + + return result; +} + +/* + * This function updates ATU wall time and + * reschedules for next ATU wall time update. + */ +static void atu_timer_timeout(unsigned long dat) +{ + atu_time_update(); + add_timer(&patu_clk_mtner->atu_timer); +} + +/* This function initialises atu wall time update scheduler */ +static void atu_timer_init(void) +{ + init_timer(&patu_clk_mtner->atu_timer); + patu_clk_mtner->atu_timer.expires = jiffies + ATU_UPDATE_TIMER_INTRVL; + patu_clk_mtner->atu_timer.data = + (unsigned long)&patu_clk_mtner->atu_timer_data; + patu_clk_mtner->atu_timer.function = atu_timer_timeout; + + add_timer(&patu_clk_mtner->atu_timer); + pr_debug("ATU Wall Time scheduler started\n"); +} + +static void atu_timer_exit(void) +{ + del_timer_sync(&patu_clk_mtner->atu_timer); + pr_debug("ATU Wall Time scheduler removed\n"); +} + +static int atu_gettimestamp(struct atu_event *event) +{ + struct timespec timeofday; + unsigned long flags; + + if (event->counter < ATU_MAX_COUNTERS) { + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, + flags); + return -EFAULT; + } + event->timestamp_counter = + patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + + event->timestamp = 0x0; + event->timekeeping_shift = patu_clk_mtner->shift; + event->timekeeping_mult = patu_clk_mtner->mult; + atu_getnstimeofday(&timeofday); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + event->timeofday_sec = timeofday.tv_sec; + event->timeofday_ns = timeofday.tv_nsec; + + return 0; + } else { + return -ERANGE; + } +} + +static void set_frac_pll_adj_freq(int freq) +{ + atomic_set(&patu_clk_mtner->last_ppb, freq); +} + +int get_frac_pll_adj_freq(void) +{ + return atomic_read(&patu_clk_mtner->last_ppb); +} +EXPORT_SYMBOL(get_frac_pll_adj_freq); + +static long +ioctl_img_atu(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct atu_event event; + int ret; + struct timex u_txc; + struct timeval u_tv; + struct timespec u_ts; + unsigned long flags; + + if (!argp) + return -EINVAL; + + switch (cmd) { + default: + return -EINVAL; + + case ATUIO_GETEVTS: + if (copy_from_user(&event, argp, sizeof(event))) + return -EFAULT; + + ret = atu_gettimestamp(&event); + if (ret) + return ret; + + if (copy_to_user(argp, &event, sizeof(event))) + return -EFAULT; + break; + + case ATUIO_ADJTIMEX: + if (copy_from_user(&u_txc, argp, sizeof(u_txc))) + return -EFAULT; + + if (!u_txc.modes) { + struct timespec timeofday; + struct timespec sys_timeofday; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore( + &patu_clk_mtner->atu_clk_lock, flags); + return -EFAULT; + } + + /* check for status bit to get clock times */ + if (u_txc.status) { + atu_getnstimeofday(&timeofday); + getnstimeofday(&sys_timeofday); + u_txc.status = 0; + u_txc.time.tv_sec = timeofday.tv_sec; + u_txc.time.tv_usec = timeofday.tv_nsec; + u_txc.maxerror = sys_timeofday.tv_sec; + u_txc.esterror = sys_timeofday.tv_nsec; + ret = 0; + } else { + /* fill ppb and rate change event */ + if (u_txc.freq) { + u_txc.freq = get_frac_pll_adj_freq(); + u_txc.tick = patu_clk_mtner->event_timer_rate; + } else if (atu_rate_changed) { + u_txc.status = 1; + atu_rate_changed = 0; + } + ret = 0; + } + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, + flags); + } else { + ret = atu_adjtimex(&u_txc); + } + + if (copy_to_user(argp, &u_txc, sizeof(u_txc))) + return -EFAULT; + + if (ret) + return ret; + break; + + case ATUIO_SETTIMEOFDAY: + if (copy_from_user(&u_tv, argp, sizeof(u_tv))) + return -EFAULT; + u_ts.tv_sec = u_tv.tv_sec; + u_ts.tv_nsec = u_tv.tv_usec * NSEC_PER_USEC; + ret = atu_settimeofday(&u_ts); + if (ret) + return ret; + break; + + case ATUIO_GETTIMESPEC: + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, + flags); + return -EFAULT; + } + atu_getnstimeofday(&u_ts); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + if (copy_to_user(argp, &u_ts, sizeof(u_ts))) + return -EFAULT; + break; + } + + return 0; +} + +static const struct file_operations atu_dev_fops = { + .unlocked_ioctl = ioctl_img_atu, +}; + +static int atu_chardev_init(void) +{ + int error; + + patu_clk_mtner->miscdev.minor = MISC_DYNAMIC_MINOR; + patu_clk_mtner->miscdev.fops = &atu_dev_fops; + patu_clk_mtner->miscdev.name = "img-atu"; + error = misc_register(&patu_clk_mtner->miscdev); + if (error) { + pr_err("Unable to register atu device\n"); + goto err_misc_reg; + } + pr_debug("ATU Clock dev added\n"); + + return 0; + +err_misc_reg: + return error; +} + +static int atu_chardev_remove(void) +{ + misc_deregister(&patu_clk_mtner->miscdev); + pr_debug("ATU Clock dev removed\n"); + + return 0; +} + +static void +atu_clk_mtner_setup_internals(struct cyclecounter *patu_cyclecntr, + struct clk *clk_atu) +{ + cycle_t clk_cycle; + u64 tmp, ntp_ns_per_cycle; + + patu_clk_mtner->atu_timecntr.cc = patu_cyclecntr; + patu_clk_mtner->clk_atu = clk_atu; + + /* Clear the error */ + patu_clk_mtner->tm_error = 0; + patu_clk_mtner->tm_error_shift = NTP_SCALE_SHIFT - + patu_clk_mtner->atu_timecntr.cc->shift; + + tmp = NTP_INTERVAL_LENGTH; + tmp <<= patu_clk_mtner->atu_timecntr.cc->shift; + ntp_ns_per_cycle = tmp; + + tmp = do_div_round_closest(tmp, patu_clk_mtner->atu_timecntr.cc->mult); + if (tmp == 0) + tmp = 1; + + clk_cycle = (cycle_t)tmp; + patu_clk_mtner->clk_cycles_per_ntp_cycle = clk_cycle; + + patu_clk_mtner->shifted_ns_per_ntp_cycle = + (u64)clk_cycle * patu_clk_mtner->atu_timecntr.cc->mult; + patu_clk_mtner->shifted_remain_ns_per_ntp_cycle = + ntp_ns_per_cycle - patu_clk_mtner->shifted_ns_per_ntp_cycle; + + patu_clk_mtner->shift = patu_clk_mtner->atu_timecntr.cc->shift; + + patu_clk_mtner->mult = patu_clk_mtner->atu_timecntr.cc->mult; + + /* Update cycle_last with current read value */ + patu_clk_mtner->atu_timecntr.cycle_last = patu_clk_mtner-> + atu_timecntr.cc->read(patu_clk_mtner->atu_timecntr.cc); +} + +static s64 atu_tm_get_ns(void) +{ + cycle_t ticks_now, ticks_delta; + + /* Read present clock ticks */ + ticks_now = patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + + /* Calculate the ticks delta since the last atu_update_clk_time: */ + ticks_delta = (ticks_now - patu_clk_mtner->atu_timecntr.cycle_last) & + patu_clk_mtner->atu_timecntr.cc->mask; + + /* Convert to nanoseconds */ + return clocksource_cyc2ns(ticks_delta, patu_clk_mtner->mult, + patu_clk_mtner->shift); +} + +/* It will update the latest clock's tick to the clk time */ +static void atu_time_refresh(void) +{ + cycle_t cycle_now, cycle_delta; + s64 nsec; + + cycle_now = patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + + cycle_delta = (cycle_now - patu_clk_mtner->atu_timecntr.cycle_last) & + patu_clk_mtner->atu_timecntr.cc->mask; + patu_clk_mtner->atu_timecntr.cycle_last = cycle_now; + + nsec = clocksource_cyc2ns(cycle_delta, patu_clk_mtner->mult, + patu_clk_mtner->shift); + + timespec_add_ns(&patu_clk_mtner->atu_time, nsec); +} + +void atu_getnstimeofday(struct timespec *ts) +{ + s64 nsecs; + + if (!ts) + return; + *ts = patu_clk_mtner->atu_time; + nsecs = atu_tm_get_ns(); + timespec_add_ns(ts, nsecs); +} + +u64 atu_get_current_time(void) +{ + u64 nsecs; + struct timespec ts; + unsigned long flags; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return -EFAULT; + } + ts = patu_clk_mtner->atu_time; + nsecs = atu_tm_get_ns(); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + nsecs += ((u64)ts.tv_sec * NSEC_PER_SEC) + ((u64)ts.tv_nsec); + + return nsecs; +} +EXPORT_SYMBOL(atu_get_current_time); + +int atu_settimeofday(const struct timespec *ts) +{ + unsigned long flags; + + if (ts->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return -EFAULT; + } + + /* + * Refresh the time so that it will update + * clock's last cycle to present value + */ + atu_time_refresh(); + + patu_clk_mtner->atu_time = *ts; + + patu_clk_mtner->tm_error = 0; + atu_ntp_reset(&patu_clk_mtner->atu_ntp); + + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + return 0; +} + +int atu_tm_add_offset(struct timespec *ts) +{ + if (ts->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + atu_time_refresh(); + + patu_clk_mtner->atu_time = timespec_add(patu_clk_mtner->atu_time, *ts); + + patu_clk_mtner->tm_error = 0; + + atu_ntp_reset(&patu_clk_mtner->atu_ntp); + + return 0; +} + +unsigned long atu_get_seconds(void) +{ + return patu_clk_mtner->atu_time.tv_sec; +} + +static void frc_ticks_to_atu_units(u32 frccnt, u64 *patu) +{ + *patu = ((__u64)frccnt * patu_clk_mtner->mult) >> patu_clk_mtner->shift; +} + +static void atu_units_to_frc_ticks(u64 atu, u64 *pfrccnt) +{ + *pfrccnt = do_div_round_closest(atu << + patu_clk_mtner->shift, patu_clk_mtner->mult); +} + +static void atu_get_cur_atu_frc_pair(u64 *patu, u32 *pfrc) +{ + u64 nsecs; + struct timespec ts; + cycle_t ticks_now, ticks_delta; + + ts = patu_clk_mtner->atu_time; + + /* Read present clock ticks */ + ticks_now = patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + ticks_now = ticks_now & patu_clk_mtner->atu_timecntr.cc->mask; + + /* Calculate the ticks delta since the last atu_update_clk_time: */ + ticks_delta = (ticks_now - patu_clk_mtner->atu_timecntr.cycle_last) & + patu_clk_mtner->atu_timecntr.cc->mask; + + /* Convert to nanoseconds */ + nsecs = clocksource_cyc2ns(ticks_delta, patu_clk_mtner->mult, + patu_clk_mtner->shift); + + nsecs += ((u64)ts.tv_sec * NSEC_PER_SEC) + ((u64)ts.tv_nsec); + + *patu = nsecs; + *pfrc = ticks_now; +} + +int frc_to_atu(u32 frc, u64 *patu, s32 dir) +{ + u32 diff = 0, cur_frc; + u64 cur_atu; + unsigned long flags; + + if (!patu) + return -EINVAL; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return -EFAULT; + } + + /* Get current atu and frc count */ + atu_get_cur_atu_frc_pair(&cur_atu, &cur_frc); + + if (cur_frc >= frc) + diff = cur_frc - frc; + else + diff = patu_clk_mtner->atu_timecntr.cc->mask + + 1 + cur_frc - frc; + frc_ticks_to_atu_units(diff, patu); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + if (dir == ATU_PAST) { + *patu = cur_atu - *patu; + if (*patu < 0) + return -ERANGE; + } else { + *patu = cur_atu + *patu; + } + + return 0; +} +EXPORT_SYMBOL(frc_to_atu); + +int atu_to_frc(u64 atu, u32 *pfrc, u64 min_nsec) +{ + u64 cur_atu, atu_diff, frc_cnt; + u32 cur_frc; + unsigned long flags; + + if (!pfrc) + return -EINVAL; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return -EFAULT; + } + + /* Get current atu and frc count */ + atu_get_cur_atu_frc_pair(&cur_atu, &cur_frc); + + atu_diff = atu - cur_atu; + + /* Check for past time and min time diff */ + if (atu < cur_atu || atu_diff < min_nsec) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return -ERANGE; + } + + atu_units_to_frc_ticks(atu_diff, &frc_cnt); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + /* Check for obtained value is not more than counter rollover value */ + if (frc_cnt > patu_clk_mtner->atu_timecntr.cc->mask) + return -ERANGE; + + /* Get cycle count val at the given atu time */ + *pfrc = (frc_cnt + cur_frc) & + patu_clk_mtner->atu_timecntr.cc->mask; + + /* Check with latest time for min time diff */ + if ((atu - min_nsec) < atu_get_current_time()) + return -ERANGE; + + return 0; +} +EXPORT_SYMBOL(atu_to_frc); + +void +atu_clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec) +{ + u64 tmp; + u32 sft, sftacc = 32; + + /* + * Calculate the shift factor which is limiting the conversion + * range: + */ + tmp = ((u64)maxsec * from) >> 32; + while (tmp) { + tmp >>= 1; + sftacc--; + } + + /* + * Find the conversion shift/mult pair which has the best + * accuracy and fits the maxsec conversion range: + */ + for (sft = 32; sft > 0; sft--) { + tmp = (u64)to << sft; + tmp += from / 2; + do_div(tmp, from); + if ((tmp >> sftacc) == 0) + break; + } + *mult = tmp; + *shift = sft; +} + +static void atu_update_clk_time_on_rate_change(void) +{ + cycle_t ticks_now, ticks_delta; + s64 t; + + /* Read present clock ticks */ + ticks_now = patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + + /* Calculate the ticks delta since the last atu time update */ + ticks_delta = (patu_clk_mtner->atu_timecntr.cc->mask + 1 + ticks_now - + patu_clk_mtner->atu_timecntr.cycle_last) & + patu_clk_mtner->atu_timecntr.cc->mask; + + /* Convert to nanoseconds */ + t = clocksource_cyc2ns(ticks_delta, patu_clk_mtner->mult, + patu_clk_mtner->shift); + timespec_add_ns(&patu_clk_mtner->atu_time, t); + /* set cycle last */ + patu_clk_mtner->atu_timecntr.cycle_last = ticks_now; + + return; +} + +static void atu_clk_rate_change_on_the_fly(unsigned long int rate) +{ + unsigned long flags; + u32 mult, shift, mask; + cycle_t clk_cycle; + u64 ntp_ns_per_cycle, ntp_ns_per_cycle_div; + + /* update the time */ + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + atu_update_clk_time_on_rate_change(); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + mask = patu_clk_mtner->atu_timecntr.cc->mask; + + clocks_calc_mult_shift(&mult, &shift, rate, + NSEC_PER_SEC, DIV_ROUND_UP(mask, rate)); + + ntp_ns_per_cycle = (u64)NTP_INTERVAL_LENGTH << shift; + ntp_ns_per_cycle_div = do_div_round_closest(ntp_ns_per_cycle, mult); + + clk_cycle = ntp_ns_per_cycle_div ? (cycle_t)ntp_ns_per_cycle_div : + (cycle_t)1; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + + patu_clk_mtner->shift = shift; + patu_clk_mtner->mult = mult; + patu_clk_mtner->event_timer_rate = rate; + + atu_rate_changed = 1; + + /* Clear the error */ + patu_clk_mtner->tm_error = 0; + patu_clk_mtner->tm_error_shift = NTP_SCALE_SHIFT - shift; + + patu_clk_mtner->clk_cycles_per_ntp_cycle = clk_cycle; + + patu_clk_mtner->shifted_ns_per_ntp_cycle = + (u64)clk_cycle * mult; + patu_clk_mtner->shifted_remain_ns_per_ntp_cycle = + ntp_ns_per_cycle - patu_clk_mtner->shifted_ns_per_ntp_cycle; + + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); +} + +static int atu_adjtimex(struct timex *txc) +{ + int ret = 0; + unsigned long flags; + + /* Fractional PLL */ + if (patu_clk_mtner->clk_atu) { + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + + /* Time error correction - ADJ_SETOFFSET */ + if (txc->modes & ADJ_SETOFFSET) { + ret = atu_set_time_offset(txc); + goto unlock_and_return; + } + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + + /* Rate error correction - ADJ_FREQUENCY */ + if (txc->modes & ADJ_FREQUENCY) { + unsigned long int rate; + long int freq; + int dir; + + freq = txc->freq; + set_frac_pll_adj_freq(freq); + + if (freq < 0) { + dir = -1; + freq = -freq; + } else { + dir = 1; + } + + rate = patu_clk_mtner->event_timer_rate + + do_div_round_closest(((u64)freq) * + patu_clk_mtner->event_timer_rate, + NSEC_PER_SEC) * dir; + + /* Setting new rate */ + clk_set_rate(patu_clk_mtner->clk_atu, rate); + txc->freq = rate; + } + + } else { /* Fixed PLL */ + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (patu_clk_mtner->atu_timecntr.cc) + ret = __atu_adjtimex(txc, &patu_clk_mtner->atu_ntp); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + } + + return ret; + +unlock_and_return: + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return ret; +} + +static int atu_tm_big_adj_clk(s64 error, s64 *clk_cycle, s64 *ticks) +{ + s64 ntp_error, clk_interval; + u32 look_ahead, shift; + s32 mult = 1; + s32 tm_error; + + /* + * Correction of about 1msec within about 1 sec + * or 2^20 nsec in 2^SHIFT_HZ ticks. + */ + tm_error = patu_clk_mtner->tm_error >> + (NTP_SCALE_SHIFT + 22 - 2 * SHIFT_HZ); + + tm_error = abs(tm_error); + + for (look_ahead = 0; tm_error > 0; look_ahead++) + tm_error >>= 2; + + ntp_error = get_ntp_shifted_nsecs_per_cycle(&patu_clk_mtner->atu_ntp) >> + (patu_clk_mtner->tm_error_shift + 1); + ntp_error -= patu_clk_mtner->shifted_ns_per_ntp_cycle >> 1; + + error = ((error - ntp_error) >> look_ahead) + ntp_error; + + clk_interval = *clk_cycle; + if (error < 0) { + error = -error; + *clk_cycle = -*clk_cycle; + *ticks = -*ticks; + mult = -1; + } + for (shift = 0; + error > clk_interval; shift++) + error >>= 1; + + *clk_cycle <<= shift; + *ticks <<= shift; + return mult << shift; +} + +static void atu_tm_adj_clk(s64 ticks) +{ + s64 error, clk_cycle; + int mult; + + clk_cycle = patu_clk_mtner->clk_cycles_per_ntp_cycle; + + error = patu_clk_mtner->tm_error >> + (patu_clk_mtner->tm_error_shift - 1); + + if (error > clk_cycle) { + error >>= 3; + + if (likely(error <= clk_cycle)) + mult = 1; + else + mult = atu_tm_big_adj_clk(error, &clk_cycle, + &ticks); + } else if (error < -clk_cycle) { + error >>= 3; + + if (likely(error >= -clk_cycle)) { + mult = -1; + clk_cycle = -clk_cycle; + ticks = -ticks; + } else { + mult = atu_tm_big_adj_clk(error, &clk_cycle, + &ticks); + } + } else { + return; + } + + patu_clk_mtner->mult += mult; + patu_clk_mtner->shifted_ns_per_ntp_cycle += clk_cycle; + patu_clk_mtner->atu_time.tv_nsec -= + ticks >> patu_clk_mtner->shift; + patu_clk_mtner->tm_error -= (clk_cycle - ticks) << + patu_clk_mtner->tm_error_shift; +} + +static cycle_t calculate_remainder_ticks(cycle_t ticks) +{ + u64 no_cycles = ticks; + u64 clk_cycle; + s64 tick_error = 0; + + clk_cycle = patu_clk_mtner->clk_cycles_per_ntp_cycle; + + /* Get the no clk cycles per ntp cycle in the given ticks */ + do_div(no_cycles, clk_cycle); + + /* Subtract the no. of rounded cycles from ticks */ + ticks -= (no_cycles * patu_clk_mtner->clk_cycles_per_ntp_cycle); + + /* Add the no. of clk cycles to the cycle_last */ + patu_clk_mtner->atu_timecntr.cycle_last += + (no_cycles * patu_clk_mtner->clk_cycles_per_ntp_cycle); + + /* Add the no.of clk cycles to nano seconds */ + patu_clk_mtner->atu_time.tv_nsec += (no_cycles * patu_clk_mtner-> + shifted_ns_per_ntp_cycle) >> patu_clk_mtner->shift; + + /* + * If there is an overflow of nano seconds will update tv_sec and + * try to update/sync ntp's params + */ + while (patu_clk_mtner->atu_time.tv_nsec >= NSEC_PER_SEC) { + patu_clk_mtner->atu_time.tv_nsec -= NSEC_PER_SEC; + patu_clk_mtner->atu_time.tv_sec++; + atu_ntp_param_update_per_second(&patu_clk_mtner->atu_ntp); + } + + tick_error += get_ntp_shifted_nsecs_per_cycle(&patu_clk_mtner->atu_ntp); + tick_error -= + (patu_clk_mtner->shifted_ns_per_ntp_cycle + + patu_clk_mtner->shifted_remain_ns_per_ntp_cycle) << + (patu_clk_mtner->tm_error_shift); + + /* Add the tick error of no_cycles to ntp error */ + patu_clk_mtner->tm_error += (no_cycles * tick_error); + + /* Return the left over ticks count */ + return ticks; +} + +static void atu_update_clk_time(void) +{ + cycle_t ticks; + cycle_t curticks; + + curticks = patu_clk_mtner->atu_timecntr.cc-> + read(patu_clk_mtner->atu_timecntr.cc); + + ticks = (curticks - patu_clk_mtner->atu_timecntr.cycle_last) & + patu_clk_mtner->atu_timecntr.cc->mask; + + ticks = calculate_remainder_ticks(ticks); + + /* Correct the clock */ + atu_tm_adj_clk(ticks); + + /* If tv_nsec is -ve we will adjust that as error */ + if (unlikely((s64)patu_clk_mtner->atu_time.tv_nsec < 0)) { + s64 neg = -(s64)patu_clk_mtner->atu_time.tv_nsec; + + patu_clk_mtner->atu_time.tv_nsec = 0; + patu_clk_mtner->tm_error += + neg << patu_clk_mtner->tm_error_shift; + } + + while ((patu_clk_mtner->atu_time.tv_nsec >= NSEC_PER_SEC)) { + patu_clk_mtner->atu_time.tv_nsec -= NSEC_PER_SEC; + patu_clk_mtner->atu_time.tv_sec++; + atu_ntp_param_update_per_second(&patu_clk_mtner->atu_ntp); + } +} + +static void atu_time_update(void) +{ + unsigned long flags; + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + if (!patu_clk_mtner->atu_timecntr.cc) { + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + return; + } + atu_update_clk_time(); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); +} + +static int atu_clk_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + long int diff; + + switch (event) { + case PRE_RATE_CHANGE: + diff = abs((int)patu_clk_mtner->event_timer_rate + - (int)ndata->new_rate); + + /* + * Assumption is that card driver rate change + * will be more than EVENT_TIMER_RATE_TOLERANCE + */ + if (diff > EVENT_TIMER_RATE_TOLERANCE) { + atu_clk_rate_change_on_the_fly(ndata->new_rate); + //pr_info("ATU rate change %lu\n", ndata->new_rate); + } + return NOTIFY_OK; + case POST_RATE_CHANGE: + case ABORT_RATE_CHANGE: + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +int atu_cyclecounter_register(struct cyclecounter *pcc, struct clk *clk_atu) +{ + unsigned long flags; + + if (!pcc) { + pr_err("%s got NULL pointer\n", __func__); + return -EINVAL; + } + + if (clk_atu) + pr_info("Register Fractional PLL to ATU Clock\n"); + else + pr_info("Register Fixed PLL to ATU Clock\n"); + + if (clk_atu) { + patu_clk_mtner->event_timer_rate = clk_get_rate(clk_atu); + patu_clk_mtner->atu_clk_notifier.notifier_call = + atu_clk_notifier_cb; + clk_notifier_register(clk_atu, + &patu_clk_mtner->atu_clk_notifier); + pr_info("ATU rate %d\n", patu_clk_mtner->event_timer_rate); + } + + atu_chardev_init(); + atu_ntp_init(&patu_clk_mtner->atu_ntp); + + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + atu_clk_mtner_setup_internals(pcc, clk_atu); + atu_ntp_reset(&patu_clk_mtner->atu_ntp); + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + atu_timer_init(); + pr_info("ATU Clock Registered for cycle counter:0x%p\n", pcc); + + return 0; +} +EXPORT_SYMBOL(atu_cyclecounter_register); + +int atu_cyclecounter_unregister(struct cyclecounter *pcc) +{ + unsigned long flags; + + if (!pcc) { + pr_err("%s got NULL pointer\n", __func__); + return -EINVAL; + } + if (pcc != patu_clk_mtner->atu_timecntr.cc) { + pr_err("Invalid input data to %s\n", __func__); + return -EINVAL; + } + if (patu_clk_mtner->clk_atu) + clk_notifier_unregister(patu_clk_mtner->clk_atu, + &patu_clk_mtner->atu_clk_notifier); + + atu_timer_exit(); + atu_chardev_remove(); + spin_lock_irqsave(&patu_clk_mtner->atu_clk_lock, flags); + patu_clk_mtner->atu_timecntr.cc = NULL; + patu_clk_mtner->clk_atu = NULL; + spin_unlock_irqrestore(&patu_clk_mtner->atu_clk_lock, flags); + pr_info("ATU Clock Un-Registered for cycle counter:0x%p\n", pcc); + + return 0; +} +EXPORT_SYMBOL(atu_cyclecounter_unregister); + +static int __init atu_tm_init(void) +{ + patu_clk_mtner = kzalloc(sizeof(*patu_clk_mtner), GFP_KERNEL); + if (!patu_clk_mtner) + return -ENOMEM; + + /* Initialise the spin lock */ + spin_lock_init(&patu_clk_mtner->atu_clk_lock); + + /* Initialise ATU wall time */ + patu_clk_mtner->atu_time.tv_sec = 0; + patu_clk_mtner->atu_time.tv_nsec = 0; + + pr_info("ATU Clock Module Loaded\n"); + + return 0; +} +module_init(atu_tm_init); + +static void __exit atu_tm_exit(void) +{ + if (patu_clk_mtner->atu_timecntr.cc) + atu_timer_exit(); + atu_chardev_remove(); + kfree(patu_clk_mtner); + patu_clk_mtner = NULL; +} +module_exit(atu_tm_exit); + +MODULE_DESCRIPTION("ATU Clock Maintainer"); +MODULE_AUTHOR("Krishna.Badam@imgtec.com"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/atu/atu_clk_maintainer.h b/drivers/misc/atu/atu_clk_maintainer.h new file mode 100644 index 00000000000000..8b082d13e53df5 --- /dev/null +++ b/drivers/misc/atu/atu_clk_maintainer.h @@ -0,0 +1,22 @@ +/* + * Atu Clock Maintainer Header File + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#ifndef _ATU_CLK_MAINTAINER_H +#define _ATU_CLK_MAINTAINER_H + +#include +#include + +extern int atu_tm_add_offset(struct timespec *ts); +extern unsigned long atu_get_seconds(void); +extern void atu_getnstimeofday(struct timespec *tv); +extern int atu_settimeofday(const struct timespec *ts); + +#endif /* _ATU_CLK_MAINTAINER_H */ diff --git a/drivers/misc/atu/atu_clk_ntp.c b/drivers/misc/atu/atu_clk_ntp.c new file mode 100644 index 00000000000000..f186c5292ce8cb --- /dev/null +++ b/drivers/misc/atu/atu_clk_ntp.c @@ -0,0 +1,378 @@ +/* + * Atu Clock NTP handler + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "atu_clk_maintainer.h" +#include "atu_clk_ntp.h" + +#define ATU_NTP_MAX_TICK_ADJ (500LL) /* usecs */ +#define ATU_NTP_MAX_TICK_ADJ_SCALED \ + (((ATU_NTP_MAX_TICK_ADJ * NSEC_PER_USEC) << \ + NTP_SCALE_SHIFT) / NTP_INTERVAL_FREQ) +#define ATU_NTP_DEV_CAP 15000 + +u64 get_ntp_shifted_nsecs_per_cycle(struct atu_clk_ntp *patu_ntp) +{ + return patu_ntp->shifted_nsecs_per_cycle; +} + +static +void atu_ntp_shifted_nsecs_per_cycle_update(struct atu_clk_ntp *patu_ntp) +{ + u64 num_ticks_per_second; + u64 new_base; + s32 remainder; + + num_ticks_per_second = (u64)(patu_ntp->cycle_period_usec * + NSEC_PER_USEC * USER_HZ) << NTP_SCALE_SHIFT; + + num_ticks_per_second += patu_ntp->ntp_tick_adj; + + num_ticks_per_second += patu_ntp->time_freq; + + patu_ntp->nsec_per_cycle = div_u64_rem(num_ticks_per_second, + HZ, &remainder) >> NTP_SCALE_SHIFT; + new_base = div_u64_rem(num_ticks_per_second, NTP_INTERVAL_FREQ, + &remainder); + + /* Update shifted_nsecs_per_cycle */ + patu_ntp->shifted_nsecs_per_cycle += new_base - + patu_ntp->shifted_nsecs_per_cycle_ref; + + patu_ntp->shifted_nsecs_per_cycle_ref = new_base; +} + +static +s64 atu_calc_freq_adj(struct atu_clk_ntp *patu_ntp, s64 offset, long secs) +{ + s32 remainder; + + /* Check lapsed seconds with in perimitted range of update intervals */ + if (secs < MINSEC || secs > MAXSEC) + return 0; + + /* If it is in not in FLL mode, we won't adjust frequency */ + if (!(patu_ntp->time_status & STA_FLL)) + return 0; + + return div_s64_rem(offset << (NTP_SCALE_SHIFT - SHIFT_FLL), + secs, &remainder); +} + +static void atu_ntp_offset_update(struct atu_clk_ntp *patu_ntp, s64 offset) +{ + s32 remainder; + + /* Check offset is with in allowed range */ + offset = clamp_val(offset, -MAXPHASE, MAXPHASE); + patu_ntp->time_offset = div_s64_rem(offset << NTP_SCALE_SHIFT, + NTP_INTERVAL_FREQ, &remainder); +} + +static +void atu_ntp_offset_freq_adjust(struct atu_clk_ntp *patu_ntp, s64 offset) +{ + s64 freq_adj; + long secs; + + /* Get the update interval time and update ref time */ + secs = atu_get_seconds() - patu_ntp->time_reftime; + patu_ntp->time_reftime = secs + patu_ntp->time_reftime; + + /* If freq is in locked state, then we wont do freq adjustments */ + if (patu_ntp->time_status & STA_FREQHOLD) + secs = 0; + + freq_adj = atu_calc_freq_adj(patu_ntp, offset, secs); + + /* + * If update interval is long then clamp to some value, + * such that it won't mess PLL adjustments too much + */ + if (secs > (1 << (SHIFT_PLL + 1 + patu_ntp->time_constant))) + secs = 1 << (SHIFT_PLL + 1 + patu_ntp->time_constant); + + freq_adj += (offset * secs) << + (NTP_SCALE_SHIFT - 2 * + (SHIFT_PLL + 2 + patu_ntp->time_constant)); + + patu_ntp->time_freq = clamp_val(freq_adj + patu_ntp->time_freq, + -MAXFREQ_SCALED, MAXFREQ_SCALED); +} + +void atu_ntp_reset(struct atu_clk_ntp *patu_ntp) +{ + patu_ntp->time_adjust = 0; + patu_ntp->time_status |= STA_UNSYNC; + patu_ntp->time_maxerror = NTP_PHASE_LIMIT; + patu_ntp->time_esterror = NTP_PHASE_LIMIT; + + atu_ntp_shifted_nsecs_per_cycle_update(patu_ntp); + + /* Update shifted_nsecs_per_cycle to last update value */ + patu_ntp->shifted_nsecs_per_cycle = + patu_ntp->shifted_nsecs_per_cycle_ref; + patu_ntp->time_offset = 0; +} + +void atu_ntp_param_update_per_second(struct atu_clk_ntp *patu_ntp) +{ + s64 delta; + + patu_ntp->time_maxerror += MAXFREQ / NSEC_PER_USEC; + + if (patu_ntp->time_maxerror > NTP_PHASE_LIMIT) { + patu_ntp->time_maxerror = NTP_PHASE_LIMIT; + patu_ntp->time_status |= STA_UNSYNC; + } + + patu_ntp->shifted_nsecs_per_cycle = + patu_ntp->shifted_nsecs_per_cycle_ref; + + delta = shift_right(patu_ntp->time_offset, + SHIFT_PLL + patu_ntp->time_constant); + patu_ntp->time_offset -= delta; + patu_ntp->shifted_nsecs_per_cycle += delta; + + if (!patu_ntp->time_adjust) + return; + + if (patu_ntp->time_adjust > ATU_NTP_MAX_TICK_ADJ) { + patu_ntp->time_adjust -= ATU_NTP_MAX_TICK_ADJ; + patu_ntp->shifted_nsecs_per_cycle += + ATU_NTP_MAX_TICK_ADJ_SCALED; + return; + } + + if (patu_ntp->time_adjust < -ATU_NTP_MAX_TICK_ADJ) { + patu_ntp->time_adjust += ATU_NTP_MAX_TICK_ADJ; + patu_ntp->shifted_nsecs_per_cycle -= + ATU_NTP_MAX_TICK_ADJ_SCALED; + return; + } + + patu_ntp->shifted_nsecs_per_cycle += (s64)(patu_ntp->time_adjust * + NSEC_PER_USEC / NTP_INTERVAL_FREQ) << NTP_SCALE_SHIFT; + + patu_ntp->time_adjust = 0; +} + +static +void atu_ntp_status_update(struct timex *txc, struct atu_clk_ntp *patu_ntp) +{ + /* + * If local time status is PLL and ntp's time status FLL, + * then update status as not in sync + */ + if ((patu_ntp->time_status & STA_PLL) && !(txc->status & STA_PLL)) { + patu_ntp->time_state = TIME_OK; + patu_ntp->time_status = STA_UNSYNC; + } + + /* If PLL is just selected then we have to update the reference time */ + if (!(patu_ntp->time_status & STA_PLL) && (txc->status & STA_PLL)) + patu_ntp->time_reftime = atu_get_seconds(); + + /* We are interested only in related status bits */ + patu_ntp->time_status &= STA_RONLY; + patu_ntp->time_status |= txc->status & ~STA_RONLY; +} + +static +void adjtimex_modes_handler(struct timex *txc, struct atu_clk_ntp *patu_ntp) +{ + if (txc->modes & ADJ_STATUS) + atu_ntp_status_update(txc, patu_ntp); + + if (txc->modes & ADJ_NANO) + patu_ntp->time_status |= STA_NANO; + + if (txc->modes & ADJ_MICRO) + patu_ntp->time_status &= ~STA_NANO; + + if (txc->modes & ADJ_MAXERROR) + patu_ntp->time_maxerror = txc->maxerror; + + if (txc->modes & ADJ_ESTERROR) + patu_ntp->time_esterror = txc->esterror; + + if (txc->modes & ADJ_FREQUENCY) { + patu_ntp->time_freq = txc->freq * PPM_SCALE; + patu_ntp->time_freq = clamp_val(patu_ntp->time_freq, + -MAXFREQ_SCALED, MAXFREQ_SCALED); + } + + if (txc->modes & ADJ_TIMECONST) { + patu_ntp->time_constant = txc->constant; + if (!(patu_ntp->time_status & STA_NANO)) + patu_ntp->time_constant += 4; + patu_ntp->time_constant = clamp_val(patu_ntp->time_constant, + 0, MAXTC); + } + + if (txc->modes & ADJ_OFFSET) { + s64 offset; + if (!(patu_ntp->time_status & STA_PLL)) + return; + + offset = txc->offset; + + if (!(patu_ntp->time_status & STA_NANO)) + offset *= NSEC_PER_USEC; + + /* Adjust time_offset for the given offset */ + atu_ntp_offset_update(patu_ntp, offset); + + /* Adjust time_freq for the given offset */ + atu_ntp_offset_freq_adjust(patu_ntp, offset); + } + + if (txc->modes & ADJ_TICK) + patu_ntp->cycle_period_usec = txc->tick; + + /* + * We need to adjust shifted_nsecs_per_cycle in case of + * ADJ_TICK, ADJ_FREQUENCY and ADJ_OFFSET modes + */ + if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET)) + atu_ntp_shifted_nsecs_per_cycle_update(patu_ntp); +} + +int atu_set_time_offset(struct timex *txc) +{ + int result; + struct timespec offset_time; + + offset_time.tv_sec = txc->time.tv_sec; + offset_time.tv_nsec = txc->time.tv_usec; + if (!(txc->modes & ADJ_NANO)) + offset_time.tv_nsec *= NSEC_PER_USEC; + + result = atu_tm_add_offset(&offset_time); + + return result; +} + +int __atu_adjtimex(struct timex *txc, struct atu_clk_ntp *patu_ntp) +{ + struct timespec ts; + int result; + + if (txc->modes & ADJ_ADJTIME) { + /* + * ADJTIME's single shot must not be used + * with any other mode bits + */ + if (!(txc->modes & ADJ_OFFSET_SINGLESHOT)) + return -EINVAL; + } else { + /* ADJ_TIMEX mode */ + + /* + * If the tick duaration is deviated more than + * 15% then treat it as invalid data + */ + if (txc->modes & ADJ_TICK && + (txc->tick < (NSEC_PER_SEC - ATU_NTP_DEV_CAP)/USER_HZ || + txc->tick > (NSEC_PER_SEC + ATU_NTP_DEV_CAP)/USER_HZ)) + return -EINVAL; + } + + if (txc->modes & ADJ_SETOFFSET) { + result = atu_set_time_offset(txc); + if (result) + return result; + } + + if (txc->modes & ADJ_ADJTIME) { + long save_adjust = patu_ntp->time_adjust; + + /* + * This is not part of time adjust, it is a one and time + * independent one. After this update will return back old + * time_adjust + */ + if (!(txc->modes & ADJ_OFFSET_READONLY)) + patu_ntp->time_adjust = txc->offset; + + txc->offset = save_adjust; + } else { + + if (txc->modes) + adjtimex_modes_handler(txc, patu_ntp); + + txc->offset = shift_right(patu_ntp->time_offset * + NTP_INTERVAL_FREQ, NTP_SCALE_SHIFT); + + if (!(patu_ntp->time_status & STA_NANO)) + txc->offset /= NSEC_PER_USEC; + } + + if (patu_ntp->time_status & (STA_UNSYNC|STA_CLOCKERR)) + result = TIME_ERROR; + else + result = TIME_OK; + + /* Fill txc with possible/available data */ + txc->freq = shift_right((patu_ntp->time_freq >> PPM_SCALE_INV_SHIFT) * + PPM_SCALE_INV, NTP_SCALE_SHIFT); + txc->maxerror = patu_ntp->time_maxerror; + txc->esterror = patu_ntp->time_esterror; + txc->status = patu_ntp->time_status; + txc->constant = patu_ntp->time_constant; + txc->precision = 1; + txc->tolerance = MAXFREQ_SCALED / PPM_SCALE; + txc->tick = patu_ntp->cycle_period_usec; + txc->tai = 0; + txc->ppsfreq = 0; + txc->jitter = 0; + txc->shift = 0; + txc->stabil = 0; + txc->jitcnt = 0; + txc->calcnt = 0; + txc->errcnt = 0; + txc->stbcnt = 0; + + /* Update time */ + atu_getnstimeofday(&ts); + txc->time.tv_sec = ts.tv_sec; + txc->time.tv_usec = ts.tv_nsec; + if (!(patu_ntp->time_status & STA_NANO)) + txc->time.tv_usec /= NSEC_PER_USEC; + + return result; +} + +void atu_ntp_init(struct atu_clk_ntp *patu_ntp) +{ + /* Initialise with default values */ + patu_ntp->time_state = TIME_OK; + patu_ntp->time_status = STA_UNSYNC; + patu_ntp->cycle_period_usec = TICK_USEC; + patu_ntp->nsec_per_cycle = 0; + patu_ntp->shifted_nsecs_per_cycle = 0; + patu_ntp->shifted_nsecs_per_cycle_ref = 0; + patu_ntp->time_offset = 0; + patu_ntp->time_constant = 0x2; + patu_ntp->time_freq = 0; + patu_ntp->time_maxerror = NTP_PHASE_LIMIT; + patu_ntp->time_esterror = 0; + patu_ntp->time_reftime = 0; + patu_ntp->time_adjust = 0; + patu_ntp->ntp_tick_adj = 0; + + atu_ntp_reset(patu_ntp); +} diff --git a/drivers/misc/atu/atu_clk_ntp.h b/drivers/misc/atu/atu_clk_ntp.h new file mode 100644 index 00000000000000..3b0b44a414a490 --- /dev/null +++ b/drivers/misc/atu/atu_clk_ntp.h @@ -0,0 +1,40 @@ +/* + * Atu Clock NTP handler Header File + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#ifndef ATU_NTP_H +#define ATU_NTP_H + +struct atu_clk_ntp { + int time_state; + int time_status; + unsigned long cycle_period_usec; + unsigned long nsec_per_cycle; + u64 shifted_nsecs_per_cycle; + u64 shifted_nsecs_per_cycle_ref; + s64 time_offset; + long time_constant; + s64 time_freq; + long time_maxerror; + long time_esterror; + long time_reftime; + long time_adjust; + s64 ntp_tick_adj; +}; + +struct timex; + +extern u64 get_ntp_shifted_nsecs_per_cycle(struct atu_clk_ntp *patu_ntp); +extern void atu_ntp_param_update_per_second(struct atu_clk_ntp *patu_ntp); +extern void atu_ntp_init(struct atu_clk_ntp *patu_ntp); +extern void atu_ntp_reset(struct atu_clk_ntp *patu_ntp); +extern int __atu_adjtimex(struct timex *txc, struct atu_clk_ntp *patu_ntp); +extern int atu_set_time_offset(struct timex *txc); + +#endif /* ATU_NTP_H */ diff --git a/drivers/misc/img-pdm.c b/drivers/misc/img-pdm.c new file mode 100644 index 00000000000000..f486dd566e5113 --- /dev/null +++ b/drivers/misc/img-pdm.c @@ -0,0 +1,653 @@ +/** + * Imagination Technologies Pulse Density Modulator driver + * + * Copyright (C) 2014-2015 Imagination Technologies Ltd. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define PERIP_PWM_PDM_CONTROL_CH_MASK 0x1 +#define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch) ((ch) * 4) + +#define PERIP_PDM0_VAL 0x4 +#define PERIP_PDM_CH_ADDR_SHIFT(ch) ((ch) * 4) +#define PERIP_PDM_SRC_DATA_MASK 0xfff + +#define PERIP_PDM_CONTROL_MASK 0x1 +#define PERIP_PDM_CONTROL_SHIFT(ch) ((ch) * 1) + +#define IMG_NUM_PDM 4 +#define PDM_CHANNEL_REQUESTED 1 +#define PDM_CHANNEL_ENABLED 2 + +struct img_pdm_soc_data { + u32 syscon_offset; + int en; +}; + +struct img_pdm_device { + struct clk *clk; + struct kobject **pdm_kobj; + struct regmap *periph_regs; + struct platform_device *pdev; + const struct img_pdm_soc_data *data; +}; + +static struct img_pdm_channel *pdm_channels; +static DEFINE_MUTEX(pdm_lock); + +int img_pdm_channel_config(struct img_pdm_channel *chan, unsigned int val) +{ + struct img_pdm_device *pdm_dev; + u32 offset = chan->pdm_dev->data->syscon_offset; + + mutex_lock(&pdm_lock); + + if (!chan) { + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + pdm_dev = chan->pdm_dev; + if (!test_bit(PDM_CHANNEL_REQUESTED, &chan->flags)) { + dev_err(&pdm_dev->pdev->dev, "channel not requested\n"); + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + val &= PERIP_PDM_SRC_DATA_MASK; + regmap_write(pdm_dev->periph_regs, + PERIP_PDM0_VAL + offset + + PERIP_PDM_CH_ADDR_SHIFT(chan->pdm_id), + val); + mutex_unlock(&pdm_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(img_pdm_channel_config); + +static int img_pdm_channel_free(struct img_pdm_channel *chan) +{ + unsigned int i; + struct img_pdm_device *pdm_dev; + + mutex_lock(&pdm_lock); + + if (!pdm_channels || !chan) { + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + pdm_dev = pdm_channels[0].pdm_dev; + if (!pdm_dev) { + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + for (i = 0; i < IMG_NUM_PDM; i++) { + if (&pdm_channels[i] && (&pdm_channels[i] == chan)) + break; + } + + if (i == IMG_NUM_PDM) { + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + if (test_bit(PDM_CHANNEL_ENABLED, &chan->flags)) { + dev_err(&pdm_dev->pdev->dev, + "can't free the channel while it is enabled\n"); + mutex_unlock(&pdm_lock); + return -EBUSY; + } + + if (!test_bit(PDM_CHANNEL_REQUESTED, &chan->flags)) { + dev_err(&pdm_dev->pdev->dev, + "trying to free channel which is not requested\n"); + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + clear_bit(PDM_CHANNEL_REQUESTED, &chan->flags); + mutex_unlock(&pdm_lock); + + return 0; +} + +static struct img_pdm_channel *img_pdm_channel_request(unsigned int pdm_id) +{ + unsigned int i; + struct img_pdm_device *pdm_dev; + struct img_pdm_channel *chan = NULL; + + mutex_lock(&pdm_lock); + + if (pdm_id < 0 || pdm_id >= IMG_NUM_PDM || !pdm_channels) { + mutex_unlock(&pdm_lock); + return NULL; + } + + pdm_dev = pdm_channels[0].pdm_dev; + if (!pdm_dev) + return NULL; + + for (i = 0; i < IMG_NUM_PDM; i++) { + if (&pdm_channels[i] && (pdm_channels[i].pdm_id == pdm_id)) { + chan = &pdm_channels[i]; + break; + } + } + + if (!chan) { + mutex_unlock(&pdm_lock); + return NULL; + } + + /* Check if channel is already requested */ + if (test_bit(PDM_CHANNEL_REQUESTED, &chan->flags)) { + dev_err(&pdm_dev->pdev->dev, + "pdm channel %d already requested\n", chan->pdm_id); + mutex_unlock(&pdm_lock); + return NULL; + } + + set_bit(PDM_CHANNEL_REQUESTED, &chan->flags); + mutex_unlock(&pdm_lock); + + return chan; +} + +static struct img_pdm_channel *of_img_pdm_channel_get(struct device_node *np) +{ + int err; + struct of_phandle_args args; + struct img_pdm_channel *chan; + + err = of_parse_phandle_with_args(np, "pdms", "#pdm-cells", 0, &args); + if (err) { + pr_debug("%s: can't parse \"pdms\" property\n", __func__); + return ERR_PTR(err); + } + + if (args.args_count != 2) { + pr_debug("%s: wrong #pwm-cells\n", __func__); + return ERR_PTR(-EINVAL); + } + + chan = img_pdm_channel_request(args.args[0]); + if (chan) + img_pdm_channel_config(chan, args.args[1]); + + return chan; +} + +static void of_img_pdm_channel_put(struct device_node *np) +{ + int err; + struct of_phandle_args args; + struct img_pdm_channel *chan; + + err = of_parse_phandle_with_args(np, "pdms", "#pdm-cells", 0, &args); + if (err) { + pr_debug("%s: can't parse \"pdms\" property\n", __func__); + return; + } + + if (args.args_count != 2) { + pr_debug("%s: wrong #pwm-cells\n", __func__); + return; + } + + if (args.args[0] < 0 || args.args[0] >= IMG_NUM_PDM || !pdm_channels) + return; + + chan = &pdm_channels[args.args[0]]; + img_pdm_channel_free(chan); +} + +struct img_pdm_channel *img_pdm_channel_get(struct device *dev) +{ + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + return of_img_pdm_channel_get(dev->of_node); + + return NULL; +} +EXPORT_SYMBOL_GPL(img_pdm_channel_get); + +void img_pdm_channel_put(struct device *dev) +{ + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + of_img_pdm_channel_put(dev->of_node); +} +EXPORT_SYMBOL_GPL(img_pdm_channel_put); + +int img_pdm_channel_enable(struct img_pdm_channel *chan, bool state) +{ + struct img_pdm_device *pdm_dev; + u32 offset; + + mutex_lock(&pdm_lock); + + if (!chan) { + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + pdm_dev = chan->pdm_dev; + offset = pdm_dev->data->syscon_offset; + + if (!test_bit(PDM_CHANNEL_REQUESTED, &chan->flags)) { + dev_err(&pdm_dev->pdev->dev, "channel not requested\n"); + mutex_unlock(&pdm_lock); + return -EINVAL; + } + + if (pdm_dev->data->en > 0) { + regmap_update_bits(pdm_dev->periph_regs, + offset + pdm_dev->data->en, + PERIP_PDM_CONTROL_MASK << + PERIP_PDM_CONTROL_SHIFT(chan->pdm_id), + (!!state) << + PERIP_PDM_CONTROL_SHIFT(chan->pdm_id)); + } + + if (state) { + regmap_update_bits(pdm_dev->periph_regs, + offset, + PERIP_PWM_PDM_CONTROL_CH_MASK << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(chan->pdm_id), + 1 << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(chan->pdm_id)); + set_bit(PDM_CHANNEL_ENABLED, &chan->flags); + } else { + regmap_write(pdm_dev->periph_regs, + PERIP_PDM0_VAL + offset + + PERIP_PDM_CH_ADDR_SHIFT(chan->pdm_id), 0); + regmap_update_bits(pdm_dev->periph_regs, + offset, + PERIP_PWM_PDM_CONTROL_CH_MASK << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(chan->pdm_id), + 0 << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(chan->pdm_id)); + clear_bit(PDM_CHANNEL_ENABLED, &chan->flags); + } + mutex_unlock(&pdm_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(img_pdm_channel_enable); + +static ssize_t img_pdm_enable_read(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + unsigned int ch_num; + unsigned char kobj_name[2]; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + struct img_pdm_channel *chan; + + pdev = to_platform_device(kobj_to_dev(kobj->parent)); + pdm_dev = platform_get_drvdata(pdev); + kobj_name[0] = *(kobj->name+3); + kobj_name[1] = '\0'; + + ret = kstrtou32(kobj_name, 10, &ch_num); + if (ret) { + dev_err(&pdev->dev, "could not parse channel number string\n"); + return ret; + } + + chan = &pdm_channels[ch_num]; + return sprintf(buf, "%d\n", + test_bit(PDM_CHANNEL_ENABLED, &chan->flags) ? 1 : 0); +} + +static ssize_t img_pdm_pulse_in_read(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + unsigned int ch_num, val; + unsigned char kobj_name[2]; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + struct img_pdm_channel *chan; + u32 offset; + + pdev = to_platform_device(kobj_to_dev(kobj->parent)); + pdm_dev = platform_get_drvdata(pdev); + kobj_name[0] = *(kobj->name+3); + kobj_name[1] = '\0'; + ret = kstrtou32(kobj_name, 10, &ch_num); + if (ret) { + dev_err(&pdev->dev, "could not parse channel number string\n"); + return ret; + } + + offset = pdm_dev->data->syscon_offset; + chan = &pdm_channels[ch_num]; + regmap_read(pdm_dev->periph_regs, + PERIP_PDM0_VAL + offset + + PERIP_PDM_CH_ADDR_SHIFT(chan->pdm_id), &val); + val &= PERIP_PDM_SRC_DATA_MASK; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t img_pdm_enable_write(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned int ch_num, enable; + unsigned char kobj_name[2]; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + + pdev = to_platform_device(kobj_to_dev(kobj->parent)); + pdm_dev = platform_get_drvdata(pdev); + + kobj_name[0] = *(kobj->name+3); + kobj_name[1] = '\0'; + ret = kstrtou32(kobj_name, 10, &ch_num); + if (ret) { + dev_err(&pdev->dev, "could not parse channel number string\n"); + return ret; + } + + ret = kstrtou32(buf, 10, &enable); + if (ret) { + dev_err(&pdev->dev, "could not parse enable attr value\n"); + return ret; + } + + ret = img_pdm_channel_enable(&pdm_channels[ch_num], !!enable); + if (ret < 0) + return ret; + + return size; +} + +static ssize_t img_pdm_pulse_in_write(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned int pulse_in, ch_num; + unsigned char kobj_name[2]; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + + pdev = to_platform_device(kobj_to_dev(kobj->parent)); + pdm_dev = platform_get_drvdata(pdev); + + kobj_name[0] = *(kobj->name+3); + kobj_name[1] = '\0'; + ret = kstrtou32(kobj_name, 10, &ch_num); + if (ret) { + dev_err(&pdev->dev, "could not parse channel number string\n"); + return ret; + } + + ret = kstrtouint(buf, 16, &pulse_in); + if (ret) { + dev_err(&pdev->dev, + "could not parse pulse_in attr value\n"); + return ret; + } + + if (pulse_in > PERIP_PDM_SRC_DATA_MASK) { + dev_err(&pdev->dev, + "invalid attr value for pulse_in string\n"); + return -EINVAL; + } + + ret = img_pdm_channel_config(&pdm_channels[ch_num], pulse_in); + if (ret < 0) + return ret; + + return size; +} + +#define PDM_ATTR(_name, _mode, _show, _store) \ +struct kobj_attribute pdm_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode}, \ + .show = _show, \ + .store = _store, \ +} + +static PDM_ATTR(enable, S_IRUGO | S_IWUSR, img_pdm_enable_read, + img_pdm_enable_write); + +static PDM_ATTR(pulse_in, S_IRUGO | S_IWUSR, img_pdm_pulse_in_read, + img_pdm_pulse_in_write); + +static struct attribute *pdm_sysfs_attrs[] = { + &pdm_attr_enable.attr, + &pdm_attr_pulse_in.attr, + NULL, +}; + +static const struct attribute_group pdm_attr_group = { + .attrs = pdm_sysfs_attrs, +}; + +static ssize_t img_pdm_export(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned int ch_num; + unsigned char kobj_name[5]; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + struct img_pdm_channel *pdm_chan; + + pdev = to_platform_device(dev); + pdm_dev = platform_get_drvdata(pdev); + + ret = kstrtou32(buf, 10, &ch_num); + if (ret) { + dev_err(&pdev->dev, "could not parse channel number string\n"); + return ret; + } + + pdm_chan = img_pdm_channel_request(ch_num); + if (!pdm_chan) + return -EINVAL; + + memset(kobj_name, 0, sizeof(kobj_name)); + sprintf(kobj_name, "pdm%d", ch_num); + pdm_dev->pdm_kobj[ch_num] = kobject_create_and_add(kobj_name, + &pdev->dev.kobj); + if (!pdm_dev->pdm_kobj[ch_num]) { + img_pdm_channel_free(pdm_chan); + return -ENOMEM; + } + + ret = sysfs_create_group(pdm_dev->pdm_kobj[ch_num], &pdm_attr_group); + if (ret) { + kobject_put(pdm_dev->pdm_kobj[ch_num]); + img_pdm_channel_free(pdm_chan); + dev_err(&pdev->dev, "unable to register device attributes\n"); + return ret; + } + + return size; +} + +static ssize_t img_pdm_unexport(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned int ch_num; + struct img_pdm_channel *channel; + struct platform_device *pdev; + struct img_pdm_device *pdm_dev; + + pdev = to_platform_device(dev); + pdm_dev = platform_get_drvdata(pdev); + + ret = kstrtou32(buf, 10, &ch_num); + if (ret < 0) + return ret; + + if (ch_num < 0 || ch_num >= IMG_NUM_PDM) { + dev_err(&pdev->dev, "invalid channel number %d\n", ch_num); + return -EINVAL; + } + + channel = &pdm_channels[ch_num]; + if (img_pdm_channel_free(channel) < 0) + return -EINVAL; + + if (pdm_dev->pdm_kobj[ch_num]) { + sysfs_remove_group(pdm_dev->pdm_kobj[ch_num], &pdm_attr_group); + kobject_put(pdm_dev->pdm_kobj[ch_num]); + } + + return size; +} + +static DEVICE_ATTR(export, S_IRUGO | S_IWUSR, NULL, img_pdm_export); +static DEVICE_ATTR(unexport, S_IRUGO | S_IWUSR, NULL, img_pdm_unexport); + +static struct attribute *img_pdm_sysfs_attrs[] = { + &dev_attr_export.attr, + &dev_attr_unexport.attr, + NULL, +}; + +static const struct attribute_group img_pdm_attr_group = { + .attrs = img_pdm_sysfs_attrs, +}; + +static int img_pdm_probe(struct platform_device *pdev) +{ + int ret; + unsigned int i; + struct img_pdm_device *pdm_dev; + + pdm_dev = devm_kzalloc(&pdev->dev, sizeof(*pdm_dev), GFP_KERNEL); + if (!pdm_dev) + return -ENOMEM; + + pdm_dev->pdm_kobj = devm_kcalloc(&pdev->dev, IMG_NUM_PDM, + sizeof(struct kobject), GFP_KERNEL); + if (!pdm_dev->pdm_kobj) + return -ENOMEM; + + pdm_channels = devm_kcalloc(&pdev->dev, IMG_NUM_PDM, + sizeof(struct img_pdm_channel), GFP_KERNEL); + if (!pdm_channels) + return -ENOMEM; + + pdm_dev->periph_regs = syscon_regmap_lookup_by_phandle( + pdev->dev.of_node, "img,cr-periph"); + if (IS_ERR(pdm_dev->periph_regs)) + return PTR_ERR(pdm_dev->periph_regs); + + pdm_dev->clk = devm_clk_get(&pdev->dev, "pdm"); + if (IS_ERR(pdm_dev->clk)) { + dev_err(&pdev->dev, "failed to get pdm clock\n"); + return PTR_ERR(pdm_dev->clk); + } + + ret = clk_prepare_enable(pdm_dev->clk); + if (ret < 0) { + dev_err(&pdev->dev, "could not prepare or enable pdm clock\n"); + return ret; + } + + for (i = 0; i < IMG_NUM_PDM; i++) { + pdm_channels[i].pdm_id = i; + pdm_channels[i].pdm_dev = pdm_dev; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &img_pdm_attr_group); + if (ret) { + dev_err(&pdev->dev, "unable to register device attributes\n"); + clk_disable_unprepare(pdm_dev->clk); + return ret; + } + + pdm_dev->pdev = pdev; + platform_set_drvdata(pdev, pdm_dev); + + return 0; +} + +static int img_pdm_remove(struct platform_device *pdev) +{ + unsigned int i; + struct img_pdm_channel *chan; + struct img_pdm_device *pdm_dev; + + pdm_dev = platform_get_drvdata(pdev); + + for (i = 0; i < IMG_NUM_PDM; i++) { + chan = &pdm_channels[i]; + if (test_bit(PDM_CHANNEL_REQUESTED, &chan->flags)) { + img_pdm_channel_enable(chan, false); + img_pdm_channel_config(chan, 0); + if (pdm_dev->pdm_kobj[i]) { + sysfs_remove_group(pdm_dev->pdm_kobj[i], + &pdm_attr_group); + kobject_del(pdm_dev->pdm_kobj[i]); + } + } + } + + clk_disable_unprepare(pdm_dev->clk); + + return 0; +} + +static const struct img_pdm_soc_data pistachio_pdm = { + .syscon_offset = 0x140, + .en = -1, +}; + +static const struct of_device_id img_pdm_of_match[] = { + { .compatible = "img,pistachio-pdm", }, + { } +}; +MODULE_DEVICE_TABLE(of, img_pdm_of_match); + +static struct platform_driver img_pdm_driver = { + .driver = { + .name = "img-pdm", + .of_match_table = img_pdm_of_match, + }, + .probe = img_pdm_probe, + .remove = img_pdm_remove, +}; +module_platform_driver(img_pdm_driver); + +MODULE_AUTHOR("Arul Ramasamy "); +MODULE_DESCRIPTION("Imagination Technologies PDM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/img-scratchpad.c b/drivers/misc/img-scratchpad.c new file mode 100644 index 00000000000000..b2da3530ce5987 --- /dev/null +++ b/drivers/misc/img-scratchpad.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 Imagination Technologies Ltd. + * + * This driver provides sysfs read/write access to the scratchpad + * registers, these registers are soft reset protected registers. + * + * 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MAX_NUM_REG 8 +#define REG_SIZE 4 +#define FILE_PREFIX "reg" + +struct scratchpad_device { + struct clk *wdt_clk; + struct clk *sys_clk; + struct regmap *regmap; + struct attribute_group attr_group; + struct device_attribute *attr; + struct attribute **attrs; +}; + +static int reg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned int value; + unsigned int index; + int ret; + struct scratchpad_device *priv = dev_get_drvdata(dev); + + /* find out reg number based on name of attr */ + if (sscanf(attr->attr.name, FILE_PREFIX"%u", &index) != 1) + return -EINVAL; + + if (index > MAX_NUM_REG) + return -EINVAL; + + ret = regmap_read(priv->regmap, index*REG_SIZE, &value); + if (ret) + return ret; + + return sprintf(buf, "0x%x\n", value); +} + +static int reg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + u32 value; + unsigned int index; + struct scratchpad_device *priv = dev_get_drvdata(dev); + + /* find out reg number based on name of attr */ + if (sscanf(attr->attr.name, FILE_PREFIX"%u", &index) != 1) + return -EINVAL; + + if (index > MAX_NUM_REG) + return -EINVAL; + + ret = kstrtou32(buf, 0, &value); + if (ret < 0) + return ret; + + ret = regmap_write(priv->regmap, index*REG_SIZE, value); + if (ret) + return ret; + + return count; +} + +static int enable_wdt_clk(struct device *dev) +{ + int ret; + struct scratchpad_device *priv = dev_get_drvdata(dev); + + priv->sys_clk = devm_clk_get(dev, "sys"); + if (IS_ERR(priv->sys_clk)) { + dev_err(dev, "failed to get the sys clock\n"); + return PTR_ERR(priv->sys_clk); + } + + priv->wdt_clk = devm_clk_get(dev, "wdt"); + if (IS_ERR(priv->wdt_clk)) { + dev_err(dev, "failed to get the wdt clock\n"); + return PTR_ERR(priv->wdt_clk); + } + + ret = clk_prepare_enable(priv->sys_clk); + if (ret) { + dev_err(dev, "could not prepare or enable sys clock\n"); + return ret; + } + + ret = clk_prepare_enable(priv->wdt_clk); + if (ret) { + dev_err(dev, "could not prepare or enable wdt clock\n"); + clk_disable_unprepare(priv->sys_clk); + } + + return ret; +} + +static void disable_wdt_clk(struct device *dev) +{ + struct scratchpad_device *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->wdt_clk); + clk_disable_unprepare(priv->sys_clk); +} + +static int create_sysfs_files(struct device *dev, unsigned long sysfs_mask) +{ + int i, attr_index = 0; + unsigned int num_regs = 0; + int ret; + struct scratchpad_device *priv = dev_get_drvdata(dev); + + /* If no files to be created, just return */ + if (!sysfs_mask) + return 0; + + for_each_set_bit(i, &sysfs_mask, BITS_PER_BYTE) + num_regs++; + + /* Allocate memory for sysfs attributes based on number + * of registers to be exposed + * +1 for NULL termination in the end + */ + priv->attrs = devm_kzalloc(dev, + sizeof(struct attribute *) * (num_regs+1), + GFP_KERNEL); + if (!priv->attrs) + return -ENOMEM; + + priv->attr = devm_kzalloc(dev, + sizeof(struct device_attribute) * num_regs, + GFP_KERNEL); + if (!priv->attr) + return -ENOMEM; + + /* create sysfs attributes */ + for_each_set_bit(i, &sysfs_mask, BITS_PER_BYTE) { + char *name; + const int name_len = sizeof(FILE_PREFIX) + 2; + + name = devm_kmalloc(dev, name_len, GFP_KERNEL); + if (!name) + return -ENOMEM; + + /* specify file name based on the actual reg index */ + snprintf(name, name_len, FILE_PREFIX"%01u", i); + + sysfs_attr_init(&priv->attr[attr_index].attr); + priv->attr[attr_index].attr.name = name; + priv->attr[attr_index].attr.mode = (S_IWUSR | S_IRUGO); + priv->attr[attr_index].show = reg_show; + priv->attr[attr_index].store = reg_store; + priv->attrs[attr_index] = &priv->attr[attr_index].attr; + attr_index++; + } + + priv->attr_group.attrs = priv->attrs; + ret = sysfs_create_group(&dev->kobj, &priv->attr_group); + if (ret) + dev_err(dev, "Error in creating sysfs group\n"); + + return ret; +} + +static int scratchpad_probe(struct platform_device *ofdev) +{ + struct device *dev = &ofdev->dev; + struct scratchpad_device *priv; + int ret; + u8 sysfs_mask = 0xFF; + + priv = devm_kzalloc(dev, sizeof(struct scratchpad_device), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->regmap = syscon_node_to_regmap(dev->of_node); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Unable to get regmap\n"); + return PTR_ERR(priv->regmap); + } + + if (of_property_read_u8(dev->of_node, "sysfs-mask", &sysfs_mask)) + dev_info(dev, "sysfs-mask property not specified\n"); + + ret = enable_wdt_clk(dev); + if (ret) + return ret; + + ret = create_sysfs_files(dev, sysfs_mask); + if (ret) + goto clk_unprepare; + + return 0; + +clk_unprepare: + disable_wdt_clk(dev); + return ret; +} + +static int scratchpad_remove(struct platform_device *ofdev) +{ + struct scratchpad_device *priv = dev_get_drvdata(&ofdev->dev); + + sysfs_remove_group(&ofdev->dev.kobj, &priv->attr_group); + + disable_wdt_clk(&ofdev->dev); + + return 0; +} + +static const struct of_device_id scratchpad_match[] = { + { .compatible = "img,pistachio-scratchpad", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, scratchpad_match); + +static struct platform_driver scratchpad_driver = { + .driver = { + .name = "img-scratchpad", + .of_match_table = scratchpad_match, + }, + .probe = scratchpad_probe, + .remove = scratchpad_remove, +}; + +module_platform_driver(scratchpad_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Shraddha Chaudhari"); +MODULE_DESCRIPTION("Provide syfs read/write access to the scratchpad registers"); diff --git a/drivers/misc/uboot_bootcount.c b/drivers/misc/uboot_bootcount.c new file mode 100644 index 00000000000000..eecdb4dde3c295 --- /dev/null +++ b/drivers/misc/uboot_bootcount.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * This driver gives access(read/write) to the bootcounter used by u-boot. + * Access is supported via sysfs. + * + * Based on work from: Steffen Rumler + * + * 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UBOOT_BOOTCOUNT_MAGIC 0xB001C041 /* magic number value */ +#define UBOOT_BOOTCOUNT_MAGIC_MASK 0xFFFF0000 /* magic, when combined */ +#define UBOOT_BOOTCOUNT_COUNT_MASK 0x0000FFFF /* value, when combined */ + + +struct bootcount_device { + struct regmap *regmap; + unsigned int regmap_offset; +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int bootcount_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + unsigned int bootcount; + struct bootcount_device *priv = dev_get_drvdata(dev); + + ret = regmap_read(priv->regmap, priv->regmap_offset, &bootcount); + if (ret) + return ret; + + if ((bootcount & UBOOT_BOOTCOUNT_MAGIC_MASK) != + (UBOOT_BOOTCOUNT_MAGIC & UBOOT_BOOTCOUNT_MAGIC_MASK)) { + return -EINVAL; + } + bootcount &= UBOOT_BOOTCOUNT_COUNT_MASK; + return sprintf(buf, "%u\n", bootcount); +} + +static int bootcount_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret; + u32 value; + struct bootcount_device *priv = dev_get_drvdata(dev); + + ret = kstrtou32(buf, 0, &value); + if (ret < 0) + return ret; + + value = (UBOOT_BOOTCOUNT_MAGIC & UBOOT_BOOTCOUNT_MAGIC_MASK) | + (value & UBOOT_BOOTCOUNT_COUNT_MASK); + ret = regmap_write(priv->regmap, priv->regmap_offset, value); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(bootcount); + +static int bootcount_probe(struct platform_device *ofdev) +{ + unsigned int magic; + struct bootcount_device *priv; + struct resource *res; + int status, ret; + + priv = devm_kzalloc(&ofdev->dev, sizeof(struct bootcount_device), GFP_KERNEL); + if (!priv) { + dev_err(&ofdev->dev, "Unable to allocate device private data\n"); + return -ENOMEM; + } + + res = platform_get_resource(ofdev, IORESOURCE_MEM, 0); + if (res) { + void __iomem *reg; + + reg = devm_ioremap_resource(&ofdev->dev, res); + if (IS_ERR(reg)) { + dev_err(&ofdev->dev, "Unable to map register\n"); + return PTR_ERR(reg); + } + priv->regmap = devm_regmap_init_mmio(&ofdev->dev, reg, + ®map_config); + if (IS_ERR(priv->regmap)) { + dev_err(&ofdev->dev, "Unable to get regmap\n"); + return PTR_ERR(priv->regmap); + } + + priv->regmap_offset = 0; + } else { + struct of_phandle_args args; + + ret = of_parse_phandle_with_fixed_args(ofdev->dev.of_node, + "syscon-reg", 1, 0, + &args); + if (ret) + return ret; + priv->regmap = syscon_node_to_regmap(args.np); + if (IS_ERR(priv->regmap)) { + dev_err(&ofdev->dev, "Unable to get regmap\n"); + return PTR_ERR(priv->regmap); + } + priv->regmap_offset = args.args[0]; + } + + ret = regmap_read(priv->regmap, priv->regmap_offset, &magic); + if (ret) + return ret; + + if ((magic & UBOOT_BOOTCOUNT_MAGIC_MASK) != + (UBOOT_BOOTCOUNT_MAGIC & UBOOT_BOOTCOUNT_MAGIC_MASK)) { + dev_err(&ofdev->dev, "bad magic\n"); + return -EINVAL; + } + + status = device_create_file(&ofdev->dev, &dev_attr_bootcount); + if (status) { + dev_err(&ofdev->dev, "unable to register sysfs entry\n"); + return status; + } + dev_set_drvdata(&ofdev->dev, priv); + return 0; +} + +static int bootcount_remove(struct platform_device *ofdev) +{ + device_remove_file(&ofdev->dev, &dev_attr_bootcount); + return 0; +} + +static const struct of_device_id bootcount_match[] = { + { .compatible = "uboot,bootcount", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, bootcount_match); + +static struct platform_driver bootcount_driver = { + .driver = { + .name = "bootcount", + .of_match_table = bootcount_match, + }, + .probe = bootcount_probe, + .remove = bootcount_remove, +}; + +module_platform_driver(bootcount_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Avinash Tahakik "); +MODULE_DESCRIPTION("Provide (read/write) access to the U-Boot bootcounter via sysfs"); diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 7a6cedbe48a837..4601b1f3be0c65 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -3217,14 +3217,17 @@ EXPORT_SYMBOL(dw_mci_remove); #ifdef CONFIG_PM_SLEEP -/* - * TODO: we should probably disable the clock to the card in the suspend path. - */ int dw_mci_suspend(struct dw_mci *host) { if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); + if (!IS_ERR(host->ciu_clk)) + clk_disable(host->ciu_clk); + + if (!IS_ERR(host->biu_clk)) + clk_disable(host->biu_clk); + return 0; } EXPORT_SYMBOL(dw_mci_suspend); @@ -3233,6 +3236,12 @@ int dw_mci_resume(struct dw_mci *host) { int i, ret; + if (!IS_ERR(host->ciu_clk)) + clk_enable(host->ciu_clk); + + if (!IS_ERR(host->biu_clk)) + clk_enable(host->biu_clk); + if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_ALL_RESET_FLAGS)) { ret = -ENODEV; return ret; diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index a03ad2951c7b42..5b1b3ba9a97b39 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -334,6 +334,8 @@ source "drivers/mtd/onenand/Kconfig" source "drivers/mtd/lpddr/Kconfig" +source "drivers/mtd/spi-nand/Kconfig" + source "drivers/mtd/spi-nor/Kconfig" source "drivers/mtd/ubi/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 99bb9a1f6e16fc..38a475692cdeb8 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -32,5 +32,6 @@ inftl-objs := inftlcore.o inftlmount.o obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/ +obj-$(CONFIG_MTD_SPI_NAND) += spi-nand/ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ obj-$(CONFIG_MTD_UBI) += ubi/ diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index fe9ceb7b5405ad..74e4d40b81aeb7 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -47,13 +47,15 @@ static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len) return ret; } -static void m25p_addr2cmd(struct spi_nor *nor, unsigned int addr, u8 *cmd) +static void m25p_addr2cmd(unsigned int addr, unsigned int addr_width, u8 *cmd) { - /* opcode is in cmd[0] */ - cmd[1] = addr >> (nor->addr_width * 8 - 8); - cmd[2] = addr >> (nor->addr_width * 8 - 16); - cmd[3] = addr >> (nor->addr_width * 8 - 24); - cmd[4] = addr >> (nor->addr_width * 8 - 32); + if (addr_width) { + /* opcode is in cmd[0] */ + cmd[1] = addr >> (addr_width * 8 - 8); + cmd[2] = addr >> (addr_width * 8 - 16); + cmd[3] = addr >> (addr_width * 8 - 24); + cmd[4] = addr >> (addr_width * 8 - 32); + } } static int m25p_cmdsz(struct spi_nor *nor) @@ -88,7 +90,7 @@ static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len, cmd_sz = 1; flash->command[0] = nor->program_opcode; - m25p_addr2cmd(nor, to, flash->command); + m25p_addr2cmd(to, nor->addr_width, flash->command); t[0].tx_buf = flash->command; t[0].len = cmd_sz; @@ -103,9 +105,9 @@ static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len, *retlen += m.actual_length - cmd_sz; } -static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor) +static inline unsigned int m25p80_rx_nbits(enum read_mode mode) { - switch (nor->flash_read) { + switch (mode) { case SPI_NOR_DUAL: return 2; case SPI_NOR_QUAD: @@ -142,7 +144,7 @@ static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len, spi_message_add_tail(&t[0], &m); t[1].rx_buf = buf; - t[1].rx_nbits = m25p80_rx_nbits(nor); + t[1].rx_nbits = m25p80_rx_nbits(nor->flash_read); t[1].len = len; spi_message_add_tail(&t[1], &m); @@ -161,13 +163,97 @@ static int m25p80_erase(struct spi_nor *nor, loff_t offset) /* Set up command buffer. */ flash->command[0] = nor->erase_opcode; - m25p_addr2cmd(nor, offset, flash->command); + m25p_addr2cmd(offset, nor->addr_width, flash->command); spi_write(flash->spi, flash->command, m25p_cmdsz(nor)); return 0; } +/* From spi_nor_xfer_cfg, this call ignores cmd_pins, addr_pins, so single I/O + * line is used for cmd and addr + * mode_pins, mode_cycles are ignored, decides nbits based on mode + */ +static int m25p80_read_xfer(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg, + u8 *buf, size_t len, size_t *retlen) +{ + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; + struct spi_transfer t[2]; + struct spi_message m; + u32 dummy = cfg->dummy_cycles/8; /* convert dummy cycles into bytes */ + u32 cmd_sz = (1 + cfg->addr_width + dummy); + int ret; + + if (cfg->addr_width > 4 || cmd_sz > MAX_CMD_SIZE) + return -EINVAL; + + spi_message_init(&m); + memset(t, 0, sizeof (t)); + + memset(flash->command, 0, MAX_CMD_SIZE); + flash->command[0] = cfg->cmd; + m25p_addr2cmd(cfg->addr, cfg->addr_width, flash->command); + + t[0].tx_buf = flash->command; + t[0].len = cmd_sz; + spi_message_add_tail(&t[0], &m); + + if (len) { + t[1].rx_buf = buf; + t[1].rx_nbits = m25p80_rx_nbits(cfg->mode); + t[1].len = len; + spi_message_add_tail(&t[1], &m); + } + + ret = spi_sync(spi, &m); + + if (!ret) + *retlen += (m.actual_length - cmd_sz); + return ret; +} + +/* From spi_nor_xfer_cfg, this call ignores cmd_pins, addr_pins, mode, mode_pins, + * mode_cycles, so single I/O line is used for cmd, addr and data + */ +static int m25p80_write_xfer(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg, + u8 *buf, size_t len, size_t *retlen) +{ + struct m25p *flash = nor->priv; + struct spi_device *spi = flash->spi; + struct spi_transfer t[2]; + struct spi_message m; + u32 dummy = cfg->dummy_cycles/8; /* convert dummy cycles into bytes */ + u32 cmd_sz = (1 + cfg->addr_width + dummy); + int ret; + + if (cfg->addr_width > 4 || cmd_sz > MAX_CMD_SIZE) + return -EINVAL; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + memset(flash->command, 0, MAX_CMD_SIZE); + flash->command[0] = cfg->cmd; + m25p_addr2cmd(cfg->addr, cfg->addr_width, flash->command); + + t[0].tx_buf = flash->command; + t[0].len = cmd_sz; + spi_message_add_tail(&t[0], &m); + + if (len) { + t[1].tx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + } + + ret = spi_sync(spi, &m); + + if (!ret) + *retlen += (m.actual_length - cmd_sz); + return ret; +} + /* * board specific setup should have ensured the SPI clock used here * matches what the READ command supports, at least until this driver @@ -197,6 +283,8 @@ static int m25p_probe(struct spi_device *spi) nor->erase = m25p80_erase; nor->write_reg = m25p80_write_reg; nor->read_reg = m25p80_read_reg; + nor->read_xfer = m25p80_read_xfer; + nor->write_xfer = m25p80_write_xfer; nor->dev = &spi->dev; nor->flash_node = spi->dev.of_node; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 3ff583f165cdfc..7fb42b0b3b831b 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -3758,11 +3758,13 @@ static bool find_full_id_nand(struct mtd_info *mtd, struct nand_chip *chip, mtd->erasesize = type->erasesize; mtd->oobsize = type->oobsize; - chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + if (type->id_len > 2) + chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]); chip->chipsize = (uint64_t)type->chipsize << 20; chip->options |= type->options; chip->ecc_strength_ds = NAND_ECC_STRENGTH(type); chip->ecc_step_ds = NAND_ECC_STEP(type); + chip->ecc.layout = type->ecc.layout; chip->onfi_timing_mode_default = type->onfi_timing_mode_default; diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c index a8804a3da07609..1b1d75edc7c0ef 100644 --- a/drivers/mtd/nand/nand_ids.c +++ b/drivers/mtd/nand/nand_ids.c @@ -181,6 +181,7 @@ struct nand_manufacturers nand_manuf_ids[] = { {NAND_MFR_SANDISK, "SanDisk"}, {NAND_MFR_INTEL, "Intel"}, {NAND_MFR_ATO, "ATO"}, + {NAND_MFR_GIGADEVICE, "Gigadevice"}, {0x0, "Unknown"} }; diff --git a/drivers/mtd/spi-nand/Kconfig b/drivers/mtd/spi-nand/Kconfig new file mode 100644 index 00000000000000..df29abe64829d0 --- /dev/null +++ b/drivers/mtd/spi-nand/Kconfig @@ -0,0 +1,18 @@ +menuconfig MTD_SPI_NAND + tristate "SPI NAND device support" + depends on MTD + select MTD_NAND + help + This is the framework for the SPI NAND. + +if MTD_SPI_NAND + +config MTD_SPI_NAND_DEVICES + tristate "Support for SPI NAND devices (MT29F, GD5F)" + default y + depends on MTD_SPI_NAND + help + Select this option if you require support for the most common SPI NAND + devices such as mt29f and gd5f. + +endif # MTD_SPI_NAND diff --git a/drivers/mtd/spi-nand/Makefile b/drivers/mtd/spi-nand/Makefile new file mode 100644 index 00000000000000..f4f95b7d56a9cd --- /dev/null +++ b/drivers/mtd/spi-nand/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MTD_SPI_NAND) += spi-nand-base.o +obj-$(CONFIG_MTD_SPI_NAND_DEVICES) += spi-nand-device.o diff --git a/drivers/mtd/spi-nand/spi-nand-base.c b/drivers/mtd/spi-nand/spi-nand-base.c new file mode 100644 index 00000000000000..8127437ca45583 --- /dev/null +++ b/drivers/mtd/spi-nand/spi-nand-base.c @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2014 Imagination Technologies 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; version 2 of the License. + * + * Notes: + * 1. Erase and program operations need to call write_enable() first, + * to clear the enable bit. This bit is cleared automatically after + * the erase or program operation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers common to all devices */ +#define SPI_NAND_LOCK_REG 0xa0 +#define SPI_NAND_PROT_UNLOCK_ALL 0x0 + +#define SPI_NAND_FEATURE_REG 0xb0 +#define SPI_NAND_ECC_EN BIT(4) +#define SPI_NAND_QUAD_EN BIT(0) + +#define SPI_NAND_STATUS_REG 0xc0 +#define SPI_NAND_STATUS_REG_ECC_MASK 0x3 +#define SPI_NAND_STATUS_REG_ECC_SHIFT 4 +#define SPI_NAND_STATUS_REG_PROG_FAIL BIT(3) +#define SPI_NAND_STATUS_REG_ERASE_FAIL BIT(2) +#define SPI_NAND_STATUS_REG_WREN BIT(1) +#define SPI_NAND_STATUS_REG_BUSY BIT(0) + +#define SPI_NAND_CMD_BUF_LEN 8 + +/* Rewind and fill the buffer with 0xff */ +static void spi_nand_clear_buffer(struct spi_nand *snand) +{ + snand->buf_start = 0; + memset(snand->data_buf, 0xff, snand->buf_size); +} + +static int spi_nand_enable_ecc(struct spi_nand *snand) +{ + int ret; + + ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + + snand->buf[0] |= SPI_NAND_ECC_EN; + ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + snand->ecc = true; + + return 0; +} + +static int spi_nand_disable_ecc(struct spi_nand *snand) +{ + int ret; + + ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + + snand->buf[0] &= ~SPI_NAND_ECC_EN; + ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + snand->ecc = false; + + return 0; +} + +static int spi_nand_enable_quad(struct spi_nand *snand) +{ + int ret; + + ret = snand->read_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + + snand->buf[0] |= SPI_NAND_QUAD_EN; + ret = snand->write_reg(snand, SPI_NAND_FEATURE_REG, snand->buf); + if (ret) + return ret; + + return 0; +} +/* + * Wait until the status register busy bit is cleared. + * Returns a negatie errno on error or time out, and a non-negative status + * value if the device is ready. + */ +static int spi_nand_wait_till_ready(struct spi_nand *snand) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(100); + bool timeout = false; + int ret; + + /* + * Perhaps we should set a different timeout for each + * operation (reset, read, write, erase). + */ + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = true; + + ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf); + if (ret < 0) { + dev_err(snand->dev, "error reading status register\n"); + return ret; + } else if (!(snand->buf[0] & SPI_NAND_STATUS_REG_BUSY)) { + return snand->buf[0]; + } + + cond_resched(); + } + + dev_err(snand->dev, "operation timed out\n"); + + return -ETIMEDOUT; +} + +static int spi_nand_reset(struct spi_nand *snand) +{ + int ret; + + ret = snand->reset(snand); + if (ret < 0) { + dev_err(snand->dev, "reset command failed\n"); + return ret; + } + + /* + * The NAND core won't wait after a device reset, so we need + * to do that here. + */ + ret = spi_nand_wait_till_ready(snand); + if (ret < 0) + return ret; + return 0; +} + +static int spi_nand_status(struct spi_nand *snand) +{ + int ret, status; + + ret = snand->read_reg(snand, SPI_NAND_STATUS_REG, snand->buf); + if (ret < 0) { + dev_err(snand->dev, "error reading status register\n"); + return ret; + } + status = snand->buf[0]; + + /* Convert this into standard NAND_STATUS values */ + if (status & SPI_NAND_STATUS_REG_BUSY) + snand->buf[0] = 0; + else + snand->buf[0] = NAND_STATUS_READY; + + if (status & SPI_NAND_STATUS_REG_PROG_FAIL || + status & SPI_NAND_STATUS_REG_ERASE_FAIL) + snand->buf[0] |= NAND_STATUS_FAIL; + + /* + * Since we unlock the entire device at initialization, unconditionally + * set the WP bit to indicate it's not protected. + */ + snand->buf[0] |= NAND_STATUS_WP; + return 0; +} + +static int spi_nand_erase(struct spi_nand *snand, int page_addr) +{ + int ret; + + ret = snand->write_enable(snand); + if (ret < 0) { + dev_err(snand->dev, "write enable command failed\n"); + return ret; + } + + ret = snand->block_erase(snand, page_addr); + if (ret < 0) { + dev_err(snand->dev, "block erase command failed\n"); + return ret; + } + + return 0; +} + +static int spi_nand_write(struct spi_nand *snand) +{ + int ret; + + /* Enable quad mode */ + ret = spi_nand_enable_quad(snand); + if (ret) { + dev_err(snand->dev, "error %d enabling quad mode\n", ret); + return ret; + } + /* Store the page to cache */ + ret = snand->store_cache(snand, 0, snand->buf_size, snand->data_buf); + if (ret < 0) { + dev_err(snand->dev, "error %d storing page 0x%x to cache\n", + ret, snand->page_addr); + return ret; + } + + ret = snand->write_enable(snand); + if (ret < 0) { + dev_err(snand->dev, "write enable command failed\n"); + return ret; + } + + /* Get page from the device cache into our internal buffer */ + ret = snand->write_page(snand, snand->page_addr); + if (ret < 0) { + dev_err(snand->dev, "error %d reading page 0x%x from cache\n", + ret, snand->page_addr); + return ret; + } + + return 0; +} + +static int spi_nand_read_id(struct spi_nand *snand) +{ + int ret; + + ret = snand->read_id(snand, snand->data_buf); + if (ret < 0) { + dev_err(snand->dev, "error %d reading ID\n", ret); + return ret; + } + return 0; +} + +static int spi_nand_read_page(struct spi_nand *snand, unsigned int page_addr, + unsigned int page_offset, size_t length) +{ + unsigned int corrected = 0, ecc_error = 0; + int ret; + + /* Load a page into the cache register */ + ret = snand->load_page(snand, page_addr); + if (ret < 0) { + dev_err(snand->dev, "error %d loading page 0x%x to cache\n", + ret, page_addr); + return ret; + } + + ret = spi_nand_wait_till_ready(snand); + if (ret < 0) + return ret; + + if (snand->ecc) { + snand->get_ecc_status(ret, &corrected, &ecc_error); + snand->bitflips = corrected; + + /* + * If there's an ECC error, print a message and notify MTD + * about it. Then complete the read, to load actual data on + * the buffer (instead of the status result). + */ + if (ecc_error) { + dev_err(snand->dev, + "internal ECC error reading page 0x%x\n", + page_addr); + snand->mtd.ecc_stats.failed++; + } else { + snand->mtd.ecc_stats.corrected += corrected; + } + } + + /* Enable quad mode */ + ret = spi_nand_enable_quad(snand); + if (ret) { + dev_err(snand->dev, "error %d enabling quad mode\n", ret); + return ret; + } + /* Get page from the device cache into our internal buffer */ + ret = snand->read_cache(snand, page_offset, length, snand->data_buf); + if (ret < 0) { + dev_err(snand->dev, "error %d reading page 0x%x from cache\n", + ret, page_addr); + return ret; + } + return 0; +} + +static u8 spi_nand_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct spi_nand *snand = chip->priv; + char val = 0xff; + + if (snand->buf_start < snand->buf_size) + val = snand->data_buf[snand->buf_start++]; + return val; +} + +static void spi_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct spi_nand *snand = chip->priv; + size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start); + + memcpy(snand->data_buf + snand->buf_start, buf, n); + snand->buf_start += n; +} + +static void spi_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct spi_nand *snand = chip->priv; + size_t n = min_t(size_t, len, snand->buf_size - snand->buf_start); + + memcpy(buf, snand->data_buf + snand->buf_start, n); + snand->buf_start += n; +} + +static int spi_nand_write_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf, int oob_required) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + return 0; +} + +static int spi_nand_read_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, int oob_required, + int page) +{ + struct spi_nand *snand = chip->priv; + + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + return snand->bitflips; +} + +static int spi_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) +{ + struct spi_nand *snand = chip->priv; + int ret; + + ret = spi_nand_wait_till_ready(snand); + + if (ret < 0) { + return NAND_STATUS_FAIL; + } else if (ret & SPI_NAND_STATUS_REG_PROG_FAIL) { + dev_err(snand->dev, "page program failed\n"); + return NAND_STATUS_FAIL; + } else if (ret & SPI_NAND_STATUS_REG_ERASE_FAIL) { + dev_err(snand->dev, "block erase failed\n"); + return NAND_STATUS_FAIL; + } + + return NAND_STATUS_READY; +} + +static void spi_nand_cmdfunc(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + struct nand_chip *chip = mtd->priv; + struct spi_nand *snand = chip->priv; + + /* + * In case there's any unsupported command, let's make sure + * we don't keep garbage around in the buffer. + */ + if (command != NAND_CMD_PAGEPROG) { + spi_nand_clear_buffer(snand); + snand->page_addr = 0; + } + + switch (command) { + case NAND_CMD_READ0: + spi_nand_read_page(snand, page_addr, 0, mtd->writesize); + break; + case NAND_CMD_READOOB: + spi_nand_disable_ecc(snand); + spi_nand_read_page(snand, page_addr, mtd->writesize, + mtd->oobsize); + spi_nand_enable_ecc(snand); + break; + case NAND_CMD_READID: + spi_nand_read_id(snand); + break; + case NAND_CMD_ERASE1: + spi_nand_erase(snand, page_addr); + break; + case NAND_CMD_ERASE2: + /* There's nothing to do here, as the erase is one-step */ + break; + case NAND_CMD_SEQIN: + snand->buf_start = column; + snand->page_addr = page_addr; + break; + case NAND_CMD_PAGEPROG: + spi_nand_write(snand); + break; + case NAND_CMD_STATUS: + spi_nand_status(snand); + break; + case NAND_CMD_RESET: + spi_nand_reset(snand); + break; + default: + dev_err(&mtd->dev, "unknown command 0x%x\n", command); + } +} + +static void spi_nand_select_chip(struct mtd_info *mtd, int chip) +{ + /* We need this to override the default */ +} + +int spi_nand_check(struct spi_nand *snand) +{ + if (!snand->dev) + return -ENODEV; + if (!snand->read_cache) + return -ENODEV; + if (!snand->load_page) + return -ENODEV; + if (!snand->store_cache) + return -ENODEV; + if (!snand->write_page) + return -ENODEV; + if (!snand->write_reg) + return -ENODEV; + if (!snand->read_reg) + return -ENODEV; + if (!snand->block_erase) + return -ENODEV; + if (!snand->reset) + return -ENODEV; + if (!snand->write_enable) + return -ENODEV; + if (!snand->write_disable) + return -ENODEV; + if (!snand->get_ecc_status) + return -ENODEV; + return 0; +} + +int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids) +{ + struct nand_chip *chip = &snand->nand_chip; + struct mtd_part_parser_data ppdata = {}; + struct mtd_info *mtd = &snand->mtd; + struct device_node *np = snand->dev->of_node; + const char __maybe_unused *of_mtd_name = NULL; + int ret; + + /* Let's check all the hooks are in-place so we don't panic later */ + ret = spi_nand_check(snand); + if (ret) + return ret; + + chip->priv = snand; + chip->read_buf = spi_nand_read_buf; + chip->write_buf = spi_nand_write_buf; + chip->read_byte = spi_nand_read_byte; + chip->cmdfunc = spi_nand_cmdfunc; + chip->waitfunc = spi_nand_waitfunc; + chip->select_chip = spi_nand_select_chip; + chip->options |= NAND_NO_SUBPAGE_WRITE; + chip->bits_per_cell = 1; + + chip->ecc.read_page = spi_nand_read_page_hwecc; + chip->ecc.write_page = spi_nand_write_page_hwecc; + chip->ecc.mode = NAND_ECC_HW; + + if (of_get_nand_on_flash_bbt(np)) + chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + +#ifdef CONFIG_MTD_OF_PARTS + of_property_read_string(np, "linux,mtd-name", &of_mtd_name); +#endif + if (of_mtd_name) + mtd->name = of_mtd_name; + else + mtd->name = snand->name; + mtd->owner = THIS_MODULE; + mtd->priv = chip; + + /* Allocate buffer to be used to read/write the internal registers */ + snand->buf = kmalloc(SPI_NAND_CMD_BUF_LEN, GFP_KERNEL); + if (!snand->buf) + return -ENOMEM; + + /* This is enabled at device power up but we'd better make sure */ + ret = spi_nand_enable_ecc(snand); + if (ret) + return ret; + + /* Preallocate buffer for flash identification (NAND_CMD_READID) */ + snand->buf_size = SPI_NAND_CMD_BUF_LEN; + snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL); + + ret = nand_scan_ident(mtd, 1, flash_ids); + if (ret) + return ret; + + /* + * SPI NAND has on-die ECC, which means we can correct as much as + * we are required to. This must be done after identification of + * the device. + */ + chip->ecc.strength = chip->ecc_strength_ds; + chip->ecc.size = chip->ecc_step_ds; + + /* + * Unlock all the device before calling nand_scan_tail. This is needed + * in case the in-flash bad block table needs to be created. + * We could override __nand_unlock(), but since it's not currently used + * by the NAND core we call this explicitly. + */ + snand->buf[0] = SPI_NAND_PROT_UNLOCK_ALL; + ret = snand->write_reg(snand, SPI_NAND_LOCK_REG, snand->buf); + if (ret) + return ret; + + /* Free the buffer and allocate a good one, to fit a page plus OOB */ + kfree(snand->data_buf); + + snand->buf_size = mtd->writesize + mtd->oobsize; + snand->data_buf = kmalloc(snand->buf_size, GFP_KERNEL); + if (!snand->data_buf) + return -ENOMEM; + + ret = nand_scan_tail(mtd); + if (ret) + return ret; + + ppdata.of_node = np; + return mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); +} +EXPORT_SYMBOL_GPL(spi_nand_register); + +void spi_nand_unregister(struct spi_nand *snand) +{ + kfree(snand->buf); + kfree(snand->data_buf); + nand_release(&snand->mtd); +} +EXPORT_SYMBOL_GPL(spi_nand_unregister); + +MODULE_AUTHOR("Ezequiel Garcia "); +MODULE_DESCRIPTION("Framework for SPI NAND"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/spi-nand/spi-nand-device.c b/drivers/mtd/spi-nand/spi-nand-device.c new file mode 100644 index 00000000000000..ff782239e47c0e --- /dev/null +++ b/drivers/mtd/spi-nand/spi-nand-device.c @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2014 Imagination Technologies 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; version 2 of the License. + * + * Notes: + * 1. We avoid using a stack-allocated buffer for SPI messages. Using + * a kmalloced buffer is probably better, given we shouldn't assume + * any particular usage by SPI core. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPI NAND commands */ +#define SPI_NAND_WRITE_ENABLE 0x06 +#define SPI_NAND_WRITE_DISABLE 0x04 +#define SPI_NAND_GET_FEATURE 0x0f +#define SPI_NAND_SET_FEATURE 0x1f +#define SPI_NAND_PAGE_READ 0x13 +#define SPI_NAND_READ_CACHE 0x03 +#define SPI_NAND_FAST_READ_CACHE 0x0b +#define SPI_NAND_READ_CACHE_X2 0x3b +#define SPI_NAND_READ_CACHE_X4 0x6b +#define SPI_NAND_READ_CACHE_DUAL_IO 0xbb +#define SPI_NAND_READ_CACHE_QUAD_IO 0xeb +#define SPI_NAND_READ_ID 0x9f +#define SPI_NAND_PROGRAM_LOAD 0x02 +#define SPI_NAND_PROGRAM_LOAD4 0x32 +#define SPI_NAND_PROGRAM_EXEC 0x10 +#define SPI_NAND_PROGRAM_LOAD_RANDOM 0x84 +#define SPI_NAND_PROGRAM_LOAD_RANDOM4 0xc4 +#define SPI_NAND_BLOCK_ERASE 0xd8 +#define SPI_NAND_RESET 0xff + +#define SPI_NAND_GD5F_READID_LEN 2 +#define SPI_NAND_MT29F_READID_LEN 2 + +#define SPI_NAND_GD5F_ECC_MASK (BIT(0) | BIT(1) | BIT(2)) +#define SPI_NAND_GD5F_ECC_UNCORR (BIT(0) | BIT(1) | BIT(2)) +#define SPI_NAND_GD5F_ECC_SHIFT 4 + +#define SPI_NAND_MT29F_ECC_MASK (BIT(0) | BIT(1)) +#define SPI_NAND_MT29F_ECC_UNCORR (BIT(1)) +#define SPI_NAND_MT29F_ECC_SHIFT 4 + +static struct nand_ecclayout ecc_layout_gd5f = { + .eccbytes = 128, + .eccpos = { + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249, 250, 251, 252, 253, 254, 255 + }, + .oobfree = { {1, 127} } +}; + +static struct nand_ecclayout ecc_layout_mt29f = { + .eccbytes = 32, + .eccpos = { + 8, 9, 10, 11, 12, 13, 14, 15, + 24, 25, 26, 27, 28, 29, 30, 31, + 40, 41, 42, 43, 44, 45, 46, 47, + 56, 57, 58, 59, 60, 61, 62, 63, + }, +}; + +static struct nand_flash_dev spi_nand_flash_ids[] = { + { + .name = "SPI NAND 512MiB 3,3V", + .id = { NAND_MFR_GIGADEVICE, 0xb4 }, + .chipsize = 512, + .pagesize = SZ_4K, + .erasesize = SZ_256K, + .id_len = 2, + .oobsize = 256, + .ecc.strength_ds = 8, + .ecc.step_ds = 512, + .ecc.layout = &ecc_layout_gd5f, + }, + { + .name = "SPI NAND 512MiB 1,8V", + .id = { NAND_MFR_GIGADEVICE, 0xa4 }, + .chipsize = 512, + .pagesize = SZ_4K, + .erasesize = SZ_256K, + .id_len = 2, + .oobsize = 256, + .ecc.strength_ds = 8, + .ecc.step_ds = 512, + .ecc.layout = &ecc_layout_gd5f, + }, + { + .name = "SPI NAND 512MiB 3,3V", + .id = { NAND_MFR_MICRON, 0x32 }, + .chipsize = 512, + .pagesize = SZ_2K, + .erasesize = SZ_128K, + .id_len = 2, + .oobsize = 64, + .ecc.strength_ds = 4, + .ecc.step_ds = 512, + .ecc.layout = &ecc_layout_mt29f, + }, + { + .name = "SPI NAND 256MiB 3,3V", + .id = { NAND_MFR_MICRON, 0x22 }, + .chipsize = 256, + .pagesize = SZ_2K, + .erasesize = SZ_128K, + .id_len = 2, + .oobsize = 64, + .ecc.strength_ds = 4, + .ecc.step_ds = 512, + .ecc.layout = &ecc_layout_mt29f, + }, +}; + +enum spi_nand_device_variant { + SPI_NAND_GENERIC, + SPI_NAND_MT29F, + SPI_NAND_GD5F, +}; + +struct spi_nand_device_cmd { + + /* + * Command and address. I/O errors have been observed if a + * separate spi_transfer is used for command and address, + * so keep them together. + */ + u32 n_cmd; + u8 cmd[5]; + + /* Tx data */ + u32 n_tx; + u8 *tx_buf; + + /* Rx data */ + u32 n_rx; + u8 *rx_buf; + u8 rx_nbits; + u8 tx_nbits; +}; + +struct spi_nand_device { + struct spi_nand spi_nand; + struct spi_device *spi; + + struct spi_nand_device_cmd cmd; +}; + +static int spi_nand_send_command(struct spi_device *spi, + struct spi_nand_device_cmd *cmd) +{ + struct spi_message message; + struct spi_transfer x[2]; + + if (!cmd->n_cmd) { + dev_err(&spi->dev, "cannot send an empty command\n"); + return -EINVAL; + } + + if (cmd->n_tx && cmd->n_rx) { + dev_err(&spi->dev, "cannot send and receive data at the same time\n"); + return -EINVAL; + } + + spi_message_init(&message); + memset(x, 0, sizeof(x)); + + /* Command and address */ + x[0].len = cmd->n_cmd; + x[0].tx_buf = cmd->cmd; + x[0].tx_nbits = cmd->tx_nbits; + spi_message_add_tail(&x[0], &message); + + /* Data to be transmitted */ + if (cmd->n_tx) { + x[1].len = cmd->n_tx; + x[1].tx_buf = cmd->tx_buf; + x[1].tx_nbits = cmd->tx_nbits; + spi_message_add_tail(&x[1], &message); + } + + /* Data to be received */ + if (cmd->n_rx) { + x[1].len = cmd->n_rx; + x[1].rx_buf = cmd->rx_buf; + x[1].rx_nbits = cmd->rx_nbits; + spi_message_add_tail(&x[1], &message); + } + + return spi_sync(spi, &message); +} + +static int spi_nand_device_reset(struct spi_nand *snand) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 1; + cmd->cmd[0] = SPI_NAND_RESET; + + dev_dbg(snand->dev, "%s\n", __func__); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_read_reg(struct spi_nand *snand, u8 opcode, u8 *buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 2; + cmd->cmd[0] = SPI_NAND_GET_FEATURE; + cmd->cmd[1] = opcode; + cmd->n_rx = 1; + cmd->rx_buf = buf; + + dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_write_reg(struct spi_nand *snand, u8 opcode, u8 *buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 2; + cmd->cmd[0] = SPI_NAND_SET_FEATURE; + cmd->cmd[1] = opcode; + cmd->n_tx = 1; + cmd->tx_buf = buf; + + dev_dbg(snand->dev, "%s: reg 0%x\n", __func__, opcode); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_write_enable(struct spi_nand *snand) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 1; + cmd->cmd[0] = SPI_NAND_WRITE_ENABLE; + + dev_dbg(snand->dev, "%s\n", __func__); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_write_disable(struct spi_nand *snand) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 1; + cmd->cmd[0] = SPI_NAND_WRITE_DISABLE; + + dev_dbg(snand->dev, "%s\n", __func__); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_write_page(struct spi_nand *snand, + unsigned int page_addr) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 4; + cmd->cmd[0] = SPI_NAND_PROGRAM_EXEC; + cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); + cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); + cmd->cmd[3] = (u8)(page_addr & 0xff); + + dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_store_cache(struct spi_nand *snand, + unsigned int page_offset, size_t length, + u8 *write_buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + struct spi_device *spi = snand_dev->spi; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 3; + cmd->cmd[0] = spi->mode & SPI_TX_QUAD ? SPI_NAND_PROGRAM_LOAD4 : + SPI_NAND_PROGRAM_LOAD; + cmd->cmd[1] = (u8)((page_offset & 0xff00) >> 8); + cmd->cmd[2] = (u8)(page_offset & 0xff); + cmd->n_tx = length; + cmd->tx_buf = write_buf; + cmd->tx_nbits = spi->mode & SPI_TX_QUAD ? 4 : 1; + + dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_load_page(struct spi_nand *snand, + unsigned int page_addr) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 4; + cmd->cmd[0] = SPI_NAND_PAGE_READ; + cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); + cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); + cmd->cmd[3] = (u8)(page_addr & 0xff); + + dev_dbg(snand->dev, "%s: page 0x%x\n", __func__, page_addr); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_read_cache(struct spi_nand *snand, + unsigned int page_offset, size_t length, + u8 *read_buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + struct spi_device *spi = snand_dev->spi; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + if ((spi->mode & SPI_RX_DUAL) || (spi->mode & SPI_RX_QUAD)) + cmd->n_cmd = 5; + else + cmd->n_cmd = 4; + cmd->cmd[0] = (spi->mode & SPI_RX_QUAD) ? SPI_NAND_READ_CACHE_X4 : + ((spi->mode & SPI_RX_DUAL) ? SPI_NAND_READ_CACHE_X2 : + SPI_NAND_READ_CACHE); + cmd->cmd[1] = 0; /* dummy byte */ + cmd->cmd[2] = (u8)((page_offset & 0xff00) >> 8); + cmd->cmd[3] = (u8)(page_offset & 0xff); + cmd->cmd[4] = 0; /* dummy byte */ + cmd->n_rx = length; + cmd->rx_buf = read_buf; + cmd->rx_nbits = (spi->mode & SPI_RX_QUAD) ? 4 : + ((spi->mode & SPI_RX_DUAL) ? 2 : 1); + + dev_dbg(snand->dev, "%s: offset 0x%x\n", __func__, page_offset); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_device_block_erase(struct spi_nand *snand, + unsigned int page_addr) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 4; + cmd->cmd[0] = SPI_NAND_BLOCK_ERASE; + cmd->cmd[1] = (u8)((page_addr & 0xff0000) >> 16); + cmd->cmd[2] = (u8)((page_addr & 0xff00) >> 8); + cmd->cmd[3] = (u8)(page_addr & 0xff); + + dev_dbg(snand->dev, "%s: block 0x%x\n", __func__, page_addr); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_gd5f_read_id(struct spi_nand *snand, u8 *buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 1; + cmd->cmd[0] = SPI_NAND_READ_ID; + cmd->n_rx = SPI_NAND_GD5F_READID_LEN; + cmd->rx_buf = buf; + + dev_dbg(snand->dev, "%s\n", __func__); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static int spi_nand_mt29f_read_id(struct spi_nand *snand, u8 *buf) +{ + struct spi_nand_device *snand_dev = snand->priv; + struct spi_nand_device_cmd *cmd = &snand_dev->cmd; + + memset(cmd, 0, sizeof(struct spi_nand_device_cmd)); + cmd->n_cmd = 1; + cmd->cmd[0] = SPI_NAND_READ_ID; + cmd->n_rx = SPI_NAND_MT29F_READID_LEN; + cmd->rx_buf = buf; + + dev_dbg(snand->dev, "%s\n", __func__); + + return spi_nand_send_command(snand_dev->spi, cmd); +} + +static void spi_nand_mt29f_ecc_status(unsigned int status, + unsigned int *corrected, + unsigned int *ecc_error) +{ + unsigned int ecc_status = (status >> SPI_NAND_MT29F_ECC_SHIFT) & + SPI_NAND_MT29F_ECC_MASK; + + *ecc_error = (ecc_status == SPI_NAND_MT29F_ECC_UNCORR) ? 1 : 0; + if (*ecc_error == 0) + *corrected = ecc_status; +} + +static void spi_nand_gd5f_ecc_status(unsigned int status, + unsigned int *corrected, + unsigned int *ecc_error) +{ + unsigned int ecc_status = (status >> SPI_NAND_GD5F_ECC_SHIFT) & + SPI_NAND_GD5F_ECC_MASK; + + *ecc_error = (ecc_status == SPI_NAND_GD5F_ECC_UNCORR) ? 1 : 0; + if (*ecc_error == 0) + *corrected = (ecc_status > 1) ? (2 + ecc_status) : 0; +} + +static int spi_nand_device_probe(struct spi_device *spi) +{ + enum spi_nand_device_variant variant; + struct spi_nand_device *priv; + struct spi_nand *snand; + int ret; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + snand = &priv->spi_nand; + + snand->read_cache = spi_nand_device_read_cache; + snand->load_page = spi_nand_device_load_page; + snand->store_cache = spi_nand_device_store_cache; + snand->write_page = spi_nand_device_write_page; + snand->write_reg = spi_nand_device_write_reg; + snand->read_reg = spi_nand_device_read_reg; + snand->block_erase = spi_nand_device_block_erase; + snand->reset = spi_nand_device_reset; + snand->write_enable = spi_nand_device_write_enable; + snand->write_disable = spi_nand_device_write_disable; + snand->dev = &spi->dev; + snand->priv = priv; + + /* + * gd5f reads three ID bytes, and mt29f reads one dummy address byte + * and two ID bytes. Therefore, we could detect both in the same + * read_id implementation by reading _with_ and _without_ a dummy byte, + * until a proper manufacturer is found. + * + * This'll mean we won't need to specify any specific compatible string + * for a given device, and instead just support spi-nand. + */ + variant = spi_get_device_id(spi)->driver_data; + switch (variant) { + case SPI_NAND_MT29F: + snand->read_id = spi_nand_mt29f_read_id; + snand->get_ecc_status = spi_nand_mt29f_ecc_status; + break; + case SPI_NAND_GD5F: + snand->read_id = spi_nand_gd5f_read_id; + snand->get_ecc_status = spi_nand_gd5f_ecc_status; + break; + default: + dev_err(snand->dev, "unknown device\n"); + return -ENODEV; + } + + spi_set_drvdata(spi, snand); + priv->spi = spi; + + ret = spi_nand_register(snand, spi_nand_flash_ids); + if (ret) + return ret; + return 0; +} + +static int spi_nand_device_remove(struct spi_device *spi) +{ + struct spi_nand *snand = spi_get_drvdata(spi); + + spi_nand_unregister(snand); + + return 0; +} + +const struct spi_device_id spi_nand_id_table[] = { + { "spi-nand", SPI_NAND_GENERIC }, + { "mt29f", SPI_NAND_MT29F }, + { "gd5f", SPI_NAND_GD5F }, + { }, +}; +MODULE_DEVICE_TABLE(spi, spi_nand_id_table); + +static struct spi_driver spi_nand_device_driver = { + .driver = { + .name = "spi_nand_device", + .owner = THIS_MODULE, + }, + .id_table = spi_nand_id_table, + .probe = spi_nand_device_probe, + .remove = spi_nand_device_remove, +}; +module_spi_driver(spi_nand_device_driver); + +MODULE_AUTHOR("Ezequiel Garcia "); +MODULE_DESCRIPTION("SPI NAND device support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index 2fe2a7e90fa9f2..752bdaacfa3750 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -41,4 +41,12 @@ config SPI_NXP_SPIFI Flash. Enable this option if you have a device with a SPIFI controller and want to access the Flash as a mtd device. +config MTD_SPI_NOR_WINBOND_OTP + bool "Support for winbond security register and unique ID" + depends on MTD_SPI_NOR + default n + help + This enables support for read/write of winbond security registers + as user OTP and also reading of NOR unique ID as factory OTP. + endif # MTD_SPI_NOR diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile index e53333ef85820f..86944d5090e760 100644 --- a/drivers/mtd/spi-nor/Makefile +++ b/drivers/mtd/spi-nor/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o +obj-$(CONFIG_MTD_SPI_NOR_WINBOND_OTP) += winbond-otp.o obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o diff --git a/drivers/mtd/spi-nor/spi-nor-common.h b/drivers/mtd/spi-nor/spi-nor-common.h new file mode 100644 index 00000000000000..ff15d4225a1a0d --- /dev/null +++ b/drivers/mtd/spi-nor/spi-nor-common.h @@ -0,0 +1,8 @@ +#ifndef SPI_NOR_COMMON_H +#define SPI_NOR_COMMON_H + +int spi_nor_wait_till_ready(struct spi_nor *nor); +int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops); +void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops); + +#endif diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 32477c4eb42139..0406cdc249a35e 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -22,6 +22,8 @@ #include #include #include +#include "winbond-otp.h" +#include "spi-nor-common.h" /* Define max times to check status register before we give up. */ @@ -68,6 +70,7 @@ struct flash_info { #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 WINBOND_OTP 0x100 /* use winbond security reg as OTP */ }; #define JEDEC_MFR(info) ((info)->id[0]) @@ -270,7 +273,7 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, return -ETIMEDOUT; } -static int spi_nor_wait_till_ready(struct spi_nor *nor) +int spi_nor_wait_till_ready(struct spi_nor *nor) { return spi_nor_wait_till_ready_with_timeout(nor, DEFAULT_READY_WAIT_JIFFIES); @@ -288,7 +291,7 @@ static int erase_chip(struct spi_nor *nor) return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0); } -static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops) +int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops) { int ret = 0; @@ -305,7 +308,7 @@ static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops) return ret; } -static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops) +void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops) { if (nor->unprepare) nor->unprepare(nor, ops); @@ -764,7 +767,7 @@ static const struct flash_info spi_nor_ids[] = { { "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) }, { "s25fl004k", INFO(0xef4013, 0, 64 * 1024, 8, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "s25fl008k", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, - { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | WINBOND_OTP) }, { "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, { "s25fl132k", INFO(0x014016, 0, 64 * 1024, 64, SECT_4K) }, { "s25fl164k", INFO(0x014017, 0, 64 * 1024, 128, SECT_4K) }, @@ -1152,6 +1155,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode) struct device *dev = nor->dev; struct mtd_info *mtd = &nor->mtd; struct device_node *np = nor->flash_node; + const char __maybe_unused *of_mtd_name = NULL; int ret; int i; @@ -1205,7 +1209,12 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode) write_sr(nor, 0); } - if (!mtd->name) +#ifdef CONFIG_MTD_OF_PARTS + of_property_read_string(np, "linux,mtd-name", &of_mtd_name); +#endif + if (of_mtd_name) + mtd->name = of_mtd_name; + else if (!mtd->name) mtd->name = dev_name(dev); mtd->priv = nor; mtd->type = MTD_NORFLASH; @@ -1228,6 +1237,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode) mtd->_is_locked = spi_nor_is_locked; } + if (info->flags & WINBOND_OTP) + winbond_otp_register(mtd); + /* sst nor chips use AAI word program */ if (info->flags & SST_WRITE) mtd->_write = sst_write; diff --git a/drivers/mtd/spi-nor/winbond-otp.c b/drivers/mtd/spi-nor/winbond-otp.c new file mode 100644 index 00000000000000..0f1a7bfd2663c4 --- /dev/null +++ b/drivers/mtd/spi-nor/winbond-otp.c @@ -0,0 +1,482 @@ +/* + * Imagination Technologies + * + * Copyright (c) 2015 Imagination Technologies Ltd. + * + * 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. + * + * This driver provides read/write access to the 3 x 256 bytes security + * registers as user OTP and unique ID of the NOR can be read as factory OTP + * + */ + +#include +#include +#include +#include +#include +#include +#include "spi-nor-common.h" + +#define SECURITY_REG_START_ADDR 0x1000 /*first security register addr*/ +#define SECURITY_REG_ADDR_OFFSET 0x1000 /*diff between consecutive reg*/ +#define SECURITY_REG_NUM 3 /* number of security registers */ +#define SECURITY_REG_SIZE 256 /* bytes per security register */ +#define SECURITY_REG_TOTAL_SIZE (SECURITY_REG_NUM * SECURITY_REG_SIZE) +#define SPI_NOR_UNIQUE_ID_LEN 8 /*number of bytes of unique ID */ + +/* SPI FLASH opcodes */ +#define SPINOR_OP_RD_SR2 0x35 /* Read status register 2 */ +#define SPINOR_OP_PR_SECURITY_REG 0x42 /* Program security register */ +#define SPINOR_OP_ER_SECURITY_REG 0x44 /* Erase security register */ +#define SPINOR_OP_RD_SECURITY_REG 0x48 /* Read security register */ +#define SPINOR_OP_RD_UNIQUE_ID 0x4B /* Read unique id */ + +/* Status register 2 */ +#define SR2_LB1_BIT 3 /* security register lock bit 1 */ + +/* Get start addr of the security reg*/ +#define SEC_REG_START_ADDR(addr) (addr & 0x3000) + +static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd) +{ + return mtd->priv; +} + +static inline int write_enable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0); +} + +static inline int write_disable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0); +} + +static int read_sr(struct spi_nor *nor, u8 opcode, u8 *val) +{ + int ret; + + ret = nor->read_reg(nor, opcode, val, 1); + if (ret < 0) + pr_err("error %d reading SR\n", ret); + return ret; +} + +/* + * Converts address range + * 0 - 0xFF -> 0x1000 - 0x10FF + * 0x100 - 0x1FF -> 0x2000 - 0x20FF + * 0x200 - 0x2FF -> 0x3000 - 0x30FF + * + * This func assumes that sanity checks on addr are done and is in valid range + */ +static loff_t translate_addr(loff_t addr) +{ + int i; + loff_t new_addr = SECURITY_REG_START_ADDR; + + for (i = 0; i < SECURITY_REG_NUM; i++) { + if (addr < ((i+1)*SECURITY_REG_SIZE)) { + new_addr |= addr & (SECURITY_REG_SIZE-1); + break; + } + new_addr += SECURITY_REG_ADDR_OFFSET; + } + + return new_addr; +} + +/* + * Return 3 blocks of 256 bytes security register as user OTP, + * address of these blocks will be 0, 0x100, 0x200 + * driver will convert these address to actual address while doing + * read/write + */ +static int winbond_get_user_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, + struct otp_info *otpinfo) +{ + u8 val; + int i, ret; + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + mutex_lock(&nor->lock); + ret = read_sr(nor, SPINOR_OP_RD_SR2, &val); + mutex_unlock(&nor->lock); + + if (ret < 0) + return ret; + + for (i = 0; i < SECURITY_REG_NUM; i++) { + otpinfo[i].start = i * SECURITY_REG_SIZE; + otpinfo[i].length = SECURITY_REG_SIZE; + otpinfo[i].locked = !!(val & BIT(SR2_LB1_BIT + i)); + } + + *retlen = SECURITY_REG_NUM * sizeof(*otpinfo); + + return 0; +} + +static int spi_otp_read(struct spi_nor *nor, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + struct spi_nor_xfer_cfg cfg = { + .cmd = SPINOR_OP_RD_SECURITY_REG, + .addr = from, + .addr_width = nor->addr_width, + .mode = SPI_NOR_NORMAL, + .dummy_cycles = 8, + }; + + return nor->read_xfer(nor, &cfg, buf, len, retlen); +} + +static int spi_otp_write(struct spi_nor *nor, loff_t to, + size_t len, size_t *retlen, u_char *buf) +{ + struct spi_nor_xfer_cfg cfg = { + .cmd = SPINOR_OP_PR_SECURITY_REG, + .addr = to, + .addr_width = nor->addr_width, + .mode = SPI_NOR_NORMAL, + }; + + return nor->write_xfer(nor, &cfg, buf, len, retlen); +} + +static int spi_otp_erase(struct spi_nor *nor, loff_t offs) +{ + size_t temp_retlen; + struct spi_nor_xfer_cfg cfg = { + .cmd = SPINOR_OP_ER_SECURITY_REG, + .addr = offs, + .addr_width = nor->addr_width, + .mode = SPI_NOR_NORMAL, + }; + + return nor->write_xfer(nor, &cfg, NULL, 0, &temp_retlen); +} + +static int spi_read_uniqueid(struct spi_nor *nor, u8 *buf) +{ + size_t temp_retlen; + struct spi_nor_xfer_cfg cfg = { + .cmd = SPINOR_OP_RD_UNIQUE_ID, + .addr_width = 0, + .mode = SPI_NOR_NORMAL, + .dummy_cycles = 32, + }; + + return nor->read_xfer(nor, &cfg, buf, SPI_NOR_UNIQUE_ID_LEN, + &temp_retlen); +} + + +static int winbond_read_user_otp(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + int ret; + u32 i, read_len, end_addr, sreg_offset; + loff_t temp_addr; + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + *retlen = 0; + + if (from < 0 || from >= SECURITY_REG_TOTAL_SIZE + || (from + len) > SECURITY_REG_TOTAL_SIZE) + return -EINVAL; + + if (!len) + return 0; + + end_addr = from + len; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ); + if (ret) + return ret; + + for (i = from; i < end_addr; i += read_len) { + sreg_offset = i & (SECURITY_REG_SIZE-1); + /* if offset not on boundary, read first few bytes */ + if (sreg_offset) { + /* check if everything has to be read from 1 reg */ + if ((sreg_offset + len) <= SECURITY_REG_SIZE) + read_len = len; + else + read_len = SECURITY_REG_SIZE - sreg_offset; + } + /* if it is last chunk, read the remaining bytes */ + else if ((end_addr - i) < SECURITY_REG_SIZE) + read_len = end_addr - i; + else + read_len = SECURITY_REG_SIZE; + + temp_addr = translate_addr(i); + ret = spi_otp_read(nor, temp_addr, read_len, retlen, + buf + (i-from)); + if (ret < 0) + goto error; + } +error: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); + return ret; +} + +/* + * This func assumes that offset is within valid range of security registers, + * valid offset are 0x1000, 0x2000 or 0x3000 + */ +static int winbond_erase_security_reg(struct spi_nor *nor, loff_t offset) +{ + int ret; + + ret = write_enable(nor); + if (ret < 0) + return ret; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + return ret; + + ret = spi_otp_erase(nor, offset); + if (ret < 0) + return ret; + + ret = spi_nor_wait_till_ready(nor); + + return ret; +} + +/* + * This function does read, modify locally, erase and write to the register to + * be written + * It doesn't do any range checks on reg_addr, sreg_offset, len + */ +static int winbond_write_security_reg(struct spi_nor *nor, loff_t reg_addr, + u32 sreg_offset, size_t len, + size_t *retlen, u_char *buf) +{ + int ret; + size_t temp_retlen = 0; + u8 *reg_buffer; + + if (unlikely(sreg_offset + len > SECURITY_REG_SIZE)) + return -EINVAL; + + reg_buffer = kmalloc(SECURITY_REG_SIZE, GFP_KERNEL); + if (!reg_buffer) + return -ENOMEM; + + /* read the security register */ + ret = spi_otp_read(nor, reg_addr, SECURITY_REG_SIZE, &temp_retlen, + reg_buffer); + if (ret < 0 || temp_retlen != SECURITY_REG_SIZE) + goto error; + + /* modify the part to be written */ + memcpy(reg_buffer + sreg_offset, buf, len); + + /* erase the security register */ + ret = winbond_erase_security_reg(nor, reg_addr); + if (ret < 0) + goto error; + + /* write the security reg*/ + ret = write_enable(nor); + if (ret < 0) + goto error; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto error; + + temp_retlen = 0; + + ret = spi_otp_write(nor, reg_addr, SECURITY_REG_SIZE, &temp_retlen, + reg_buffer); + if (ret < 0 || temp_retlen != SECURITY_REG_SIZE) + goto error; + + ret = spi_nor_wait_till_ready(nor); + + *retlen += len; + +error: + kfree(reg_buffer); + return ret; +} + +static int winbond_write_user_otp(struct mtd_info *mtd, loff_t to, + size_t len, size_t *retlen, u_char *buf) +{ + int ret; + u32 i, write_len, end_addr, sreg_offset; + loff_t temp_addr; + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + *retlen = 0; + + if (to < 0 || to >= SECURITY_REG_TOTAL_SIZE + || (to + len) > SECURITY_REG_TOTAL_SIZE) + return -EINVAL; + + if (!len) + return 0; + + end_addr = to + len; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE); + if (ret) + return ret; + + for (i = to; i < end_addr; i += write_len) { + sreg_offset = i & (SECURITY_REG_SIZE-1); + /* if offset not on boundary, write first few bytes */ + if (sreg_offset) { + /* check if everything has to be written in 1 reg */ + if ((sreg_offset + len) <= SECURITY_REG_SIZE) + write_len = len; + else + write_len = SECURITY_REG_SIZE - sreg_offset; + } + /* if it is last chunk, write the remaining bytes */ + else if ((end_addr - i) < SECURITY_REG_SIZE) + write_len = end_addr - i; + else + write_len = SECURITY_REG_SIZE; + + temp_addr = translate_addr(i); + ret = winbond_write_security_reg(nor, + SEC_REG_START_ADDR(temp_addr), + sreg_offset, write_len, + retlen, buf + (i-to)); + if (ret < 0) + goto error; + } + +error: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); + return ret; +} + +static int winbond_lock_user_otp(struct mtd_info *mtd, loff_t from, size_t len) +{ + int ret; + u8 sr1, sr2, security_reg_num; + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + /* allow locking 1 register at a time, + * so ensure that len is 256 + * also check if address is on security register boundary + */ + if (len != SECURITY_REG_SIZE || from < 0 + || from >= SECURITY_REG_TOTAL_SIZE + || from & (SECURITY_REG_SIZE - 1)) + return -EINVAL; + + /* find out the security reg to set */ + security_reg_num = from / SECURITY_REG_SIZE; + + if (unlikely(security_reg_num > (SECURITY_REG_NUM-1))) + return -EINVAL; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK); + if (ret) + return ret; + + /* read status registers */ + ret = read_sr(nor, SPINOR_OP_RDSR, &sr1); + if (ret < 0) + goto error; + + ret = read_sr(nor, SPINOR_OP_RD_SR2, &sr2); + if (ret < 0) + goto error; + + ret = write_enable(nor); + if (ret < 0) + goto error; + + /* set the corresponding LB bit in security register 2 */ + sr2 |= BIT(SR2_LB1_BIT + security_reg_num); + + /* write status registers */ + nor->cmd_buf[0] = sr1; + nor->cmd_buf[1] = sr2; + ret = nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 2); + + write_disable(nor); + +error: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); + return ret; +} + +/* + * Unique ID of NOR device will be reported as factory OTP + */ +static int winbond_get_fact_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, + struct otp_info *otpinfo) +{ + otpinfo->start = 0; + otpinfo->length = SPI_NOR_UNIQUE_ID_LEN; + otpinfo->locked = 1; + + *retlen = sizeof(*otpinfo); + + return 0; +} + +static int winbond_read_fact_otp(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + int ret; + + char unique_id[SPI_NOR_UNIQUE_ID_LEN] = {0}; + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + *retlen = 0; + + if (from < 0 || from >= SPI_NOR_UNIQUE_ID_LEN + || (from + len) > SPI_NOR_UNIQUE_ID_LEN) + return -EINVAL; + + if (!len) + return 0; + + ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ); + if (ret) + return ret; + + ret = spi_read_uniqueid(nor, unique_id); + if (ret < 0) + goto error; + + /* Read complete unique ID,but just copy whatever is requested */ + memcpy(buf, unique_id + from, len); + *retlen = len; +error: + spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); + return ret; +} + +void winbond_otp_register(struct mtd_info *mtd) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + + if (nor->read_xfer && nor->write_xfer) { + mtd->_get_user_prot_info = winbond_get_user_otp_info; + mtd->_read_user_prot_reg = winbond_read_user_otp; + mtd->_write_user_prot_reg = winbond_write_user_otp; + mtd->_lock_user_prot_reg = winbond_lock_user_otp; + mtd->_get_fact_prot_info = winbond_get_fact_otp_info; + mtd->_read_fact_prot_reg = winbond_read_fact_otp; + } else + dev_err(nor->dev, "Required nor interfaces " + "(read_xfer, write_xfer) not defined\n"); +} diff --git a/drivers/mtd/spi-nor/winbond-otp.h b/drivers/mtd/spi-nor/winbond-otp.h new file mode 100644 index 00000000000000..29cbbcc25d9b25 --- /dev/null +++ b/drivers/mtd/spi-nor/winbond-otp.h @@ -0,0 +1,20 @@ +/* + * Imagination Technologies + * + * Copyright (c) 2015 Imagination Technologies Ltd. + * + * 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 WINBOND_OTP_H +#define WINBOND_OTP_H + +#ifdef CONFIG_MTD_SPI_NOR_WINBOND_OTP +void winbond_otp_register(struct mtd_info *mtd); +#else +static inline void winbond_otp_register(struct mtd_info *mtd) { return; } +#endif + +#endif diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a5b869eb46783e..af8126fefe1200 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1701,7 +1701,8 @@ static void stmmac_init_tx_coalesce(struct stmmac_priv *priv) * 0 on success and an appropriate (-)ve integer as defined in errno.h * file on failure. */ -static int stmmac_hw_setup(struct net_device *dev, bool init_ptp) +static int stmmac_hw_setup(struct net_device *dev, bool init_ptp, + bool init_debugfs) { struct stmmac_priv *priv = netdev_priv(dev); int ret; @@ -1745,9 +1746,11 @@ static int stmmac_hw_setup(struct net_device *dev, bool init_ptp) } #ifdef CONFIG_DEBUG_FS - ret = stmmac_init_fs(dev); - if (ret < 0) - pr_warn("%s: failed debugFS registration\n", __func__); + if (init_debugfs) { + ret = stmmac_init_fs(dev); + if (ret < 0) + pr_warn("%s: failed debugFS registration\n", __func__); + } #endif /* Start the ball rolling... */ pr_debug("%s: DMA RX/TX processes started...\n", dev->name); @@ -1819,7 +1822,7 @@ static int stmmac_open(struct net_device *dev) goto init_error; } - ret = stmmac_hw_setup(dev, true); + ret = stmmac_hw_setup(dev, true, true); if (ret < 0) { pr_err("%s: Hw setup failed\n", __func__); goto init_error; @@ -3029,8 +3032,11 @@ int stmmac_suspend(struct net_device *ndev) struct stmmac_priv *priv = netdev_priv(ndev); unsigned long flags; - if (!ndev || !netif_running(ndev)) + if (!ndev || !netif_running(ndev)) { + clk_disable(priv->pclk); + clk_disable(priv->stmmac_clk); return 0; + } if (priv->phydev) phy_stop(priv->phydev); @@ -3046,6 +3052,10 @@ int stmmac_suspend(struct net_device *ndev) priv->hw->dma->stop_tx(priv->ioaddr); priv->hw->dma->stop_rx(priv->ioaddr); + /* Release the DMA TX/RX socket buffers */ + dma_free_rx_skbufs(priv); + dma_free_tx_skbufs(priv); + /* Enable Power down mode by programming the PMT regs */ if (device_may_wakeup(priv->device)) { priv->hw->mac->pmt(priv->hw, priv->wolopts); @@ -3077,8 +3087,11 @@ int stmmac_resume(struct net_device *ndev) struct stmmac_priv *priv = netdev_priv(ndev); unsigned long flags; - if (!netif_running(ndev)) + if (!netif_running(ndev)) { + clk_enable(priv->stmmac_clk); + clk_enable(priv->pclk); return 0; + } spin_lock_irqsave(&priv->lock, flags); @@ -3109,7 +3122,7 @@ int stmmac_resume(struct net_device *ndev) priv->cur_tx = 0; stmmac_clear_descriptors(priv); - stmmac_hw_setup(ndev, false); + stmmac_hw_setup(ndev, false, false); stmmac_init_tx_coalesce(priv); stmmac_set_rx_mode(ndev); diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c index e65b60591317ba..04311b9aaedb0a 100644 --- a/drivers/net/ieee802154/cc2520.c +++ b/drivers/net/ieee802154/cc2520.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,11 @@ #define CC2520_RAM_SIZE 640 #define CC2520_FIFO_SIZE 128 +#define CC2520_CRYSTAL_FREQ 32000000 +#define CC2520_EXTCLOCK_DEFAULT_FREQ 1000000 +#define CC2520_EXTCLOCK_MAX_FREQ 16000000 +#define CC2520_EXTCLOCK_MIN_FREQ 1000000 + #define CC2520RAM_TXFIFO 0x100 #define CC2520RAM_RXFIFO 0x180 #define CC2520RAM_IEEEADDR 0x3EA @@ -48,6 +54,10 @@ #define CC2520_STATUS_RSSI_VALID BIT(6) #define CC2520_STATUS_TX_UNDERFLOW BIT(3) +/* extclock reg */ +#define CC2520_EXTCLOCK_ENABLE BIT(5) +#define CC2520_EXTCLOCK_MAX_DIV_FACTOR 32 + /* IEEE-802.15.4 defined constants (2.4 GHz logical channels) */ #define CC2520_MINCHANNEL 11 #define CC2520_MAXCHANNEL 26 @@ -201,6 +211,8 @@ struct cc2520_private { struct work_struct fifop_irqwork;/* Workqueue for FIFOP */ spinlock_t lock; /* Lock for is_tx*/ struct completion tx_complete; /* Work completion for Tx */ + bool started; /* Flag to know if device is up */ + struct clk *clk; /* external clock */ }; /* Generic Functions */ @@ -846,20 +858,60 @@ static int cc2520_get_platform_data(struct spi_device *spi, pdata->cca = of_get_named_gpio(np, "cca-gpio", 0); pdata->vreg = of_get_named_gpio(np, "vreg-gpio", 0); pdata->reset = of_get_named_gpio(np, "reset-gpio", 0); - /* CC2591 front end for CC2520 */ if (of_property_read_bool(np, "amplified")) priv->amplified = true; + if (of_property_read_u32(np, "extclock-freq", &pdata->extclockfreq)) { + /* if extclock-freq is not specified, + * default to 1MHz(reset value) + */ + pdata->extclockfreq = CC2520_EXTCLOCK_DEFAULT_FREQ; + } else + pdata->registerclk = true; return 0; } +static int cc2520_register_clk(struct spi_device *spi, + struct cc2520_platform_data *pdata) +{ + struct device_node *np = spi->dev.of_node; + struct cc2520_private *priv = spi_get_drvdata(spi); + int ret = 0; + + if (pdata->registerclk) { + if (np) { + priv->clk = clk_register_fixed_rate(&spi->dev, np->name, + NULL, CLK_IS_ROOT, pdata->extclockfreq); + + if (!IS_ERR(priv->clk)) { + ret = of_clk_add_provider(np, + of_clk_src_simple_get, + priv->clk); + if (ret) { + clk_unregister(priv->clk); + dev_err(&spi->dev, + "Failed to add clk provider\n"); + } + } else { + dev_err(&spi->dev, "Failed to register clk\n"); + ret = PTR_ERR(priv->clk); + } + } else + dev_err(&spi->dev, "No device node found, ext-clk won't" + " be registered\n"); + } + + return ret; +} + static int cc2520_hw_init(struct cc2520_private *priv) { u8 status = 0, state = 0xff; int ret; int timeout = 100; struct cc2520_platform_data pdata; + u8 extclock_reg; ret = cc2520_get_platform_data(priv->spi, &pdata); if (ret) @@ -971,6 +1023,27 @@ static int cc2520_hw_init(struct cc2520_private *priv) if (ret) goto err_ret; + /* Configure EXTCLOCK register based on 'extclock-freq' property */ + if (pdata.extclockfreq == 0) { + extclock_reg = 0; + } else if (pdata.extclockfreq >= CC2520_EXTCLOCK_MIN_FREQ && + pdata.extclockfreq <= CC2520_EXTCLOCK_MAX_FREQ) { + extclock_reg = (CC2520_EXTCLOCK_ENABLE | + (CC2520_EXTCLOCK_MAX_DIV_FACTOR - + (DIV_ROUND_CLOSEST(CC2520_CRYSTAL_FREQ, + pdata.extclockfreq)))); + } else { + dev_err(&priv->spi->dev, "Invalid value specified for 'extclock-freq'\n"); + ret = -EINVAL; + goto err_ret; + } + + ret = cc2520_write_register(priv, CC2520_EXTCLOCK, extclock_reg); + if (ret) { + dev_err(&priv->spi->dev, "Failed to write 'extclock-freq' into CC2520_EXTCLOCK\n"); + goto err_ret; + } + return 0; err_ret: @@ -1115,8 +1188,16 @@ static int cc2520_probe(struct spi_device *spi) if (ret) goto err_hw_init; + ret = cc2520_register_clk(spi, &pdata); + if (ret) + goto err_free_device; + return 0; +err_free_device: + ieee802154_unregister_hw(priv->hw); + ieee802154_free_hw(priv->hw); + err_hw_init: mutex_destroy(&priv->buffer_mutex); flush_work(&priv->fifop_irqwork); @@ -1127,6 +1208,11 @@ static int cc2520_remove(struct spi_device *spi) { struct cc2520_private *priv = spi_get_drvdata(spi); + if (priv->clk) { + of_clk_del_provider(spi->dev.of_node); + clk_unregister(priv->clk); + } + mutex_destroy(&priv->buffer_mutex); flush_work(&priv->fifop_irqwork); diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index f9f94229bf1ba5..0187acc667ad64 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -285,5 +285,6 @@ source "drivers/net/wireless/zd1211rw/Kconfig" source "drivers/net/wireless/mwifiex/Kconfig" source "drivers/net/wireless/cw1200/Kconfig" source "drivers/net/wireless/rsi/Kconfig" +source "drivers/net/wireless/uccp420wlan/Kconfig" endif # WLAN diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 740fdd353c5ddc..1b34b48b7f6e7c 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_BRCMSMAC) += brcm80211/ obj-$(CONFIG_CW1200) += cw1200/ obj-$(CONFIG_RSI_91X) += rsi/ +obj-$(CONFIG_UCCP420WLAN) += uccp420wlan/ diff --git a/drivers/net/wireless/uccp420wlan/COPYING b/drivers/net/wireless/uccp420wlan/COPYING new file mode 100755 index 00000000000000..ca442d313d86dc --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/COPYING @@ -0,0 +1,356 @@ + + NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". + Also note that the GPL below is copyrighted by the Free Software + Foundation, but the instance of code that it refers to (the Linux + kernel) is copyrighted by me and others who actually wrote it. + + Also note that the only valid version of the GPL as far as the kernel + is concerned is _this_ particular version of the license (ie v2, not + v2.2 or v3.x or whatever), unless explicitly otherwise stated. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (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, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/drivers/net/wireless/uccp420wlan/Kconfig b/drivers/net/wireless/uccp420wlan/Kconfig new file mode 100644 index 00000000000000..b82304fbdc8254 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/Kconfig @@ -0,0 +1,10 @@ +# +# Imagination Wireless drivers +# + +config UCCP420WLAN + tristate "Imagination UCCP420 WLAN" + depends on MAC80211 && CFG80211 + + ---help--- + Imagination technologies UCCP420 WLAN driver diff --git a/drivers/net/wireless/uccp420wlan/Makefile b/drivers/net/wireless/uccp420wlan/Makefile new file mode 100644 index 00000000000000..d9d0c16fa8f6a7 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/Makefile @@ -0,0 +1,6 @@ +uccp420wlan-objs := src/80211_if.o src/core.o src/umac_if.o src/tx.o src/hal_hostport.o src/fwldr.o +ccflags-y += -DMULTI_CHAN_SUPPORT +obj-$(CONFIG_UCCP420WLAN) += uccp420wlan.o +ccflags-y += -I$(src)/inc + + diff --git a/drivers/net/wireless/uccp420wlan/inc/core.h b/drivers/net/wireless/uccp420wlan/inc/core.h new file mode 100755 index 00000000000000..35cd4cdccd4b30 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/core.h @@ -0,0 +1,891 @@ +/* + * File Name : core.h + * + * This file contains the declarations of structures that will + * be used by core, tx and rx code + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420WLAN_CORE_H_ +#define _UCCP420WLAN_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "host_umac_if.h" +#include "umac_if.h" +#include "descriptors.h" + +extern unsigned int vht_support; +extern struct cmd_send_recv_cnt cmd_info; +extern int uccp_debug; + +#ifdef CONFIG_PM +extern unsigned char img_suspend_status; +extern unsigned char rx_interrupt_status; +#endif + + +#define UCCP_DEBUG_TX(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_TX) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_SCAN(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_SCAN) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_ROC(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_ROC) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_TSMC(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_TSMC) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#ifdef CONFIG_NL80211_TESTMODE +#define MAX_NL_DUMP_LEN (PAGE_SIZE-1024) +/* This section contains example code for using netlink + * attributes with the testmode command in nl80211. + */ + +/* These enums need to be kept in sync with userspace */ +enum rpu_testmode_attr { + __RPU_TM_ATTR_INVALID = 0, + RPU_TM_ATTR_CMD = 1, + RPU_TM_ATTR_DUMP = 2, + /* keep last */ + __RPU_TM_ATTR_AFTER_LAST, + RPU_TM_ATTR_MAX = __RPU_TM_ATTR_AFTER_LAST - 1 +}; + +enum rpu_testmode_cmd { + RPU_TM_CMD_ALL = 0, + RPU_TM_CMD_GRAM = 1, + RPU_TM_CMD_COREA = 2, + RPU_TM_CMD_COREB = 3, + RPU_TM_CMD_PERIP = 4, + RPU_TM_CMD_SYSBUS = 5, +}; + +#endif +extern unsigned int system_rev; + +#ifdef PERF_PROFILING +extern unsigned long irq_timestamp[20]; +extern unsigned int irq_ts_index; +extern spinlock_t timing_lock; +#endif + +extern struct platform_driver img_uccp_driver; +extern unsigned char vif_macs[2][ETH_ALEN]; + +extern spinlock_t tsf_lock; + +#ifdef DRIVER_DEBUG +#define DEBUG_LOG(fmt, args...) pr_debug(fmt, ##args) +#else +#define DEBUG_LOG(...) do { } while (0) +#endif + + +#define MAX_OUTSTANDING_CTRL_REQ 2 +#define RESET_TIMEOUT 5000 /* In milli-seconds*/ +#define RESET_TIMEOUT_TICKS msecs_to_jiffies(RESET_TIMEOUT) +/*100: For ROC, 500: For initial*/ +#define CH_PROG_TIMEOUT 500 /* In milli-seconds*/ +#define CH_PROG_TIMEOUT_TICKS msecs_to_jiffies(CH_PROG_TIMEOUT) + +#define QUEUE_FLUSH_TIMEOUT 2000 /* Specify delay in milli-seconds*/ +#define QUEUE_FLUSH_TIMEOUT_TICKS msecs_to_jiffies(QUEUE_FLUSH_TIMEOUT) + +#ifdef CONFIG_PM +#define PS_ECON_CFG_TIMEOUT 1000 +#define PS_ECON_CFG_TIMEOUT_TICKS msecs_to_jiffies(PS_ECON_CFG_TIMEOUT) +#endif + +#define TX_COMPLETE_TIMEOUT 1000 /* In milli-seconds*/ +#define TX_COMPLETE_TIMEOUT_TICKS msecs_to_jiffies(TX_COMPLETE_TIMEOUT) +#define SCAN_ABORT_TIMEOUT 1000 +#define SCAN_ABORT_TIMEOUT_TICKS msecs_to_jiffies(SCAN_ABORT_TIMEOUT) +#define CANCEL_HW_ROC_TIMEOUT 1000 +#define CANCEL_HW_ROC_TIMEOUT_TICKS msecs_to_jiffies(CANCEL_HW_ROC_TIMEOUT) + +#define DEFAULT_TX_ANT_SELECT 3 /* bitmap of antennas for tx, 3=> both first and + * second antenna to be used + */ +#define DEFAULT_TX_POWER 15 +#define DEFAULT_RTS_THRESHOLD 2347 +#define SUPPORTED_FILTERS (FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC) +#define TX_DESC_BUCKET_BOUND 32 + +#define MAX_DATA_SIZE (0) /* Defined in HAL (or) can be configured from proc */ +#define MAX_TX_QUEUE_LEN 192 +#define MAX_AUX_ADC_SAMPLES 10 + +#define MAX_TX_STREAMS 2 /* Maximum number of Tx streams supported */ +#define MAX_RX_STREAMS 2 /* Maximum number of RX streams supported */ + +#define MAX_RSSI_SAMPLES 10 +#define UCCP_DBG_DEFAULT 0 + +#define CLOCK_MASK 0x3FFFFFFF +#define TICK_NUMRATOR 12288 /* 12288 KHz */ +#define TICK_DENOMINATOR 1000 /* 1000 KHz */ + +#define BTS_AP_24GHZ_ETS 195 /* Microsecs */ +#define BTS_AP_5GHZ_ETS 25 /* Microsecs */ + + +enum noa_triggers { + FROM_TX = 0, + FROM_TX_DONE, + FROM_EVENT_NOA +}; + +enum uccp420_hw_scan_status { + HW_SCAN_STATUS_NONE, + HW_SCAN_STATUS_PROGRESS +}; + +enum uccp_debug { + UCCP_DEBUG_SCAN = BIT(0), + UCCP_DEBUG_ROC = BIT(1), + UCCP_DEBUG_TX = BIT(2), + UCCP_DEBUG_CORE = BIT(3), + UCCP_DEBUG_IF = BIT(4), + UCCP_DEBUG_80211IF = BIT(5), + UCCP_DEBUG_RX = BIT(6), + UCCP_DEBUG_HAL = BIT(7), + UCCP_DEBUG_CRYPTO = BIT(8), + UCCP_DEBUG_DUMP_RX = BIT(9), + UCCP_DEBUG_DUMP_HAL = BIT(10), + UCCP_DEBUG_TSMC = BIT(11), +}; + +struct wifi_sync { + unsigned int status; + unsigned char ts1[8]; + unsigned long long atu; + unsigned char bssid[8]; + unsigned char name[10]; + unsigned int ts2; +}; + +struct wifi_params { + int ed_sensitivity; + int num_vifs; + int tx_fixed_rate; + int tx_fixed_mcs_indx; + int mgd_mode_tx_fixed_rate; + int mgd_mode_tx_fixed_mcs_indx; + unsigned int peer_ampdu_factor; + unsigned char is_associated; + unsigned char rate_protection_type; + unsigned char num_spatial_streams; + unsigned char enable_early_agg_checks; + unsigned char uccp_num_spatial_streams; + unsigned char auto_sensitivity; + /*RF Params: Input to the RF for operation*/ + unsigned char rf_params[RF_PARAMS_SIZE]; + unsigned char rf_params_vpd[RF_PARAMS_SIZE]; + /*Calibration Params: Input for different calibrations in RF*/ + unsigned char production_test; + unsigned int dot11a_support; + unsigned int dot11g_support; + unsigned int chnl_bw; + unsigned int prod_mode_chnl_bw_40_mhz; + unsigned int sec_ch_offset_40_plus; + unsigned int sec_ch_offset_40_minus; + unsigned int prod_mode_rate_flag; + unsigned int prod_mode_rate_preamble_type; + unsigned int prod_mode_stbc_enabled; + unsigned int prod_mode_bcc_or_ldpc; + unsigned int max_tx_streams; + unsigned int max_rx_streams; + unsigned int max_data_size; + unsigned int disable_power_save; + unsigned int disable_sm_power_save; + unsigned int max_tx_cmds; + unsigned int prod_mode_chnl_bw_80_mhz; + unsigned int sec_40_ch_offset_80_plus; + unsigned int sec_40_ch_offset_80_minus; +#ifdef PERF_PROFILING + unsigned int driver_tput; +#endif + unsigned int disable_beacon_ibss; + unsigned int vht_beamform_enable; + unsigned int vht_beamform_period; + unsigned int vht_beamform_support; + unsigned char bg_scan_channel_list[50]; + unsigned char bg_scan_channel_flags[50]; + unsigned int bg_scan_enable; + unsigned int bg_scan_intval; + unsigned int bg_scan_chan_dur; + unsigned int bg_scan_serv_chan_dur; + unsigned int bg_scan_num_channels; + unsigned int nw_selection; + unsigned int hw_scan_status; + unsigned int scan_type; + unsigned int set_tx_power; + unsigned int aux_adc_chain_id; + unsigned char pdout_voltage[MAX_AUX_ADC_SAMPLES]; + char rssi_average[MAX_RSSI_SAMPLES]; + unsigned int extra_scan_ies; + unsigned int fw_loading; + struct wifi_sync sync[MAX_VIFS]; + unsigned int bt_state; + unsigned int antenna_sel; + int pkt_gen_val; + int init_pkt_gen; + int payload_length; + int start_prod_mode; + int init_prod; + unsigned char bypass_vpd; + unsigned int cont_tx; +}; + +struct cmd_send_recv_cnt { + int tx_cmd_send_count; + int tx_done_recv_count; + int total_cmd_send_count; + unsigned int outstanding_ctrl_req; + unsigned long control_path_flags; + spinlock_t control_path_lock; + struct sk_buff_head outstanding_cmd; +}; + +struct wifi_stats { + unsigned int ht_tx_mcs0_packet_count; + unsigned int ht_tx_mcs1_packet_count; + unsigned int ht_tx_mcs2_packet_count; + unsigned int ht_tx_mcs3_packet_count; + unsigned int ht_tx_mcs4_packet_count; + unsigned int ht_tx_mcs5_packet_count; + unsigned int ht_tx_mcs6_packet_count; + unsigned int ht_tx_mcs7_packet_count; + unsigned int ht_tx_mcs8_packet_count; + unsigned int ht_tx_mcs9_packet_count; + unsigned int ht_tx_mcs10_packet_count; + unsigned int ht_tx_mcs11_packet_count; + unsigned int ht_tx_mcs12_packet_count; + unsigned int ht_tx_mcs13_packet_count; + unsigned int ht_tx_mcs14_packet_count; + unsigned int ht_tx_mcs15_packet_count; + unsigned int vht_tx_mcs0_packet_count; + unsigned int vht_tx_mcs1_packet_count; + unsigned int vht_tx_mcs2_packet_count; + unsigned int vht_tx_mcs3_packet_count; + unsigned int vht_tx_mcs4_packet_count; + unsigned int vht_tx_mcs5_packet_count; + unsigned int vht_tx_mcs6_packet_count; + unsigned int vht_tx_mcs7_packet_count; + unsigned int vht_tx_mcs8_packet_count; + unsigned int vht_tx_mcs9_packet_count; + unsigned int tx_cmds_from_stack; + unsigned int tx_dones_to_stack; + unsigned int system_rev; + unsigned int outstanding_cmd_cnt; + unsigned int pending_tx_cnt; + unsigned int umac_scan_req; + unsigned int umac_scan_complete; + unsigned int gen_cmd_send_count; + unsigned int tx_cmd_send_count_single; + unsigned int tx_cmd_send_count_multi; + + unsigned int tx_noagg_not_qos; + unsigned int tx_noagg_not_ampdu; + unsigned int tx_noagg_not_addr; + + unsigned int tx_cmd_send_count_beaconq; + unsigned int tx_done_recv_count; + unsigned int rx_packet_mgmt_count; + unsigned int rx_packet_data_count; + unsigned int ed_cnt; + unsigned int mpdu_cnt; + unsigned int ofdm_crc32_pass_cnt; + unsigned int ofdm_crc32_fail_cnt; + unsigned int dsss_crc32_pass_cnt; + unsigned int dsss_crc32_fail_cnt; + unsigned int mac_id_pass_cnt; + unsigned int mac_id_fail_cnt; + unsigned int ofdm_corr_pass_cnt; + unsigned int ofdm_corr_fail_cnt; + unsigned int dsss_corr_pass_cnt; + unsigned int dsss_corr_fail_cnt; + unsigned int ofdm_s2l_fail_cnt; + unsigned int lsig_fail_cnt; + unsigned int htsig_fail_cnt; + unsigned int vhtsiga_fail_cnt; + unsigned int vhtsigb_fail_cnt; + unsigned int nonht_ofdm_cnt; + unsigned int nonht_dsss_cnt; + unsigned int mm_cnt; + unsigned int gf_cnt; + unsigned int vht_cnt; + unsigned int aggregation_cnt; + unsigned int non_aggregation_cnt; + unsigned int ndp_cnt; + unsigned int ofdm_ldpc_cnt; + unsigned int ofdm_bcc_cnt; + unsigned int midpacket_cnt; + unsigned int dsss_sfd_fail_cnt; + unsigned int dsss_hdr_fail_cnt; + unsigned int dsss_short_preamble_cnt; + unsigned int dsss_long_preamble_cnt; + unsigned int sifs_event_cnt; + unsigned int cts_cnt; + unsigned int ack_cnt; + unsigned int sifs_no_resp_cnt; + unsigned int unsupported_cnt; + unsigned int l1_corr_fail_cnt; + unsigned int sifs_crc_exit_cnt; + unsigned int low_energy_event_cnt; + unsigned int deagg_error_cnt; + unsigned int nsymbols_error_cnt; + unsigned int mcs32_cnt; + unsigned int ndpa_cnt; + unsigned int lsig_duration_error_cnt; + unsigned int rts_cnt; + unsigned int non_ht_cts_cnt; + unsigned int rxp_active_exit_cnt; + unsigned int beamform_feedback_cnt; + unsigned int self_cts_cnt; + unsigned int pop_master_cnt; + unsigned int pop_error_cnt; + unsigned int multicast_cnt; + unsigned int tx_ed_abort_cnt; + unsigned int mcp_cts_cnt; + unsigned int deagg_q_post_cnt; + unsigned int rxp_active_exit_dsss_cnt; + unsigned int rxp_extreme_error_cnt; + unsigned int aci_fail_cnt; + + /* TX related */ + unsigned int tx_pkts_from_lmac; + unsigned int tx_pkts_tx2tx; + unsigned int tx_pkts_from_rx; + unsigned int tx_pkts_ofdm; + + unsigned int tx_pkts_dsss; + unsigned int tx_pkts_reached_end_of_fsm; + unsigned int tx_unsupported_modulation; + unsigned int tx_latest_pkt_from_lmac_or_sifs; + + unsigned int tx_abort_bt_confirm_cnt; /* Tx abort due to BT + * confirm at the start of + * Txn + */ + unsigned int tx_abort_txstart_timeout_cnt; /* Tx abort due to Tx start + * time-out + */ + unsigned int tx_abort_mid_bt_cnt; /* Tx abort due to BT during + * WLAN txn + */ + unsigned int tx_abort_dac_underrun_cnt; /* Tx abort due to DAC + * under-run only + */ + unsigned int tx_ofdm_symbols_master; + unsigned int tx_ofdm_symbols_slave1; + unsigned int tx_ofdm_symbols_slave2; + unsigned int tx_dsss_symbols; + unsigned int cts_received_mcp_cnt; + + /*MAC Stats*/ + unsigned int roc_start; + unsigned int roc_stop; + unsigned int roc_complete; + unsigned int roc_stop_complete; + /* TX related */ + unsigned int tx_cmd_cnt; /* Num of TX commands received from host */ + unsigned int tx_done_cnt; /* Num of Tx done events sent to host */ + unsigned int tx_edca_trigger_cnt; /* Num times EDCA engine was + * triggered + */ + unsigned int tx_edca_isr_cnt; /* Num of times EDCA ISR was generated */ + unsigned int tx_start_cnt; /* Num of TX starts to MCP */ + unsigned int tx_abort_cnt; /* Num of TX aborts detected */ + unsigned int tx_abort_isr_cnt; /* Num of TX aborts received from MCP */ + unsigned int tx_underrun_cnt; /* Num of under-runs */ + unsigned int tx_rts_cnt; /* Num of RTS frames Txd */ + unsigned int tx_ampdu_cnt; /* Num of AMPDUs txd incremented by 1 for + * each A-MPDU (consisting of one or more + * MPDUs) + */ + unsigned int tx_mpdu_cnt; /* Num of MPDUs txd incremented by 1 for + * MPDU (1 for each A-MPDU subframe) + */ + + /* RX related */ + unsigned int rx_isr_cnt; /* Num of RX ISRs */ + unsigned int rx_ack_cts_to_cnt; /* Num of timeouts ACK */ + unsigned int rx_cts_cnt; /* Num of CTS frames received */ + unsigned int rx_ack_resp_cnt; /* Num of Ack frames received */ + unsigned int rx_ba_resp_cnt; /* Num of BA frames received */ + unsigned int rx_fail_in_ba_bitmap_cnt; /* Num of BA frames indicating at + * least one failure in the BA + * bitmap + */ + unsigned int rx_circular_buffer_free_cnt; /* Num of entries returned to + * RX circular buffers + */ + unsigned int rx_mic_fail_cnt; /* Num of MIC failures */ + + /* HAL related */ + unsigned int hal_cmd_cnt; /* Num of commands received by HAL from the + * host + */ + unsigned int hal_event_cnt; /* Num of events sent by HAL to the host */ + unsigned int hal_ext_ptr_null_cnt; /* Num of packets dropped due to lack + * of Ext Ram buffers from host + */ + + /*RF Calibration Data*/ + unsigned int rf_calib_data_length; + unsigned char rf_calib_data[MAX_RF_CALIB_DATA]; + unsigned int pdout_val; + unsigned char uccp420_lmac_version[8]; +}; + + +struct tx_pkt_info { + struct sk_buff_head pkt; + unsigned int hdr_len; + unsigned int queue; + unsigned int vif_index; + unsigned int rate[4]; + unsigned int retries[4]; + unsigned int curr_retries; + unsigned int max_retries; + int roc_peer_id; + bool adjusted_rates; +}; + + +struct tx_config { + /* Used to protect the TX pool */ + spinlock_t lock; + +#ifdef PERF_PROFILING + struct timer_list persec_timer; +#endif + /* Used to store tx tokens(buff pool ids) */ + unsigned long buf_pool_bmp[(NUM_TX_DESCS/TX_DESC_BUCKET_BOUND) + 1]; + + unsigned int outstanding_tokens[NUM_ACS]; + unsigned int next_spare_token_ac; + + /* Used to store the address of pending skbs per ac */ +#ifdef MULTI_CHAN_SUPPORT + struct sk_buff_head pending_pkt[MAX_UMAC_VIF_CHANCTX_TYPES] + [MAX_PEND_Q_PER_AC] + [NUM_ACS]; +#else + struct sk_buff_head pending_pkt[MAX_PEND_Q_PER_AC] + [NUM_ACS]; +#endif + +#ifdef MULTI_CHAN_SUPPORT + /* Peer which has the opportunity to xmit next on a queue */ + unsigned int curr_peer_opp[MAX_CHANCTX + MAX_OFF_CHANCTX][NUM_ACS]; +#else + unsigned int curr_peer_opp[NUM_ACS]; +#endif + + /* Used to store the address of tx'ed skb and len of 802.11 hdr + * it will be used in tx complete. + */ +#ifdef MULTI_CHAN_SUPPORT + int desc_chan_map[NUM_TX_DESCS]; + struct tx_pkt_info pkt_info[MAX_CHANCTX + MAX_OFF_CHANCTX] + [NUM_TX_DESCS]; +#else + struct tx_pkt_info pkt_info[NUM_TX_DESCS]; +#endif + + unsigned int queue_stopped_bmp; + struct sk_buff_head proc_tx_list[NUM_TX_DESCS]; +}; + +enum device_state { + STOPPED = 0, + STARTED +}; + +enum tid_aggr_state { + TID_STATE_INVALID = 0, + TID_STATE_AGGR_START, + TID_STATE_AGGR_STOP, + TID_STATE_AGGR_OPERATIONAL +}; + +#define TID_INITIATOR_STA 0x0000 +#define TID_INITIATOR_AP 0x0010 + +struct sta_tid_info { + unsigned short ssn; + enum tid_aggr_state tid_state; +}; + +#ifdef CONFIG_PM +struct econ_ps_cfg_status { + unsigned char completed; + unsigned char result; + int wake_trig; +}; +#endif + +struct current_channel { + unsigned int pri_chnl_num; + unsigned int center_freq1; + unsigned int center_freq2; + unsigned int freq_band; + unsigned int ch_width; +}; + +struct roc_params { + unsigned char roc_in_progress; + unsigned int roc_type; + bool need_offchan; + atomic_t roc_mgmt_tx_count; +}; + +struct mac80211_dev { + struct proc_dir_entry *umac_proc_dir_entry; + struct device *dev; + struct mac_address if_mac_addresses[MAX_VIFS]; + unsigned int current_vif_count; + unsigned int active_vifs; + struct mutex mutex; + int state; + int txpower; + unsigned char mc_filters[MCST_ADDR_LIMIT][6]; + int mc_filter_count; + + struct tasklet_struct proc_tx_tasklet; + /*ROC Work*/ + struct delayed_work roc_complete_work; + struct roc_params roc_params; + struct current_channel cur_chan; + struct tx_config tx; + struct sk_buff_head pending_pkt[NUM_ACS]; + + /* Regulatory stuff */ + char alpha2[2]; /* alpha2 country code */ +#ifdef CONFIG_PM + struct econ_ps_cfg_status econ_ps_cfg_stats; +#endif + struct wifi_params *params; + struct wifi_stats *stats; + char name[20]; + char scan_abort_done; + char cancel_hw_roc_done; + char cancel_roc; + char chan_prog_done; + char reset_complete; + int power_save; /* Will be set only when a single VIF in + * STA mode is active + */ + struct ieee80211_vif *vifs[MAX_VIFS]; + struct ieee80211_sta *peers[MAX_PEERS]; + struct ieee80211_hw *hw; + struct sta_tid_info tid_info[32]; + spinlock_t bcast_lock; /* Used to ensure more_frames bit is set properly + * when transmitting bcast frames in AP in IBSS + * modes + */ + spinlock_t roc_lock; + unsigned char tx_antenna; + unsigned char tx_last_beacon; + unsigned int rts_threshold; +#ifdef MULTI_CHAN_SUPPORT + spinlock_t chanctx_lock; + struct ieee80211_chanctx_conf *chanctx[MAX_CHANCTX]; + struct umac_chanctx *off_chanctx[MAX_OFF_CHANCTX]; + int roc_off_chanctx_idx; + int curr_chanctx_idx; + int num_active_chanctx; +#endif +}; + +struct edca_params { + unsigned short txop; /* units of 32us */ + unsigned short cwmin;/* units of 2^n-1 */ + unsigned short cwmax;/* units of 2^n-1 */ + unsigned char aifs; + unsigned char uapsd; +}; + +struct umac_vif { + struct timer_list bcn_timer; +#ifdef PERF_PROFILING + struct timer_list driver_tput_timer; +#endif + struct uvif_config { + unsigned int atim_window; + unsigned int aid; + unsigned int bcn_lost_cnt; + struct edca_params edca_params[NUM_ACS]; + } config; + + unsigned int noa_active; + struct sk_buff_head noa_que; + unsigned int noa_tx_allowed; + + int vif_index; + struct ieee80211_vif *vif; + struct mac80211_dev *dev; + unsigned char bssid[ETH_ALEN]; + unsigned int peer_ampdu_factor; + + /*Global Sequence no for non-qos and mgmt frames/vif*/ + __u16 seq_no; + +#ifdef MULTI_CHAN_SUPPORT + struct list_head list; + struct umac_chanctx *chanctx; + struct umac_chanctx *off_chanctx; +#endif +}; + +struct umac_sta { + int index; + int vif_index; +#ifdef MULTI_CHAN_SUPPORT + struct umac_chanctx *chanctx; +#endif +}; + +#ifdef MULTI_CHAN_SUPPORT +struct umac_chanctx { + int index; + + struct list_head vifs; + short nvifs; +}; + +#endif + +struct curr_peer_info { + int id; + int op_chan_idx; +}; + + +#ifdef MULTI_CHAN_SUPPORT +void uccp420wlan_proc_ch_sw_event(struct umac_event_ch_switch *ch_sw_info, + void *context); +#endif +extern int wait_for_cancel_hw_roc(struct mac80211_dev *dev); +extern int wait_for_scan_abort(struct mac80211_dev *dev); +extern int wait_for_channel_prog_complete(struct mac80211_dev *dev); +extern int wait_for_tx_queue_flush_complete(struct mac80211_dev *dev, + unsigned int token); +extern int uccp420wlan_prog_nw_selection(unsigned int nw_select_enabled, + unsigned char *mac_addr); +extern int uccp420wlan_core_init(struct mac80211_dev *dev, unsigned int ftm); +extern void uccp420wlan_core_deinit(struct mac80211_dev *dev, unsigned int ftm); +extern void uccp420wlan_vif_add(struct umac_vif *uvif); +extern void uccp420wlan_vif_remove(struct umac_vif *uvif); +extern void uccp420wlan_vif_set_edca_params(unsigned short queue, + struct umac_vif *uvif, + struct edca_params *params, + unsigned int vif_active); +extern void uccp420wlan_vif_bss_info_changed(struct umac_vif *uvif, + struct ieee80211_bss_conf + *bss_conf, unsigned int changed); +extern int uccp420wlan_tx_frame(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mac80211_dev *dev, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + bool bcast); +extern int __uccp420wlan_tx_frame(struct mac80211_dev *dev, + unsigned int queue, + unsigned int token_id, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + unsigned int more_frames, + bool retry); +extern void uccp420wlan_tx_init(struct mac80211_dev *dev); +extern void uccp420wlan_tx_deinit(struct mac80211_dev *dev); +void uccp420wlan_tx_proc_send_pend_frms_all(struct mac80211_dev *dev, + int chan_id); +extern void proc_bss_info_changed(unsigned char *mac_addr, int value); +extern void packet_generation(unsigned long data); +extern int wait_for_reset_complete(struct mac80211_dev *dev); + +extern int uccp420wlan_tx_proc_pend_frms(struct mac80211_dev *dev, + int queue, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int token_id); +void free_token(struct mac80211_dev *dev, + int token_id, + int queue); + +struct curr_peer_info get_curr_peer_opp(struct mac80211_dev *dev, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int queue); + +int uccp420_flush_vif_queues(struct mac80211_dev *dev, + struct umac_vif *uvif, + int chanctx_idx, + unsigned int hw_queue_map, + enum UMAC_VIF_CHANCTX_TYPE vif_chanctx_type); + +/* Beacon TimeStamp */ +__s32 __attribute__((weak)) frc_to_atu(__u32 frccnt, __u64 *patu, s32 dir); +int __attribute__((weak)) get_evt_timer_freq(unsigned int *mask, + unsigned int *num, + unsigned int *denom); + +int tx_queue_map(int queue); +int tx_queue_unmap(int queue); + +extern unsigned char *rf_params_vpd; +extern int num_streams_vpd; + +static __always_inline long param_get_val(unsigned char *buf, + unsigned char *str, + unsigned long *val) +{ + unsigned char *temp; + + if (strstr(buf, str)) { + temp = strstr(buf, "=") + 1; + /*To handle the fixed rate 5.5Mbps case*/ + if (!strncmp(temp, "5.5", 3)) { + *val = 55; + return 1; + } else if (!kstrtoul(temp, 0, val)) { + return 1; + } else { + return 0; + } + } else { + return 0; + } +} + +static __always_inline long param_get_sval(unsigned char *buf, + unsigned char *str, + long *val) +{ + + unsigned char *temp; + + if (strstr(buf, str)) { + temp = strstr(buf, "=") + 1; + /*To handle the fixed rate 5.5Mbps case*/ + if (!strncmp(temp, "5.5", 3)) { + *val = 55; + return 1; + } else if (!kstrtol(temp, 0, val)) { + return 1; + } else { + return 0; + } + } else { + return 0; + } + +} + +static __always_inline long param_get_match(unsigned char *buf, + unsigned char *str) +{ + + if (strstr(buf, str)) + return 1; + else + return 0; +} + +static inline int vif_addr_to_index(unsigned char *addr, + struct mac80211_dev *dev) +{ + int i; + struct ieee80211_vif *vif = NULL; + + for (i = 0; i < MAX_VIFS; i++) { + if (!((i < MAX_VIFS) && (dev->active_vifs & (1 << i)))) + continue; + + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[i]); + rcu_read_unlock(); + + if (ether_addr_equal(addr, vif->addr)) + break; + } + + if (i < dev->params->num_vifs) + return i; + else + return -1; +} + +static inline int ieee80211_is_unicast_robust_mgmt_frame(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + + if (skb->len < 24 || is_multicast_ether_addr(hdr->addr1)) + return 0; + + return ieee80211_is_robust_mgmt_frame(skb); +} +static inline bool is_bufferable_mgmt_frame(struct ieee80211_hdr *hdr) +{ + __u16 fc = hdr->frame_control; + /*TODO: Handle Individual Probe Response frame in IBSS*/ + if (ieee80211_is_action(fc) || + ieee80211_is_disassoc(fc) || + ieee80211_is_deauth(fc)) + return true; + + return false; +} +#endif /* _UCCP420WLAN_CORE_H_ */ diff --git a/drivers/net/wireless/uccp420wlan/inc/descriptors.h b/drivers/net/wireless/uccp420wlan/inc/descriptors.h new file mode 100644 index 00000000000000..82cb0e110d0fec --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/descriptors.h @@ -0,0 +1,53 @@ +/* + * File Name : descriptor.h + * + * File Description: This file contains information about TX and RX descriptors + * This file contains Intermodule communication APIs + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420WLAN_DESCRIPTOR_H_ +#define _UCCP420WLAN_DESCRIPTOR_H_ + +#define NUM_ACS 5 + +#define NUM_TX_DESCS_PER_AC 2 /* reserved TX descriptors per AC + * (must be multiple of 2, minimum of 2 + * and maximum of 4) + */ +#define NUM_SPARE_TX_DESCS 2 /* Descriptors shared between ACs + * (at least 1 and maximum of 2) + */ + +#define NUM_TX_DESCS ((NUM_ACS * NUM_TX_DESCS_PER_AC) + NUM_SPARE_TX_DESCS) +/* Max size of a sub-frame in an AMPDU */ +#define MAX_AMPDU_SUBFRAME_SIZE 1500 + +/* Max no of sub frames in an AMPDU */ +#define MAX_SUBFRAMES_IN_AMPDU_HT 24 /* HT */ +#define MAX_SUBFRAMES_IN_AMPDU_VHT 24 /* VHT */ + +#define NUM_CTRL_DESCS 2 + +#define NUM_RX_BUFS_2K 256 +#define NUM_RX_BUFS_12K 16 + +#endif /* _UCCP420WLAN_DESCRIPTOR_H_ */ +/* EOF */ diff --git a/drivers/net/wireless/uccp420wlan/inc/fwldr.h b/drivers/net/wireless/uccp420wlan/inc/fwldr.h new file mode 100644 index 00000000000000..cee1bcabf17b45 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/fwldr.h @@ -0,0 +1,358 @@ +/* + * File Name : fwldr.h + * + * File Description: This file contains definitions used for firmware loader + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _FWLDR_H_ +#define _FWLDR_H_ + +#include +#include +#include "hal.h" +#include "version.h" + +#ifdef DRIVER_DEBUG + +#define fwldr_dbg_err(...) pr_err(__VA_ARGS__) +#define fwldr_dbg_info(...) pr_info(__VA_ARGS__) +#define fwldr_dbg_dump(...) pr_info(__VA_ARGS__) + +#else +#define fwldr_dbg_err(...) do { } while (0) +#define fwldr_dbg_info(...) do { } while (0) +#define fwldr_dbg_dump(...) do { } while (0) + +#endif + +#define FWLDR_NUM_BINS 2 +#define FWLDR_HW_BIN "img/uccp420wlan/MCP_LOADER.ldr" +#define FWLDR_FW_BIN "img/uccp420wlan/MAC_LOADER.ldr" + +#define FWLDR_PLRCRD_WORDS 16 /* 64-bit WORDS */ +#define FWLDR_PLRCRD_BYTES (FWLDR_PLRCRD_WORDS * 8) +#define FWLDR_PLRCRD_TRAIL_BYTES 8 +#define FWLDR_PLRCRD_DATA_BYTES (FWLDR_PLRCRD_BYTES - FWLDR_PLRCRD_TRAIL_BYTES) + +#define FW_LDR 0 +#define RPU_DUMP 1 +/****************************************************************************** +* These constants are used to access various fields within an L1 record as well +* as other constants that are used. +******************************************************************************/ + +/* The maximum number of bytes in an L1 record. */ +#define FWLDR_L1_MAXSIZE 32 + +/* The maximum number of bytes in an L2 record. */ +#define FWLDR_L2_MAXSIZE 4096 + +/* The size in bytes of the 'cmd' field in an L1 Record. */ +#define FWLDR_L1_CMD_SIZE 2 + +/* The size in bytes of the 'length' field in an L1 Record. */ +#define FWLDR_L1_LEN_SIZE 2 + +/* The size in bytes of the 'next' field in an L1 Record. */ +#define FWLDR_L1_NXT_SIZE 4 + +/* The size in bytes of the 'arg1' field in an L1 Record. */ +#define FWLDR_L1_ARG1_SIZE 4 + +/* The size in bytes of the 'arg2' field in an L1 Record. */ +#define FWLDR_L1_ARG2_SIZE 4 + +/* The size in bytes of the 'l2offset' field in an L1 Record. */ +#define FWLDR_L1_L2OFF_SIZE 4 + +/* The size in bytes of the 'xsum' field in an L1 Record. */ +#define FWLDR_L1_XSUM_SIZE 2 + +/* The offset in bytes of the 'cmd' field in an L1 record. */ +#define FWLDR_L1_CMD_OFF 0 + +/* The offset in bytes of the 'length' field in an L1 record. */ +#define FWLDR_L1_LEN_OFF (FWLDR_L1_CMD_OFF + FWLDR_L1_CMD_SIZE) + +/* The offset in bytes of the 'next' field in an L1 record. */ +#define FWLDR_L1_NXT_OFF (FWLDR_L1_LEN_OFF + FWLDR_L1_LEN_SIZE) + +/* The offset in bytes of the 'arg1' field in an L1 record. */ +#define FWLDR_L1_ARG1_OFF (FWLDR_L1_NXT_OFF + FWLDR_L1_NXT_SIZE) + +/* The offset in bytes of the 'arg2' field in an L1 record. */ +#define FWLDR_L1_ARG2_OFF (FWLDR_L1_ARG1_OFF + FWLDR_L1_ARG1_SIZE) + +/* The following is the value used to terminate a chain of L1 records */ +#define FWLDR_L1_TERMINATE 0xffffffff + + +/****************************************************************************** +* These constants are used to access various fields within an L2 record as well +* as other constants that are used. +******************************************************************************/ + +/* The size in bytes of the 'cmd' field in an L2 Record. */ +#define FWLDR_L2_CMD_SIZE 2 + +/* The size in bytes of the 'length' field in an L2 Record. */ +#define FWLDR_L2_LEN_SIZE 2 + +/* The size in bytes of the 'xsum' field in an L2 Record. */ +#define FWLDR_L2_XSUM_SIZE 2 + +/* The offset in bytes from the beginning of an L2 record to the data + * payload + */ +#define FWLDR_L2_DATA (FWLDR_L2_CMD_SIZE + FWLDR_L2_LEN_SIZE) + + +/****************************************************************************** +* Various combined values... +******************************************************************************/ + +/* Sizes of common items between L1 and L2 records */ +#define FWLDR_L1_L2LEN_SIZE FWLDR_L2_LEN_SIZE + +/* The size in bytes of an L1 record when it contains no data */ +#define FWLDR_L1_BASIC_SIZE (FWLDR_L1_CMD_SIZE + \ + FWLDR_L1_LEN_SIZE + \ + FWLDR_L1_NXT_SIZE + \ + FWLDR_L1_L2OFF_SIZE + \ + FWLDR_L1_L2LEN_SIZE + \ + FWLDR_L1_XSUM_SIZE) + +/* The size in bytes of an L2 record when it contains no data */ +#define FWLDR_L2_BASIC_SIZE (FWLDR_L2_CMD_SIZE + \ + FWLDR_L2_LEN_SIZE + \ + FWLDR_L2_XSUM_SIZE) + + +/* Offsets in bytes from the end of an L1 record for various fields */ +#define FWLDR_L1_L2LEN_OFF (FWLDR_L1_XSUM_SIZE + FWLDR_L1_L2LEN_SIZE) +#define FWLDR_L1_L2OFF_OFF (FWLDR_L1_L2LEN_OFF + FWLDR_L1_L2OFF_SIZE) + +#define UCCP_GRAM_BASE 0xB7000000 + +#define UCCP_OFFSET_MASK 0x00FFFFFF +#define UCCP_BASE_MASK 0xFF000000 +#define UCCP_SYSBUS_REG 0x02 +#define UCCP_GRAM_PACKED 0xB7 +#define UCCP_GRAM_MSB 0xB4 + + +#define MTX_REG_INDIRECT(unit, reg) (((reg & 0x7) << 4) | (unit & 0xF)) + +#define MTX_PC_REG_IND_ADDR MTX_REG_INDIRECT(5, 0) +#define MTX_A0STP_REG_IND_ADDR MTX_REG_INDIRECT(3, 0) + +#define MTX_PCX_REG_IND_ADDR MTX_REG_INDIRECT(5, 1) +#define MTX_TXMASK_REG_IND_ADDR MTX_REG_INDIRECT(7, 1) +#define MTX_TXMASKI_REG_IND_ADDR MTX_REG_INDIRECT(7, 3) +#define MTX_TXPOLL_REG_IND_ADDR MTX_REG_INDIRECT(7, 4) +#define MTX_TXPOLLI_REG_IND_ADDR MTX_REG_INDIRECT(7, 6) +#define MTX_TXSTAT_REG_IND_ADDR MTX_REG_INDIRECT(7, 0) +#define MTX_TXSTATI_REG_IND_ADDR MTX_REG_INDIRECT(7, 2) + +#define REG_IND_READ_FLAG (1 << 16) + +#define MTX_TXPRIVEXT_ADDR 0x048000E8 +#define MTX_TXSTATUS_ADDR 0x48000010 +#define MTX_TXENABLE_ADDR 0x04800000 +#define MTX_START_EXECUTION 1 +#define MTX_STOP_EXECUTION 0 + +#define MTX_TXUXXRXDT 0x0480FFF0 +#define MTX_TXUXXRXRQ 0x0480FFF8 + +#define MSLV_BASE_ADDR 0x0203C000 + +/* DATA Exchange Register */ +#define MSLVDATAX (MSLV_BASE_ADDR + 0x2000) + +/* DATA Transfer Register */ +#define MSLVDATAT (MSLV_BASE_ADDR + 0x2040) + +/* Control Register 0 */ +#define MSLVCTRL0 (MSLV_BASE_ADDR + 0x2080) + +/* Soft Reset register */ +#define MSLVSRST (MSLV_BASE_ADDR + 0x2600) + +#define SLAVE_ADDR_MODE_MASK 0xFFFFFFFC +#define SLAVE_SINGLE_WRITE 0x00 +#define SLAVE_SINGLE_READ 0x01 +#define SLAVE_BLOCK_WRITE 0x02 +#define SLAVE_BLOCK_READ 0x03 + +/* Control Register 1 */ +#define MSLVCTRL1 (MSLV_BASE_ADDR + 0x20c0) + +#define MSLVCTRL1_POLL_MASK 0x07000000 +#define MSLAVE_READY(v) ((v & MSLVCTRL1_POLL_MASK) == MSLVCTRL1_POLL_MASK) +#define LTP_THREAD_NUM 0 /* Since, only one thread exists */ + +/* Thread completion signature */ +#define UCCP_THRD_EXEC_SIG_OFFSET 0x00066CBC +#define UCCP_THRD_EXEC_SIG 0x5A5A5A5A + +#define MAX_LOAD_MEM_LEN 4096 + + +enum fwldr_status { + FWLDR_SUCCESS, + FWLDR_FAIL +}; + +/* Cmd or Tag values used in the L1/L2 records */ +enum fwldr_cmd_tag_l1_l2 { + FWLDR_L1_CMD_LOAD_MEM = 0x0000, /* Command - L1 LoadMem. */ + FWLDR_L1_CMD_START_THRDS = 0x0003, /* Command - L1 StartThrds. */ + FWLDR_L1_CMD_ZERO_MEM = 0x0004, /* Command - L1 ZeroMem. */ + FWLDR_L1_CMD_CONFIG = 0x0005, /* Command - L1 Config. */ + FWLDR_L1_CMD_FILENAME = 0x0010, /* Command - L1 FileName. */ +}; + +/* Enumerates all possible types of configuration commands */ +enum fwldr_conf_cmd { + FWLDR_CONF_CMD_PAUSE = 0x0000, /* Pause */ + FWLDR_CONF_CMD_READ, /* Read */ + FWLDR_CONF_CMD_WRITE, /* Write */ + FWLDR_CONF_CMD_MEMSET, /* MemSet */ + FWLDR_CONF_CMD_MEMCHK, /* MemChk */ + FWLDR_CONF_CMD_USER, /* User */ +}; + +/* Information contained within an .ldr file */ +enum fwldr_ldr_sec { + FWLDR_SEC_NONE = 0, /* Element is undefined */ + FWLDR_SEC_BOOT_HEADER, /* Boot header */ + FWLDR_LDR_CODE, /* Secondary loader executable */ + FWLDR_SEC_DATA_L1, /* Secondary loader top level data stream */ + FWLDR_SEC_DATA_L2 /* Secondary loader raw data stream */ +}; + +enum uccp_mem_region { + UCCP_MEM_CORE, + UCCP_MEM_DIRECT, + UCCP_MEM_ERR +}; + +struct fwldr_bootdevhdr { + unsigned int dev_id; /* Value used to verify access to boot device */ + unsigned int sl_code; /* Offset to secondary loader code */ +#define BOOTDEV_SLCSECURE_BIT 0x80000000 +#define BOOTDEV_SLCCRITICAL_BIT 0x40000000 + + unsigned int sl_data; /* Offset to data used by secondary loader */ + unsigned short pl_ctrl; /* Primary loader control */ +#define BOOTDEV_PLCREMAP_BITS 0x00FF +#define BOOTDEV_PLCREMAP_S 0 + + unsigned short CRC; /* CRC value */ +}; + +struct fwldr_load_mem_info { + unsigned int dst_addr; + unsigned int len; + unsigned char *src_buf; +}; + +struct fwldr_thrd_info { + unsigned int thrd_num; + unsigned int stack_ptr; + unsigned int prog_ctr; + unsigned int catch_state_addr; +}; + +struct fwldr_cfg_rw { + unsigned int addr; + unsigned int val; +}; + +/* Represents a secondary loader top level data stream record. */ +struct fwldr_sec_ldr_l1_record { + unsigned short cmd_tag; /* Command TagMember comment goes here */ + unsigned short len; /* Total length os this record */ + unsigned short crc; /* X25 CRC checksum for this record (including the + * checksum itself) + */ + unsigned int nxt; /* Offset within the .ldr to the next L1RECORD */ + unsigned int arg1; /* The first command argument for the command */ + unsigned int arg2; /* The second command argument for the command */ + unsigned int l2_offset; /* Offset within the .ldr to the corresponding + * raw data record + */ + unsigned int l2_len; /* The expected length of the raw data record */ +}; + +struct fwldr_memhdr_tag { + struct fwldr_memhdr_tag *p_next; + unsigned int addr; /* Target byte address */ + unsigned char *data; /* Data block pointer */ + unsigned int len; /* Len in bytes of data block */ + +}; + +struct fwload_priv { + unsigned char *gram_addr; + unsigned char *sysbus_addr; + unsigned char *gram_b4_addr; +}; + +int rpudump_init(void); +int fwldr_load_fw(const unsigned char *fw_data, int i); + +void dir_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len); + +void core_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len); + +static inline void fwload_uccp_read(struct fwload_priv *fpriv, + unsigned long base, + unsigned long offset, + unsigned int *data) +{ + if (base == UCCP_SYSBUS_REG) + *data = readl((void __iomem *)(fpriv->sysbus_addr + offset)); + else if (base == UCCP_GRAM_PACKED) + *data = readl((void __iomem *)(fpriv->gram_addr + offset)); + else if (base == UCCP_GRAM_MSB) + *data = readl((void __iomem *)(fpriv->gram_b4_addr + offset)); +} + +static inline void fwload_uccp_write(struct fwload_priv *fpriv, + unsigned long base, + unsigned long offset, + unsigned int data) +{ + if (base == UCCP_SYSBUS_REG) + writel(data, (void __iomem *)(fpriv->sysbus_addr + offset)); + else if (base == UCCP_GRAM_PACKED) + writel(data, (void __iomem *)(fpriv->gram_addr + offset)); + else if (base == UCCP_GRAM_MSB) + writel(data, (void __iomem *)(fpriv->gram_b4_addr + offset)); +} + +#endif /* _FWLDR_H_ */ diff --git a/drivers/net/wireless/uccp420wlan/inc/hal.h b/drivers/net/wireless/uccp420wlan/inc/hal.h new file mode 100644 index 00000000000000..eda5f28072e355 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/hal.h @@ -0,0 +1,67 @@ +/* + * File Name : hal.h + * + * This file contains Intermodule communication APIs + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420WLAN_HAL_H_ +#define _UCCP420WLAN_HAL_H_ + +#define HOST_MOD_ID 0 +#define UMAC_MOD_ID 1 +#define LMAC_MOD_ID 2 +#define MODULE_MAX 3 + +#define HAL_PRIV_DATA_SIZE 8 + +typedef int (*msg_handler)(void *, unsigned char); + +struct hal_ops_tag { + int (*init)(void *); + int (*deinit)(void *); + int (*start)(void); + int (*stop)(void); + void (*register_callback)(msg_handler, unsigned char); + void (*send)(void*, unsigned char, unsigned char, void*); + int (*init_bufs)(unsigned int, unsigned int, unsigned int, + unsigned int); + void (*deinit_bufs)(void); + int (*map_tx_buf)(int, int, unsigned char *, int); + int (*unmap_tx_buf)(int, int); + int (*reset_hal_params)(void); + void (*set_mem_region)(unsigned int); + void (*request_mem_regions)(unsigned char **, + unsigned char **, + unsigned char **); + void (*enable_irq_wake)(void); + void (*disable_irq_wake)(void); + int (*get_dump_gram)(long *dump_start); + int (*get_dump_core)(unsigned long *dump_start, + unsigned char region_type); + int (*get_dump_perip)(unsigned long *dump_start); + int (*get_dump_sysbus)(unsigned long *dump_start); + int (*get_dump_len)(unsigned long); +}; + +extern struct hal_ops_tag hal_ops; +#endif /* _UCCP420WLAN_HAL_H_ */ + +/* EOF */ diff --git a/drivers/net/wireless/uccp420wlan/inc/host_umac_if.h b/drivers/net/wireless/uccp420wlan/inc/host_umac_if.h new file mode 100644 index 00000000000000..01f2b3995f6f98 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/host_umac_if.h @@ -0,0 +1,1160 @@ +/* + * File Name : host_umac_if.h + * + * This file contains the UMAC<-->HOST comms data structures + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420HOST_UMAC_IF_H_ +#define _UCCP420HOST_UMAC_IF_H_ + +#include "hal.h" +#define MCST_ADDR_LIMIT 48 +#define WLAN_ADDR_LEN 6 +#define TKIP_MIC_LEN 8 +#define MICHAEL_LEN 16 +#define MAX_KEY_LEN 16 +#define MAX_VIFS 2 + +#define MAX_PEERS 15 +/* Additional queue for unicast frames directed to non-associated peers (for + * e.g. Probe Responses etc) + */ +#define MAX_PEND_Q_PER_AC (MAX_PEERS + MAX_VIFS) + +#ifdef MULTI_CHAN_SUPPORT +#define MAX_CHANCTX MAX_VIFS +#define MAX_OFF_CHANCTX MAX_VIFS +#define OFF_CHANCTX_IDX_BASE MAX_CHANCTX +#endif + +#define WEP40_KEYLEN 5 +#define WEP104_KEYLEN 13 +#define MAX_WEP_KEY_LEN 13 + +#define WLAN_20MHZ_OPERATION 0 +#define WLAN_40MHZ_OPERATION 1 +#define WLAN_80MHZ_OPERATION 2 +#define WLAN_SEC_UPPER 0 +#define WLAN_SEC_LOWER 1 + +/* TEMPdec */ +#define PWRSAVE_STATE_AWAKE 1 +#define PWRSAVE_STATE_DOZE 0 +/* TEMPDEC */ +#define MAX_SSID_LEN 32 +#define MAX_NUM_SSIDS 4 +#define TOTAL_KEY_LEN 32 +#define RX_SEQ_SIZE 6 +#define MAX_IE_LEN 100 +#define ETH_ALEN 6 + +#define MAX_TX_CMDS 32 +#define MAX_GRAM_PAYLOAD_LEN 52 + +#define RF_PARAMS_SIZE 369 +#define MAX_RF_CALIB_DATA 900 +struct hal_data { + unsigned char hal_data[HAL_PRIV_DATA_SIZE]; +} __packed; + +struct host_mac_msg_hdr { + struct hal_data hal_data; + unsigned int descriptor_id; /* LSB 2 bytes as pool id, MSB 2 bytes + * queue num, pool ID of 0xFFFF indicates + * no payload + */ + unsigned int payload_length; + unsigned int id; + unsigned int length; + unsigned int more_cmd_data; /* used for fragmenting commands */ +} __packed; + + +enum UMAC_QUEUE_NUM { + WLAN_AC_BK = 0, + WLAN_AC_BE, + WLAN_AC_VI, + WLAN_AC_VO, + WLAN_AC_BCN, + WLAN_AC_MAX_CNT +}; + + +enum UMAC_EVENT_ROC_STAT { + UMAC_ROC_STAT_STARTED, + UMAC_ROC_STAT_STOPPED, + UMAC_ROC_STAT_DONE, + UMAC_ROC_STAT_ABORTED +}; + +enum UMAC_VIF_CHANCTX_TYPE { + UMAC_VIF_CHANCTX_TYPE_OPER, + UMAC_VIF_CHANCTX_TYPE_OFF, + MAX_UMAC_VIF_CHANCTX_TYPES +}; + +struct umac_event_tx_done { + struct host_mac_msg_hdr hdr; + + unsigned char pdout_voltage; + /* frame_status - + * 0 - success + * 1 - discarded due to retry limit exceeded + * 2 - discarded due to msdu lifetime expiry + * 3 - discarded due to encryption key not available + */ +#define TX_DONE_STAT_SUCCESS (0) +#define TX_DONE_STAT_ERR_RETRY_LIM (1) +#define TX_DONE_STAT_MSDU_LIFETIME (2) +#define TX_DONE_STAT_KEY_NOT_FOUND (3) +#define TX_DONE_STAT_DISCARD (4) +#define TX_DONE_STAT_DISCARD_BCN (5) +#ifdef MULTI_CHAN_SUPPORT +#define TX_DONE_STAT_DISCARD_CHSW (6) +#endif +#define TX_DONE_STAT_DISCARD_OP_TX (7) + unsigned char frm_status[MAX_TX_CMDS]; + unsigned char retries_num[MAX_TX_CMDS]; + /* rate = Units of 500 Kbps or mcs index = 0 to 7*/ + unsigned char rate[MAX_TX_CMDS]; + unsigned char queue; + unsigned int descriptor_id; + unsigned char reserved[12]; +} __packed; + +struct umac_event_ch_prog_complete { + struct host_mac_msg_hdr hdr; +} __packed; + +#ifdef MULTI_CHAN_SUPPORT +struct umac_event_ch_switch { + struct host_mac_msg_hdr hdr; + int chan; +} __packed; +#endif + +struct umac_event_noa { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned char vif_addr[ETH_ALEN]; + + /* 1 indicates NoA feature is active + * 0 indicates NoA feature is not active + */ + unsigned int noa_active; +#define ABSENCE_START 0 /* Indicates AP is absent */ +#define ABSENCE_STOP 1 /* Indicates AP is present */ + unsigned int ap_present; +} __packed; + +struct umac_event_mib_stats { + struct host_mac_msg_hdr hdr; + unsigned int ed_cnt; + unsigned int mpdu_cnt; + unsigned int ofdm_crc32_pass_cnt; + unsigned int ofdm_crc32_fail_cnt; + unsigned int dsss_crc32_pass_cnt; + unsigned int dsss_crc32_fail_cnt; + unsigned int mac_id_pass_cnt; + unsigned int mac_id_fail_cnt; + unsigned int ofdm_corr_pass_cnt; + unsigned int ofdm_corr_fail_cnt; + unsigned int dsss_corr_pass_cnt; + unsigned int dsss_corr_fail_cnt; + unsigned int ofdm_s2l_fail_cnt; + unsigned int lsig_fail_cnt; + unsigned int htsig_fail_cnt; + unsigned int vhtsiga_fail_cnt; + unsigned int vhtsigb_fail_cnt; + unsigned int nonht_ofdm_cnt; + unsigned int nonht_dsss_cnt; + unsigned int mm_cnt; + unsigned int gf_cnt; + unsigned int vht_cnt; + unsigned int aggregation_cnt; + unsigned int non_aggregation_cnt; + unsigned int ndp_cnt; + unsigned int ofdm_ldpc_cnt; + unsigned int ofdm_bcc_cnt; + unsigned int midpacket_cnt; + unsigned int dsss_sfd_fail_cnt; + unsigned int dsss_hdr_fail_cnt; + unsigned int dsss_short_preamble_cnt; + unsigned int dsss_long_preamble_cnt; + unsigned int sifs_event_cnt; + unsigned int cts_cnt; + unsigned int ack_cnt; + unsigned int sifs_no_resp_cnt; + unsigned int unsupported_cnt; + unsigned int l1_corr_fail_cnt; + unsigned int sifs_crc_exit_cnt; + unsigned int low_energy_event_cnt; + unsigned int deagg_error_cnt; + unsigned int nsymbols_error_cnt; + unsigned int mcs32_cnt; + unsigned int ndpa_cnt; + unsigned int lsig_duration_error_cnt; + unsigned int rts_cnt; + unsigned int non_ht_cts_cnt; + unsigned int rxp_active_exit_cnt; + unsigned int beamform_feedback_cnt; + unsigned int self_cts_cnt; + unsigned int pop_master_cnt; + unsigned int pop_error_cnt; + unsigned int multicast_cnt; + unsigned int tx_ed_abort_cnt; + unsigned int mcp_cts_cnt; + unsigned int deagg_q_post_cnt; + unsigned int rxp_active_exit_dsss_cnt; + unsigned int rxp_extreme_error_cnt; + unsigned int aci_fail_cnt; + /*Tx Stats*/ + unsigned int tx_pkts_from_lmac; + unsigned int tx_pkts_tx2tx; + unsigned int tx_pkts_from_rx; + unsigned int tx_pkts_ofdm; + + unsigned int tx_pkts_dsss; + unsigned int tx_pkts_reached_end_of_fsm; + unsigned int tx_unsupported_modulation; + unsigned int tx_latest_pkt_from_lmac_or_sifs; + + unsigned int tx_abort_bt_confirm_cnt; /* Tx abort due to BT + * confirm at the start of + * Txn + */ + unsigned int tx_abort_txstart_timeout_cnt; /* Tx abort due to Tx start + * time-out + */ + unsigned int tx_abort_mid_bt_cnt; /* Tx abort due to BT during + * WLAN txn + */ + unsigned int tx_abort_dac_underrun_cnt; /* Tx abort due to DAC + * under-run only + */ + unsigned int tx_ofdm_symbols_master; + unsigned int tx_ofdm_symbols_slave1; + unsigned int tx_ofdm_symbols_slave2; + unsigned int tx_dsss_symbols; + unsigned int cts_received_mcp_cnt; +} __packed; + + +struct umac_event_mac_stats { + struct host_mac_msg_hdr hdr; + unsigned int roc_start; + unsigned int roc_stop; + unsigned int roc_complete; + unsigned int roc_stop_complete; + /* TX related */ + unsigned int tx_cmd_cnt; /* Num of TX commands received from host */ + unsigned int tx_done_cnt; /* Num of Tx done events sent to host */ + unsigned int tx_edca_trigger_cnt; /* Num of times EDCA engine was + * triggered + */ + unsigned int tx_edca_isr_cnt; /* Num of times EDCA ISR was generated */ + unsigned int tx_start_cnt; /* Num of TX starts to MCP */ + unsigned int tx_abort_cnt; /* Num of TX aborts detected */ + unsigned int tx_abort_isr_cnt;/* Num of TX aborts received from MCP */ + unsigned int tx_underrun_cnt; /* Num of under-runs */ + unsigned int tx_rts_cnt; /* Num of RTS frames Tx’d */ + unsigned int tx_ampdu_cnt; /* Num of AMPDU’s tx’d –incremented by + * 1 for each A-MPDU (consisting of one or + * more MPDUs) + */ + unsigned int tx_mpdu_cnt; /* Num of MPDU’s tx’d – incremented by 1 + * for MPDU (1 for each A-MPDU subframe) + */ + /* RX related */ + unsigned int rx_isr_cnt; /* Num of RX ISRs */ + unsigned int rx_ack_cts_to_cnt; /* Num of timeouts ACK */ + unsigned int rx_cts_cnt; /* Num of CTS frames received */ + unsigned int rx_ack_resp_cnt; /* Num of Ack frames received */ + unsigned int rx_ba_resp_cnt; /* Num of BA frames received */ + unsigned int rx_fail_in_ba_bitmap_cnt; /* Num of BA frames indicating at + * least one failure in the + * BA bitmap + */ + unsigned int rx_circular_buffer_free_cnt; /* Num of entries returned to + * RX circular buffers + */ + unsigned int rx_mic_fail_cnt; /* Num of MIC failures */ + /* HAL related */ + unsigned int hal_cmd_cnt; /* Num of commands received by HAL from the + * host + */ + unsigned int hal_event_cnt; /* Num of events sent by HAL to the host */ + unsigned int hal_ext_ptr_null_cnt; /* Num of packets dropped due to lack + * of Ext Ram buffers from host + */ + +} __packed; + + +struct wlan_rx_pkt { + struct host_mac_msg_hdr hdr; + /* MPDU/MSDU payload in bytes */ + unsigned int pkt_length; + /* bit[8] = 0 - legacy data rate + * = 1 - MCS index + */ + unsigned char rate_or_mcs; + /* RSSI in dbm */ + unsigned char rssi; + /* packet status + * 1 - mic failed + * 0 - mic succes reserved for non encryped packet\ + */ +#define RX_MIC_SUCCESS 0 /* No MIC error in frame */ +#define RX_MIC_FAILURE_TKIP 1 /* TKIP MIC error in frame */ +#define RX_MIC_FAILURE_CCMP 2 /* CCMP MIC error in frame */ + unsigned char rx_pkt_status; + +#define ENABLE_GREEN_FIELD 0x01 +#define ENABLE_CHNL_WIDTH_40MHZ 0x02 +#define ENABLE_SGI 0x04 +#define ENABLE_11N_FORMAT 0x08 +#define ENABLE_VHT_FORMAT 0x10 +#define ENABLE_CHNL_WIDTH_80MHZ 0x20 + + unsigned char rate_flags; + unsigned char nss; + unsigned char num_sts; + unsigned char timestamp[8]; + unsigned char stbc_enabled; + unsigned char ldpc_enabled; + unsigned char link_margin; + unsigned char channel; +/* Currently size of reserved = + * sizeof(beacon_time_stamp = 8) + + * sizeof(ets_timer = 4) + + * sizeof(delta_timer_diff = 4) + + * (qos_padding = 2) + */ + unsigned char reserved[18]; + /*payload bytes */ + unsigned char payload[0]; +} __packed; + +#ifdef CONFIG_PM +enum UMAC_PS_ECON_WAKE_TRIG { + TRIG_PKT_RCV, + TRIG_DISCONNECT +}; + +struct umac_event_ps_econ_wake { + struct host_mac_msg_hdr hdr; + enum UMAC_PS_ECON_WAKE_TRIG trigger; +} __packed; + +struct umac_event_ps_econ_cfg_complete { + struct host_mac_msg_hdr hdr; + unsigned char status; /* SUCCESS/FAILURE */ +} __packed; +#endif + + + +enum UMAC_CMD_TAG { + UMAC_CMD_RESET = 0, + UMAC_CMD_SCAN, + UMAC_CMD_SCAN_ABORT, + UMAC_CMD_CONNECT, + UMAC_CMD_SETKEY, + UMAC_CMD_SET_DEFAULTKEY, + UMAC_CMD_REKEY_DATA, + UMAC_CMD_TX, + UMAC_CMD_MGMT_TX, + UMAC_CMD_FRAG, + UMAC_CMD_TX_POWER, + UMAC_CMD_RATE, + UMAC_CMD_DISCONNECT, + UMAC_CMD_PS, + UMAC_CMD_PS_ECON_CFG, + UMAC_CMD_VIF_CTRL, + UMAC_CMD_SET_BEACON, + UMAC_CMD_SET_MODE, + UMAC_CMD_BA_SESSION_INFO, + UMAC_CMD_MCST_ADDR_CFG, + UMAC_CMD_MCST_FLTR_CTRL, + UMAC_CMD_VHT_BEAMFORM_CTRL, + UMAC_CMD_ROC_CTRL, + UMAC_CMD_CHANNEL, + UMAC_CMD_VIF_CFG, + UMAC_CMD_STA, + UMAC_CMD_TXQ_PARAMS, + UMAC_CMD_MIB_STATS, + UMAC_CMD_PHY_STATS, + UMAC_CMD_NW_SELECTION, + UMAC_CMD_AUX_ADC_CHAIN_SEL, + UMAC_CMD_DETECT_RADAR, + UMAC_CMD_ENABLE_TX, + UMAC_CMD_DISCARD_PKTS, + UMAC_CMD_MEASURE, + UMAC_CMD_BT_INFO, + UMAC_CMD_CLEAR_STATS, +#ifdef MULTI_CHAN_SUPPORT + UMAC_CMD_CHANCTX_TIME_INFO, +#endif + UMAC_CMD_CONT_TX, +}; + +enum UMAC_EVENT_TAG { + UMAC_EVENT_RX = 0, + UMAC_EVENT_TX_DONE, + UMAC_EVENT_DISCONNECTED, + UMAC_EVENT_CONNECT_RESULT, + UMAC_EVENT_MIC_FAIL, + UMAC_EVENT_SCAN_COMPLETE, + UMAC_EVENT_SCAN_ABORT_COMPLETE, + UMAC_EVENT_MGMT_FRAME, + UMAC_EVENT_RESET_COMPLETE, + UMAC_EVENT_RSSI, + UMAC_EVENT_STA_INFO, + UMAC_EVENT_REKEY_DATA, + UMAC_EVENT_MIB_STAT, + UMAC_EVENT_PHY_STAT, + UMAC_EVENT_NW_FOUND, + UMAC_EVENT_NOA, + UMAC_EVENT_CTRL_POOL_ACK, + UMAC_EVENT_COMMAND_PROC_DONE, + UMAC_EVENT_CH_PROG_DONE, + UMAC_EVENT_PS_ECON_CFG_DONE, + UMAC_EVENT_PS_ECON_WAKE, + UMAC_EVENT_MAC_STATS, + UMAC_EVENT_RF_CALIB_DATA, + UMAC_EVENT_RADAR_DETECTED, + UMAC_EVENT_MSRMNT_COMPLETE, + UMAC_EVENT_ROC_STATUS, +#ifdef MULTI_CHAN_SUPPORT + UMAC_EVENT_CHAN_SWITCH, +#endif + UMAC_EVENT_FW_ERROR, +}; + +enum CONNECT_RESULT_TAG { + CONNECT_SUCCESS = 0, + CONNECT_UNSPECIFIED_FAILURE, + CONNECT_AUTH_FAILURE, + CONNECT_AUTH_TIMEOUT, + CONNECT_ASSOC_TIMEOUT, + CONNECT_ASSOC_FAILURE, + CONNECT_START_IBSS +}; + +enum UMAC_TX_FLAGS { + UMAC_TX_FLAG_OFFCHAN_FRM +}; + +/* Commands */ +struct cmd_tx_ctrl { + struct host_mac_msg_hdr hdr; + /* VIF nuber this packet belongs to */ + unsigned char if_index; + /* Queue no will be VO, VI, BE, BK and BCN */ + unsigned char queue_num; + + unsigned int descriptor_id; + + /* number of frames in tx descriptors */ + unsigned int num_frames_per_desc; + + /*packet lengths of frames*/ + unsigned int pkt_length[MAX_TX_CMDS]; + + /* If more number of frames buffered at UMAC */ + unsigned char more_frms; + + /* If this field is set for any packet, + * need to be transmit even though TX has been disabled + */ + unsigned int force_tx; + + /* Flags to communicate special cases regarding the frame to the FW */ + unsigned int tx_flags; + + unsigned char num_rates; + +#define USE_PROTECTION_NONE 0 +#define USE_PROTECTION_RTS 1 +#define USE_PROTECTION_CTS2SELF 2 + unsigned char rate_protection_type[4]; + +#define USE_SHORT_PREAMBLE 0 +#define DONT_USE_SHORT_PREAMBLE 1 + unsigned char rate_preamble_type[4]; + + unsigned char rate_retries[4]; + +#define MARK_RATE_AS_MCS_INDEX 0x80 +#define MARK_RATE_AS_RATE 0x00 + unsigned char rate[4]; + +#define ENABLE_GREEN_FIELD 0x01 +#define ENABLE_CHNL_WIDTH_40MHZ 0x02 +#define ENABLE_SGI 0x04 +#define ENABLE_11N_FORMAT 0x08 +#define ENABLE_VHT_FORMAT 0x10 +#define ENABLE_CHNL_WIDTH_80MHZ 0x20 + + unsigned char rate_flags[4]; + unsigned char num_spatial_streams[4]; + unsigned char stbc_enabled; + unsigned char bcc_or_ldpc; + +#define AMPDU_AGGR_ENABLED 0x00000001 +#define AMPDU_AGGR_DISABLED 0x00000000 + unsigned char aggregate_mpdu; + +#define ENCRYPT_DISABLE 0 +#define ENCRYPT_ENABLE 1 + unsigned char encrypt; + + +#define MAC_HDR_SIZE 52 + unsigned int pkt_gram_payload_len; + /* It will be of the form It [MAX_TX_CMDS][54] + * using dynamic because max stack size is 1024 bytes + */ + unsigned char gram_payload[0]; +} __packed; + +struct bgscan_params { + unsigned int enabled; + unsigned char channel_list[50]; + unsigned char channel_flags[50]; + unsigned int scan_intval; + unsigned int channel_dur; + unsigned int serv_channel_dur; + unsigned int num_channels; +} __packed; + +struct cmd_reset { + struct host_mac_msg_hdr hdr; + #define LMAC_ENABLE 0 + #define LMAC_DISABLE 1 + unsigned int type; + int ed_sensitivity; + unsigned int auto_sensitivity; + unsigned char rf_params[RF_PARAMS_SIZE]; + unsigned int include_rxmac_hdr; + struct bgscan_params bg_scan; + unsigned char num_spatial_streams; + unsigned int system_rev; + #define LMAC_MODE_NORMAL 0 + #define LMAC_MODE_FTM 1 + unsigned int lmac_mode; + unsigned int antenna_sel; +} __packed; + +enum SCAN_TYPE_TAG { + PASSIVE = 0, + ACTIVE +}; + +struct ssid { + unsigned int len; + unsigned char ssid[MAX_SSID_LEN]; +} __packed; + +struct cmd_scan { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + enum SCAN_TYPE_TAG type; + + /* Total number of channels to scan; channel numbers will be + * informed in channel array. if n_channel value is zero, + * UMAC scans all possible channels. + */ + unsigned int n_channel; + + /* Number of SSIDs to scan; ssid information will be in ssid array. + * This is always >= 1. In case of wild card SSID, this value is 1 and + * the ssid_len of the first entry in the SSID list should be specifie + * as 0 + */ + unsigned int n_ssids; + unsigned char channel_list[50]; + unsigned char chan_max_power[50]; + unsigned char chan_flags[50]; + struct ssid ssids[MAX_NUM_SSIDS]; + unsigned int p2p_probe; + unsigned int extra_ies_len; + unsigned char extra_ies[0]; +} __packed; + +struct cmd_scan_abort { + struct host_mac_msg_hdr hdr; + unsigned int if_index; +} __packed; + +struct cmd_nw_selection { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int p2p_selection; + struct ssid ssid; + unsigned int scan_req_ie_len; + unsigned int scan_resp_ie_len; + unsigned char scan_req_ie[200]; + unsigned char scan_resp_ie[200]; +} __packed; + +struct cmd_set_mode { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int type; +} __packed; + +struct cmd_setkey { + struct host_mac_msg_hdr hdr; + unsigned int if_index; +#define KEY_CTRL_ADD 0 +#define KEY_CTRL_DEL 1 + unsigned int ctrl; +#define KEY_TYPE_UCAST 0 +#define KEY_TYPE_BCAST 1 + unsigned int key_type; + +#define CIPHER_TYPE_WEP40 0 +#define CIPHER_TYPE_WEP104 1 +#define CIPHER_TYPE_TKIP 2 +#define CIPHER_TYPE_CCMP 3 +#define CIPHER_TYPE_WAPI 4 + unsigned int cipher_type; + unsigned int key_id; + int key_len; + int rsc_len; + unsigned char mac_addr[ETH_ALEN]; + unsigned char key[TOTAL_KEY_LEN]; + unsigned char rsc[RX_SEQ_SIZE]; +} __packed; + + +struct cmd_set_defaultkey { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int key_id; +} __packed; + +struct cmd_set_rekey { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned char kek[16]; + unsigned char kck[16]; + unsigned char replay_ctr[8]; +} __packed; + +struct cmd_frag_tag { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int frag_threshold; +} __packed; + +struct cmd_tx_pwr { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + int tx_pwr; +} __packed; + +struct cmd_disconnect { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + int reason_code; +} __packed; + +struct cmd_rate { + struct host_mac_msg_hdr hdr; + int is_mcs; + int rate; +} __packed; + +struct cmd_mcst_addr_cfg { + struct host_mac_msg_hdr hdr; + /* mcst_ctrl - + * 0 -- ADD multicast address + * 1 -- Remove multicast address + */ +#define WLAN_MCAST_ADDR_ADD 0 +#define WLAN_MCAST_ADDR_REM 1 + unsigned int op; + /* addr to add or delete.. + */ + unsigned char mac_addr[6]; +} __packed; + +struct cmd_mcst_filter_ctrl { + struct host_mac_msg_hdr hdr; + /* ctrl - + * 0 - disable multicast filtering in LMAC + * 1 - enable multicast filtering in LMAC + */ +#define MCAST_FILTER_DISABLE 0 +#define MCAST_FILTER_ENABLE 1 + unsigned int ctrl; +} __packed; + +struct cmd_vht_beamform { + struct host_mac_msg_hdr hdr; +#define VHT_BEAMFORM_DISABLE 0 +#define VHT_BEAMFORM_ENABLE 1 + unsigned int vht_beamform_status; + unsigned int vht_beamform_period; +} __packed; + +struct cmd_roc { + struct host_mac_msg_hdr hdr; +#define ROC_STOP 0 +#define ROC_START 1 + unsigned int roc_ctrl; + unsigned int roc_channel; + unsigned int roc_duration; +#define ROC_TYPE_NORMAL 0 +#define ROC_TYPE_OFFCHANNEL_TX 1 + unsigned int roc_type; +} __packed; + +enum POWER_SAVE_TAG { + AWAKE = 0, + SLEEP +}; + +struct cmd_ps { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + enum POWER_SAVE_TAG mode; +} __packed; + +struct cmd_vifctrl { + struct host_mac_msg_hdr hdr; + /* if_ctrl - + * 0 - add interface address + * 1 - remove interface address + */ +#define IF_ADD 1 +#define IF_REM 2 + + unsigned int if_ctrl; + unsigned int if_index; + /* Interface mode - + * 0 - STA in infrastucture mode + * 1 - STA in AD-HOC mode + * 2 - AP + */ +#define IF_MODE_STA_BSS 0 +#define IF_MODE_STA_IBSS 1 +#define IF_MODE_AP 2 +#define IF_MODE_INVALID 3 + + unsigned int mode; + unsigned char mac_addr[ETH_ALEN]; +} __packed; + +struct cmd_set_beacon { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int interval; + unsigned int dtim_period; + unsigned int len; + unsigned char mac_addr[6]; + unsigned int channel; + unsigned char beacon_buf[0]; +} __packed; + +struct cmd_ht_ba { + struct host_mac_msg_hdr hdr; + unsigned int if_index; +#define BLOCK_ACK_SESSION_STOP 0 +#define BLOCK_ACK_SESSION_START 1 + unsigned int op; + unsigned int tid; + unsigned int ssn; + unsigned int policy; + /* vif address */ + unsigned char vif_addr[ETH_ALEN]; + /* peer address */ + unsigned char peer_addr[ETH_ALEN]; +} __packed; + +struct cmd_channel { + struct host_mac_msg_hdr hdr; + /* channel bw + * 0 - 20 + * 1 - 40 + * 2 - 80 + * 3 - 160 + */ + unsigned int channel_bw; + unsigned int primary_ch_number; + /* center frequecny of total band, if toal band is contiguous. + * First band center frequency For non contiguous bands, + */ + unsigned int channel_number1; + /* center frequecny of secondary band. + * This is valid in 80+80 band set to zero for other cases + */ + unsigned int channel_number2; + /* 0 - 2.4ghz + * 1 - 5ghz + */ + unsigned int freq_band; +#ifdef MULTI_CHAN_SUPPORT + unsigned int vif_index; +#endif +} __packed; + +struct cmd_vif_cfg { + struct host_mac_msg_hdr hdr; + + /* Bitmap indicating whether value is changed or not */ +#define BASICRATES_CHANGED (1<<0) +#define SHORTSLOT_CHANGED (1<<1) +#define POWERSAVE_CHANGED (1<<2) /* to be removed */ +#define UAPSDTYPE_CHANGED (1<<3) /* to be removed */ +#define ATIMWINDOW_CHANGED (1<<4) +#define AID_CHANGED (1<<5) +#define CAPABILITY_CHANGED (1<<6) +#define SHORTRETRY_CHANGED (1<<7) +#define LONGRETRY_CHANGED (1<<8) +#define BSSID_CHANGED (1<<9) +#define RCV_BCN_MODE_CHANGED (1<<10) +#define BCN_INT_CHANGED (1<<11) +#define DTIM_PERIOD_CHANGED (1<<12) +#define SMPS_CHANGED (1<<13) +#define CONNECT_STATE_CHANGED (1<<14) +#define OP_CHAN_CHANGED (1<<15) + + unsigned int changed_bitmap; + + /* bitmap of supported basic rates + */ + unsigned int basic_rate_set; + + /* slot type - + * 0 - long slot + * 1 - short slot + */ + unsigned int use_short_slot; + + /* ATIM window */ + unsigned int atim_window; + + unsigned int aid; + + unsigned int capability; + + unsigned int short_retry; + + unsigned int long_retry; + +#define RCV_ALL_BCNS 0 +#define RCV_ALL_NETWORK_ONLY 1 +#define RCV_NO_BCNS 2 + + unsigned int bcn_mode; + + unsigned char dtim_period; + + unsigned int beacon_interval; + + /* index of the intended interface */ + unsigned int if_index; + unsigned char vif_addr[ETH_ALEN]; + + /* bssid of interface */ + unsigned char bssid[ETH_ALEN]; + + /* SMPS Info + * + * bit0 - 0 - Disabled, 1 - Enabled + * bit1 - 0 - Static, 1 - Dynamic + * + */ +#define SMPS_ENABLED BIT(0) +#define SMPS_MODE BIT(1) + unsigned char smps_info; + +#define STA_CONN 0 +#define STA_DISCONN 1 + unsigned char connect_state; + unsigned char op_channel; +} __packed; + +struct cmd_sta { + struct host_mac_msg_hdr hdr; + unsigned int if_index; +#define ADD 0 +#define REM 1 + unsigned int op; +#define STA_NUM_BANDS 2 + unsigned int supp_rates[STA_NUM_BANDS]; +/*HT Info */ + unsigned int ht_cap; /* use IEEE80211_HT_CAP_ */ + unsigned int ht_supported; + unsigned int vht_cap; /* use IEEE80211_VHT_CAP_ */ + unsigned int vht_supported; + unsigned int ampdu_factor; + unsigned int ampdu_density; + unsigned int rx_highest; + unsigned int tx_params; +#define HT_MCS_MASK_LEN 10 + unsigned char rx_mask[HT_MCS_MASK_LEN]; + unsigned char addr[ETH_ALEN]; + unsigned char dot11_mode; + unsigned char no_of_streams; + unsigned char preamble; + unsigned char stbc_enable; + unsigned char ldpc_enable; + unsigned char guard_interval; + unsigned char aggregation; + unsigned char tid; + unsigned char band_width; +} __packed; + +struct cmd_txq_params { + struct host_mac_msg_hdr hdr; + unsigned int queue_num; + unsigned int aifsn; + unsigned int txop; + unsigned int cwmin; + unsigned int cwmax; + /* power save mode - + * 0 - indicates legacy mode powersave, 1 - indicates UAPSD for the + * corresponding AC. + */ + unsigned int uapsd; + unsigned int if_index; + unsigned char vif_addr[ETH_ALEN]; +} __packed; + +struct cmd_aux_adc_chain_sel { + struct host_mac_msg_hdr hdr; +#define AUX_ADC_CHAIN1 1 +#define AUX_ADC_CHAIN2 2 + unsigned int chain_id; +} __packed; + +struct cmd_cont_tx { + struct host_mac_msg_hdr hdr; + unsigned int op; +} __packed; + + + +/* DFS SUPPORT */ +/* Command to start/stop Radar detection operation */ +struct cmd_detect_radar { + struct host_mac_msg_hdr hdr; + /* 1 - Radar detection operation to be started + * 0 - Radar detection operation to be stopped + */ +#define RADAR_DETECT_OP_START 1 +#define RADAR_DETECT_OP_STOP 0 + unsigned int radar_detect_op; +} __packed; + +/* Command to enable TX.which would have been disabled previously.*/ +struct umac_cmd_tx_enable { + struct host_mac_msg_hdr hdr; +} __packed; + +/* Command to discard all packets in TX queue */ +struct cmd_discard_pkts { + struct host_mac_msg_hdr hdr; +} __packed; + +/* Command to do measurement on a channel + * start_time : when to start the measurement. + * msr_dur : How long measurement to be carried out. + */ +struct cmd_msrmnt_start { + struct host_mac_msg_hdr hdr; + unsigned char start_time[8]; + unsigned short msr_dur; +} __packed; + + +#ifdef MULTI_CHAN_SUPPORT +struct chanctx_time_info { + int chan; + int percentage; +}; + +struct cmd_chanctx_time_config { + struct host_mac_msg_hdr hdr; + struct chanctx_time_info info[MAX_CHANCTX]; +} __packed; +#endif + +/* Events */ + +struct nw_found_event { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + struct ssid ssid; +} __packed; + +struct host_event_mgmt_rx { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + int rssi; + unsigned int rate; + unsigned int padding; +} __packed; + +struct host_event_command_complete { + struct host_mac_msg_hdr hdr; +} __packed; + +struct bssres { + unsigned int channel; + int rssi; + unsigned int frame_len; + unsigned char frame_buf[0]; +} __packed; + +struct host_event_scanres { + struct host_mac_msg_hdr hdr; + int if_index; + unsigned int scanres_len; /* This will include total length of + * scanresult, including it's own length + */ + unsigned int status_code; + unsigned int no_of_bss; + unsigned int more_results; /* 0 - No more results, 1- Moreresults */ + unsigned char bss_res[0]; /* One or more elements of type bssres_t */ +} __packed; + +struct host_event_connect_result { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + enum CONNECT_RESULT_TAG result_code; + unsigned int aid; + unsigned int cap_info; + int ht_supported; + unsigned short ht_cap_info; + int vht_supported; + unsigned short vht_cap_info; + unsigned int qos_capability; + unsigned int wmm_acm; + unsigned int channel; + unsigned char bssid[ETH_ALEN]; + unsigned int ie_len; + unsigned char ie[MAX_IE_LEN]; + struct bssres bss_frame; +} __packed; + +struct host_event_rssi { + struct host_mac_msg_hdr hdr; + int if_index; + int rssi; +} __packed; + +struct host_event_disconnect { + struct host_mac_msg_hdr hdr; + int if_index; +#define REASON_DEAUTH 1 +#define REASON_AUTH_FAILURE 2 +#define REASON_NW_LOST 3 +#define REASON_AUTH_TIMEOUT 4 +#define REASON_TX_TOKEN_NOTAVAIL 5 +#define REASON_ASSOC_TIMEOUT 6 + unsigned int reason_code; + unsigned char mac_addr[ETH_ALEN]; +} __packed; + +struct host_event_reset_complete { + struct host_mac_msg_hdr hdr; + unsigned int cap; + unsigned int ht_supported; + unsigned int ampdu_factor; + unsigned int ampdu_density; +#define HT_MCS_MASK_LEN 10 + unsigned int rx_mask[HT_MCS_MASK_LEN]; + unsigned int rx_highest; + unsigned int tx_params; + char version[6]; +} __packed; + +struct host_event_rekey_data { + struct host_mac_msg_hdr hdr; + unsigned int if_index; + unsigned int cipher; + unsigned int key_idx; + unsigned int key_len; + unsigned int rsc_len; + unsigned char rsc[8]; + unsigned char key[MAX_KEY_LEN]; +} __packed; + +struct host_event_phy_stats { + struct host_mac_msg_hdr hdr; + unsigned int phy_stats[64]; +} __packed; + +/* DFS SUPPORT*/ +/* Event to be generated on radar detection */ +struct host_event_radar_detected { + struct host_mac_msg_hdr hdr; + int freq; +} __packed; + +/* Event generated on measurement completion with measurement status */ +struct host_event_msrmnt_complete { + struct host_mac_msg_hdr hdr; + /* MSR MNT STAUTS - + * For bit representation Refer 7.3.2.22.1 of Std 802.11h-2003 + */ +#define UMAC_EVENT_MSRMNT_STATUS_BSS (0x01) +#define UMAC_EVENT_MSRMNT_STATUS_OFDM_PREAMBLE (0x02) +#define UMAC_EVENT_MSRMNT_STATUS_UNIDENTIFIED_SIGNAL (0x04) +#define UMAC_EVENT_MSRMNT_STATUS_RADAR_SIGNAL (0x08) +#define UMAC_EVENT_MSRMNT_STATUS_NO_MSRMNT (0x10) +#define UMAC_EVENT_MSRMNT_STATUS_LATE (0x20) +#define UMAC_EVENT_MSRMNT_STATUS_INCAPABLE (0x40) +#define UMAC_EVENT_MSRMNT_STATUS_REFUSE (0x80) + unsigned int msrmnt_status; +} __packed; + + +struct umac_event_ch_switch_complete { + struct host_mac_msg_hdr hdr; + int status; +} __packed; + +struct umac_event_rf_calib_data { + struct host_mac_msg_hdr hdr; + unsigned int rf_calib_data_length; + unsigned char rf_calib_data[0]; +} __packed; + +struct cmd_bt_info { + struct host_mac_msg_hdr hdr; +#define BT_STATE_OFF 0 +#define BT_STATE_ON 1 + unsigned int bt_state; +} __packed; + +struct umac_event_roc_status { + struct host_mac_msg_hdr hdr; + unsigned int roc_status; +} __packed; + +#endif /*_UCCP420HOST_UMAC_IF_H_*/ diff --git a/drivers/net/wireless/uccp420wlan/inc/umac_if.h b/drivers/net/wireless/uccp420wlan/inc/umac_if.h new file mode 100644 index 00000000000000..36de89110f903a --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/umac_if.h @@ -0,0 +1,306 @@ +/*HEADER********************************************************************** +****************************************************************************** +*** +*** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, +*** USA. +*** +*** File Name : lmac_if.h +*** +*** File Description: +*** This file contains the helper functions exported by LMAC interface module +*** for sending commands and receiving events from the LMAC +***************************************************************************** +*END**************************************************************************/ + +#ifndef _UCCP420WLAN_UMAC_IF_H_ +#define _UCCP420WLAN_UMAC_IF_H_ +#include +#include "hal.h" +#include "host_umac_if.h" + +#define UMAC_ROC_AC WLAN_AC_VO + +struct umac_key { + unsigned char *peer_mac; + unsigned char *tx_mic; + unsigned char *rx_mic; + unsigned char *key; +}; + +struct ssid_desc { + unsigned char ssid[MAX_SSID_LEN]; + unsigned char ssid_len; +}; + +struct scan_req { + unsigned int n_channels; + int n_ssids; + unsigned int ie_len; + unsigned char ie[256]; + unsigned int p2p_probe; + /*TODO: Make this a structure*/ + unsigned short center_freq[50]; + unsigned char freq_max_power[50]; + unsigned char chan_flags[50]; + struct ssid_desc ssids[MAX_NUM_SSIDS]; +}; + +struct peer_sta_info { + unsigned int ht_cap; + unsigned int ht_supported; + unsigned int ampdu_factor; + unsigned int ampdu_density; + unsigned int vht_cap; + unsigned int vht_supported; + unsigned int rx_highest; + unsigned int tx_params; + unsigned int supp_rates[STA_NUM_BANDS]; + unsigned char addr[ETH_ALEN]; + unsigned char rx_mask[HT_MCS_MASK_LEN]; + unsigned char uapsd_queues; +}; + +/*commands*/ +extern int uccp420wlan_scan(int index, + struct scan_req *req); + +extern int uccp420wlan_scan_abort(int index); + +extern int uccp420wlan_proc_tx(void); + +extern int uccp420wlan_prog_tx(unsigned int queue, + unsigned int more_data, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + unsigned int tokenid, + bool retry); + +extern int uccp420wlan_sta_add(int index, + struct peer_sta_info *sta); + +extern int uccp420wlan_sta_remove(int index, + struct peer_sta_info *sta); + +extern int uccp420wlan_set_rate(int rate, + int mcs); + +extern int uccp420wlan_prog_reset(unsigned int reset_type, + unsigned int lmac_mode); + +extern int uccp420wlan_prog_vif_ctrl(int index, + unsigned char *vif_addr, + unsigned int vif_type, + unsigned int add_vif); + +extern int uccp420wlan_prog_vif_basic_rates(int index, + unsigned char *vif_addr, + unsigned int basic_rate_set); + +extern int uccp420wlan_prog_vif_short_slot(int index, + unsigned char *vif_addr, + unsigned int use_short_slot); + +extern int uccp420wlan_prog_vif_atim_window(int index, + unsigned char *vif_addr, + unsigned int atim_window); + +extern int uccp420wlan_prog_vif_aid(int index, + unsigned char *vif_addr, + unsigned int aid); + +extern int uccp420wlan_prog_vif_op_channel(int index, + unsigned char *vif_addr, + unsigned char op_channel); + +extern int uccp420wlan_prog_vif_conn_state(int index, + unsigned char *vif_addr, + unsigned int state); + +extern int uccp420wlan_prog_vif_assoc_cap(int index, + unsigned char *vif_addr, + unsigned int caps); + +extern int uccp420wlan_prog_vif_beacon_int(int index, + unsigned char *vif_addr, + unsigned int bcn_int); + +extern int uccp420wlan_prog_vif_dtim_period(int index, + unsigned char *vif_addr, + unsigned int dtim_period); + +extern int uccp420wlan_prog_vif_apsd_type(int index, + unsigned char *vif_addr, + unsigned int uapsd_type); + +extern int uccp420wlan_prog_long_retry(int index, + unsigned char *vif_addr, + unsigned int long_retry); + +extern int uccp420wlan_prog_short_retry(int index, + unsigned char *vif_addr, + unsigned int short_retry); + +extern int uccp420wlan_prog_vif_bssid(int index, + unsigned char *vif_addr, + unsigned char *bssid); + +extern int uccp420wlan_prog_vif_smps(int index, + unsigned char *vif_addr, + unsigned char smps_mode); + +extern int uccp420wlan_prog_ps_state(int index, + unsigned char *vif_addr, + unsigned int powersave_state); + +extern int uccp420wlan_prog_global_cfg(unsigned int rx_msdu_lifetime, + unsigned int tx_msdu_lifetime, + unsigned int sensitivity, + unsigned int dyn_ed_enabled, + unsigned char *rf_params); + +extern int uccp420wlan_prog_txpower(unsigned int txpower); + +extern int uccp420wlan_prog_btinfo(unsigned int bt_state); + +extern int uccp420wlan_prog_mcast_addr_cfg(unsigned char *mcast_addr, + unsigned int add_filter); + +extern int uccp420wlan_prog_mcast_filter_control(unsigned int + enable_mcast_filtering); + +extern int uccp420wlan_prog_rcv_bcn_mode(unsigned int bcn_rcv_mode); +extern int uccp420wlan_prog_aux_adc_chain(unsigned int chain_id); +extern int uccp420wlan_prog_cont_tx(int val); +extern int uccp420wlan_prog_txq_params(int index, + unsigned char *vif_addr, + unsigned int queue, + unsigned int aifs, + unsigned int txop, + unsigned int cwmin, + unsigned int cwmax, + unsigned int uapsd); + +extern int uccp420wlan_prog_channel(unsigned int prim_ch, + unsigned int center_freq1, + unsigned int center_freq2, + unsigned int ch_width, +#ifdef MULTI_CHAN_SUPPORT + unsigned int vif_index, +#endif + unsigned int freq_band); + +extern int uccp420wlan_prog_peer_key(int index, + unsigned char *vif_addr, + unsigned int op, + unsigned int key_id, + unsigned int key_type, + unsigned int cipher_type, + struct umac_key *key); + +extern int uccp420wlan_prog_if_key(int index, + unsigned char *vif_addr, + unsigned int op, + unsigned int key_id, + unsigned int cipher_type, + struct umac_key *key); + +extern int uccp420wlan_prog_mib_stats(void); + +extern int uccp420wlan_prog_clear_stats(void); + +extern int uccp420wlan_prog_phy_stats(void); + +extern int uccp420wlan_prog_ba_session_data(unsigned int op, + unsigned short tid, + unsigned short *ssn, + unsigned short ba_policy, + unsigned char *sta_addr, + unsigned char *peer_add); + +extern int uccp420wlan_prog_vht_bform(unsigned int vht_beamform_status, + unsigned int vht_beamform_period); + +extern int uccp420wlan_prog_roc(unsigned int roc_status, + unsigned int roc_channel, + unsigned int roc_duration, + unsigned int roc_type); + +extern int uccp420wlan_prog_radar_detect(unsigned int op_code); + +#ifdef CONFIG_PM +extern int uccp420wlan_prog_econ_ps_state(int if_index, + unsigned int ps_state); +#endif + +/* Events */ +extern void uccp420wlan_scan_complete(void *context, + struct host_event_scanres *scan_res, + unsigned char *skb, + unsigned int len); + +extern void uccp420wlan_reset_complete(char *lmac_version, + void *context); + +extern void uccp420wlan_rf_calib_data(struct umac_event_rf_calib_data *rf_data, + void *context); + +extern void uccp420wlan_proc_tx_complete(struct umac_event_tx_done *txdone, + void *context); + +extern void uccp420wlan_tx_complete(struct umac_event_tx_done *txdone, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + void *context); + +extern void uccp420wlan_rx_frame(struct sk_buff *skb, + void *context); + +extern void uccp420wlan_mib_stats(struct umac_event_mib_stats *mib_stats, + void *context); + +extern void uccp420wlan_mac_stats(struct umac_event_mac_stats *mac_stats, + void *context); + +extern void uccp420wlan_noa_event(int event, + struct umac_event_noa *noa_event, + void *context, + struct sk_buff *skb); + +extern void uccp420wlan_ch_prog_complete(int event, + struct umac_event_ch_prog_complete *ch, + void *context); + +/* Init/Deinit */ + +extern int uccp420wlan_lmac_if_init(void *context, + const char *name); + +extern void uccp420wlan_lmac_if_deinit(void); + +extern void uccp420_lmac_if_free_outstnding(void); + +#ifdef MULTI_CHAN_SUPPORT +extern int uccp420wlan_prog_chanctx_time_info(void); +#endif + +#endif /* _UCCP420WLAN_UMAC_IF_H_ */ + +/* EOF */ + diff --git a/drivers/net/wireless/uccp420wlan/inc/utils.h b/drivers/net/wireless/uccp420wlan/inc/utils.h new file mode 100644 index 00000000000000..f5fb5e60c8d929 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/utils.h @@ -0,0 +1,46 @@ +/* + * File Name : utils.h + * + * This file contains helper macros and data structures used across the code + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420WLAN_UTILS_H +#define _UCCP420WLAN_UTILS_H + + +#define MASK_BITS(msb, lsb) (((1U << ((msb) - (lsb) + 1)) - 1) \ + << (lsb)) + +#define EXTRACT_BITS(arg, msb, lsb) ((arg & MASK_BITS(msb, lsb)) >> (lsb)) + +#define INSERT_BITS(arg, msb, lsb, value) ((arg) = ((arg) & \ + ~MASK_BITS(msb, lsb)) | \ + (((value) << (lsb)) & \ + MASK_BITS(msb, lsb))) + +#define FRAME_CTRL_TYPE(arg) EXTRACT_BITS(arg, 3, 2) +#define FRAME_CTRL_STYPE(arg) EXTRACT_BITS(arg, 7, 4) +#define FTYPE_DATA 0x02 +#define FSTYPE_QOS_DATA 0x08 + +#endif /* _UCCP420WLAN_UTILS_H */ + +/* EOF */ diff --git a/drivers/net/wireless/uccp420wlan/inc/version.h b/drivers/net/wireless/uccp420wlan/inc/version.h new file mode 100644 index 00000000000000..b173a5475bf827 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/inc/version.h @@ -0,0 +1,31 @@ +/* + * File Name : version.h + * + * This file contains the UMAC version string + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef _UCCP420WLAN_VERSION_H +#define _UCCP420WLAN_VERSION_H +#define UCCP_DRIVER_VERSION "6_0_4" +#define UCCP_DRIVER_NAME "UCCP420WIFI" +#endif /* _UCCP420WLAN_VERSION_H */ + +/* EOF */ + diff --git a/drivers/net/wireless/uccp420wlan/src/80211_if.c b/drivers/net/wireless/uccp420wlan/src/80211_if.c new file mode 100644 index 00000000000000..2239c75fb03dae --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/80211_if.c @@ -0,0 +1,4751 @@ +/* + * File Name : 80211_if.c + * + * This file is the glue layer between net/mac80211 and UMAC + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "core.h" +#include "utils.h" + +#include + +#include + +#define UCCP_DEBUG_80211IF(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_80211IF) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_CRYPTO(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_CRYPTO) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +/* Its value will be the default mac address and it can only be updated with the + * command line arguments + */ +unsigned int vht_support = 1; +module_param(vht_support, int, 0); +MODULE_PARM_DESC(vht_support, "Configure the 11ac support for this device"); + +static unsigned int ftm; +module_param(ftm, int, 0); +MODULE_PARM_DESC(ftm, "Factory Test Mode, should be used only for calibrations."); + +unsigned int system_rev = 0x494D47; /*ASCII: IMG*/ + +int uccp_debug = UCCP_DEBUG_80211IF + UCCP_DEBUG_CRYPTO + UCCP_DEBUG_CORE; + +module_param(uccp_debug, uint, 0); +MODULE_PARM_DESC(uccp_debug, " uccp_debug: Configure Debugging Mask"); +static void uccp420_roc_complete_work(struct work_struct *work); +static void uccp420wlan_exit(void); +static int load_fw(struct ieee80211_hw *hw); +static char *uccp420_get_vif_name(int vif_idx); +int uccp_reinit; + +#ifdef CONFIG_PM +unsigned char img_suspend_status; +#endif + +#define CHAN2G(_freq, _idx) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ +} + +#define CHAN5G(_freq, _idx, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ + .flags = (_flags), \ +} + +struct wifi_dev { + struct proc_dir_entry *umac_proc_dir_entry; + struct wifi_params params; + struct wifi_stats stats; + struct ieee80211_hw *hw; +}; + +static struct wifi_dev *wifi; + +static struct ieee80211_channel dsss_chantable[] = { + CHAN2G(2412, 0), /* Channel 1 */ + CHAN2G(2417, 1), /* Channel 2 */ + CHAN2G(2422, 2), /* Channel 3 */ + CHAN2G(2427, 3), /* Channel 4 */ + CHAN2G(2432, 4), /* Channel 5 */ + CHAN2G(2437, 5), /* Channel 6 */ + CHAN2G(2442, 6), /* Channel 7 */ + CHAN2G(2447, 7), /* Channel 8 */ + CHAN2G(2452, 8), /* Channel 9 */ + CHAN2G(2457, 9), /* Channel 10 */ + CHAN2G(2462, 10), /* Channel 11 */ + CHAN2G(2467, 11), /* Channel 12 */ + CHAN2G(2472, 12), /* Channel 13 */ + CHAN2G(2484, 13), /* Channel 14 */ +}; + +static struct ieee80211_channel ofdm_chantable[] = { + CHAN5G(5180, 14, 0), /* Channel 36 */ + CHAN5G(5200, 15, 0), /* Channel 40 */ + CHAN5G(5220, 16, 0), /* Channel 44 */ + CHAN5G(5240, 17, 0), /* Channel 48 */ + CHAN5G(5260, 18, IEEE80211_CHAN_RADAR), /* Channel 52 */ + CHAN5G(5280, 19, IEEE80211_CHAN_RADAR), /* Channel 56 */ + CHAN5G(5300, 20, IEEE80211_CHAN_RADAR), /* Channel 60 */ + CHAN5G(5320, 21, IEEE80211_CHAN_RADAR), /* Channel 64 */ + CHAN5G(5500, 22, IEEE80211_CHAN_RADAR), /* Channel 100 */ + CHAN5G(5520, 23, IEEE80211_CHAN_RADAR), /* Channel 104 */ + CHAN5G(5540, 24, IEEE80211_CHAN_RADAR), /* Channel 108 */ + CHAN5G(5560, 25, IEEE80211_CHAN_RADAR), /* Channel 112 */ + CHAN5G(5580, 26, IEEE80211_CHAN_RADAR), /* Channel 116 */ + CHAN5G(5600, 27, IEEE80211_CHAN_RADAR), /* Channel 120 */ + CHAN5G(5620, 28, IEEE80211_CHAN_RADAR), /* Channel 124 */ + CHAN5G(5640, 29, IEEE80211_CHAN_RADAR), /* Channel 128 */ + CHAN5G(5660, 30, IEEE80211_CHAN_RADAR), /* Channel 132 */ + CHAN5G(5680, 31, IEEE80211_CHAN_RADAR), /* Channel 136 */ + CHAN5G(5700, 32, IEEE80211_CHAN_RADAR), /* Channel 140 */ + CHAN5G(5720, 33, IEEE80211_CHAN_RADAR), /* Channel 144 */ + CHAN5G(5745, 34, 0), /* Channel 149 */ + CHAN5G(5765, 35, 0), /* Channel 153 */ + CHAN5G(5785, 36, 0), /* Channel 157 */ + CHAN5G(5805, 37, 0), /* Channel 161 */ + CHAN5G(5825, 38, 0), /* Channel 165 */ +}; + +static struct ieee80211_rate dsss_rates[] = { + { .bitrate = 10, .hw_value = 2}, + { .bitrate = 20, .hw_value = 4, + .flags = IEEE80211_RATE_SHORT_PREAMBLE}, + { .bitrate = 55, .hw_value = 11, + .flags = IEEE80211_RATE_SHORT_PREAMBLE}, + { .bitrate = 110, .hw_value = 22, + .flags = IEEE80211_RATE_SHORT_PREAMBLE}, + { .bitrate = 60, .hw_value = 12}, + { .bitrate = 90, .hw_value = 18}, + { .bitrate = 120, .hw_value = 24}, + { .bitrate = 180, .hw_value = 36}, + { .bitrate = 240, .hw_value = 48}, + { .bitrate = 360, .hw_value = 72}, + { .bitrate = 480, .hw_value = 96}, + { .bitrate = 540, .hw_value = 108} +}; + +static struct ieee80211_rate ofdm_rates[] = { + { .bitrate = 60, .hw_value = 12}, + { .bitrate = 90, .hw_value = 18}, + { .bitrate = 120, .hw_value = 24}, + { .bitrate = 180, .hw_value = 36}, + { .bitrate = 240, .hw_value = 48}, + { .bitrate = 360, .hw_value = 72}, + { .bitrate = 480, .hw_value = 96}, + { .bitrate = 540, .hw_value = 108} +}; + +static struct ieee80211_supported_band band_2ghz = { + .channels = dsss_chantable, + .n_channels = ARRAY_SIZE(dsss_chantable), + .band = IEEE80211_BAND_2GHZ, + .bitrates = dsss_rates, + .n_bitrates = ARRAY_SIZE(dsss_rates), +}; + +static struct ieee80211_supported_band band_5ghz = { + .channels = ofdm_chantable, + .n_channels = ARRAY_SIZE(ofdm_chantable), + .band = IEEE80211_BAND_5GHZ, + .bitrates = ofdm_rates, + .n_bitrates = ARRAY_SIZE(ofdm_rates), +}; + + +/* Interface combinations for Virtual interfaces */ +static const struct ieee80211_iface_limit if_limit1[] = { + { .max = 2, .types = BIT(NL80211_IFTYPE_STATION)} +}; + +static const struct ieee80211_iface_limit if_limit2[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION)}, + { .max = 1, .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_P2P_GO)} +}; + +static const struct ieee80211_iface_limit if_limit3[] = { + { .max = 2, .types = BIT(NL80211_IFTYPE_P2P_CLIENT)} +}; + +static const struct ieee80211_iface_limit if_limit4[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC)}, + { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_CLIENT)} +}; + +#ifdef MULTI_CHAN_SUPPORT +static const struct ieee80211_iface_limit if_limit5[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION)}, + { .max = 1, .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT)} +}; +#endif + +static const struct ieee80211_iface_limit if_limit6[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_AP)} +}; + +static const struct ieee80211_iface_limit if_limit7[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION)} +}; + + +static const struct ieee80211_iface_combination if_comb[] = { + { .limits = if_limit1, + .n_limits = ARRAY_SIZE(if_limit1), + .max_interfaces = 2, + .num_different_channels = 1}, + { .limits = if_limit2, + .n_limits = ARRAY_SIZE(if_limit2), + .max_interfaces = 2, + .num_different_channels = 1}, + { .limits = if_limit3, + .n_limits = ARRAY_SIZE(if_limit3), + .max_interfaces = 2, + .num_different_channels = 1}, +#ifdef MULTI_CHAN_SUPPORT + { .limits = if_limit5, + .n_limits = ARRAY_SIZE(if_limit5), + .max_interfaces = 2, + .num_different_channels = 2}, + { .limits = if_limit1, + .n_limits = ARRAY_SIZE(if_limit1), + .max_interfaces = 2, + .num_different_channels = 2}, +#endif + { .limits = if_limit4, + .n_limits = ARRAY_SIZE(if_limit4), + .max_interfaces = 2, + .num_different_channels = 1}, +#ifdef NOT_YET + { .limits = if_limit6, + .n_limits = ARRAY_SIZE(if_limit6), + .max_interfaces = 1, + .num_different_channels = 1, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160)}, +#endif + { .limits = if_limit7, + .n_limits = ARRAY_SIZE(if_limit7), + .max_interfaces = 1, + .num_different_channels = 1, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160)} +}; + + +/* For getting the dev pointer */ +static struct class *hwsim_class; + +static const struct wiphy_wowlan_support uccp_wowlan_support = { + .flags = WIPHY_WOWLAN_ANY, +}; + +static int conv_str_to_byte(unsigned char *byte, + unsigned char *str, + int len) +{ + int i, j = 0; + unsigned char ch, val = 0; + + for (i = 0; i < (len * 2); i++) { + /*convert to lower*/ + ch = ((str[i] >= 'A' && str[i] <= 'Z') ? str[i] + 32 : str[i]); + + if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f')) + return -1; + + if (ch >= '0' && ch <= '9') /*check is digit*/ + ch = ch - '0'; + else + ch = ch - 'a' + 10; + + val += ch; + + if (!(i%2)) + val <<= 4; + else { + byte[j] = val; + j++; + val = 0; + } + } + + return 0; +} + + +static void uccp420_roc_complete_work(struct work_struct *work) +{ + struct delayed_work *dwork = NULL; + struct mac80211_dev *dev = NULL; +#ifdef MULTI_CHAN_SUPPORT + struct umac_chanctx *off_chanctx = NULL; + struct umac_vif *uvif = NULL, *tmp = NULL; +#endif + struct tx_config *tx = NULL; + u32 roc_queue = 0; +#ifdef MULTI_CHAN_SUPPORT + bool need_offchan; + int roc_off_chanctx_idx = -1; + int chan_id = 0; +#endif + + dwork = container_of(work, struct delayed_work, work); + dev = container_of(dwork, struct mac80211_dev, roc_complete_work); + tx = &dev->tx; + + mutex_lock(&dev->mutex); +#ifdef MULTI_CHAN_SUPPORT + need_offchan = dev->roc_params.need_offchan; +#endif + + roc_queue = tx_queue_unmap(UMAC_ROC_AC); +#ifdef MULTI_CHAN_SUPPORT + roc_off_chanctx_idx = dev->roc_off_chanctx_idx; +#endif + + /* Stop the ROC queue */ + ieee80211_stop_queue(dev->hw, roc_queue); + /* Unlock RCU immediately as we are freeing off_chanctx in this funciton + * only and because flush_vif_queues sleep + */ + rcu_read_lock(); +#ifdef MULTI_CHAN_SUPPORT + off_chanctx = rcu_dereference(dev->off_chanctx[roc_off_chanctx_idx]); +#endif + rcu_read_unlock(); + +#ifdef MULTI_CHAN_SUPPORT + list_for_each_entry_safe(uvif, tmp, &off_chanctx->vifs, list) { + if (uvif == NULL || uvif->off_chanctx == NULL) + continue; + /* Flush the TX queues */ + uccp420_flush_vif_queues(dev, + uvif, + uvif->off_chanctx->index, + BIT(UMAC_ROC_AC), + UMAC_VIF_CHANCTX_TYPE_OFF); + + + spin_lock_bh(&tx->lock); + spin_lock(&dev->chanctx_lock); + + /* ROC DONE: Move the channel context */ + if (uvif->chanctx) + dev->curr_chanctx_idx = uvif->chanctx->index; + else + dev->curr_chanctx_idx = -1; + + spin_unlock(&dev->chanctx_lock); + spin_unlock_bh(&tx->lock); + + if (need_offchan) { + /* DEL from OFF chan list */ + list_del_init(&uvif->list); + if (uvif->chanctx) { + /* Add it back to OP chan list */ + list_add_tail(&uvif->list, + &uvif->chanctx->vifs); + + /* !need_offchan: In this case, the frames are + * transmitted, so trigger is not needed. + * + * need_offchan: In this case, frames are + * buffered so we need trigger in case no frames + * come from mac80211. + */ + /* Process OPER pending frames only. + * TXQ is flushed before start of ROC + */ + chan_id = uvif->chanctx->index; + uccp420wlan_tx_proc_send_pend_frms_all(dev, + chan_id); + } + off_chanctx->nvifs--; + } + uvif->off_chanctx = NULL; + } + + if (need_offchan) + kfree(off_chanctx); + + + rcu_assign_pointer(dev->off_chanctx[roc_off_chanctx_idx], NULL); + dev->roc_off_chanctx_idx = -1; +#endif + dev->roc_params.roc_in_progress = 0; + + if (dev->cancel_roc == 0) { + ieee80211_remain_on_channel_expired(dev->hw); + UCCP_DEBUG_ROC("%s:%d ROC STOPPED..\n", __func__, __LINE__); + } else { + dev->cancel_hw_roc_done = 1; + dev->cancel_roc = 0; + UCCP_DEBUG_ROC("%s:%d ROC CANCELLED..\n", __func__, __LINE__); + } + + /* Start the ROC queue */ + ieee80211_wake_queue(dev->hw, roc_queue); + mutex_unlock(&dev->mutex); +} + + +static void tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *txctl, + struct sk_buff *skb) +{ + struct mac80211_dev *dev = hw->priv; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct umac_vif *uvif; + unsigned char null_bssid[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + struct iphdr *iphdr; + unsigned char *pktgen_magic; + unsigned int orig_pktgen_magic = 0x55e99bbe; /*Endianness 0xbe9be955*/ + struct umac_event_noa noa_event; +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx = -1; +#endif + + if (tx_info->control.vif == NULL) { + UCCP_DEBUG_80211IF("%s: Dropping injected TX frame\n", + dev->name); + dev_kfree_skb_any(skb); + return; + } + + uvif = (struct umac_vif *)(tx_info->control.vif->drv_priv); + + if (wifi->params.production_test) { + if (((hdr->frame_control & + IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA) || + (tx_info->control.vif == NULL)) + goto tx_status; + + iphdr = (struct iphdr *) skb_network_header(skb); + if (iphdr->protocol == IPPROTO_UDP) { + pktgen_magic = skb_transport_header(skb); + pktgen_magic += sizeof(struct udphdr); + /*If not PKTGEN, then drop it*/ + if (memcmp(pktgen_magic, &orig_pktgen_magic, 4) != 0) { + UCCP_DEBUG_80211IF("%s:%d Prod_Mode: The pkt ", + __func__, __LINE__); + UCCP_DEBUG_80211IF("is NOT PKTGEN so "); + UCCP_DEBUG_80211IF("dropping it\n"); + goto tx_status; + } + } else { + UCCP_DEBUG_80211IF("%s:%d prod_mode: The pkt is NOT ", + __func__, __LINE__); + UCCP_DEBUG_80211IF("PKTGEN so dropping it\n"); + goto tx_status; + } + } + if (ether_addr_equal(hdr->addr3, null_bssid)) + goto tx_status; + + if (uvif->vif->type != NL80211_IFTYPE_AP) { + if ((dev->power_save == PWRSAVE_STATE_DOZE) && + (((hdr->frame_control & + IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA) || + is_bufferable_mgmt_frame(hdr))) + hdr->frame_control |= IEEE80211_FCTL_PM; + } + + if (uvif->noa_active) { + memset(&noa_event, 0, sizeof(noa_event)); + noa_event.if_index = uvif->vif_index; + uccp420wlan_noa_event(FROM_TX, &noa_event, dev, skb); + return; + } + +#ifdef MULTI_CHAN_SUPPORT + spin_lock_bh(&dev->chanctx_lock); + curr_chanctx_idx = dev->curr_chanctx_idx; + spin_unlock_bh(&dev->chanctx_lock); +#endif + + uccp420wlan_tx_frame(skb, + txctl->sta, + dev, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + false); + + return; + +tx_status: + tx_info->flags |= IEEE80211_TX_STAT_ACK; + tx_info->status.rates[0].count = 1; + ieee80211_tx_status(hw, skb); +} + +static int start(struct ieee80211_hw *hw) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + UCCP_DEBUG_80211IF("%s-80211IF: In start\n", dev->name); + + if ((wifi->params.fw_loading == 1) && load_fw(hw)) { + UCCP_DEBUG_80211IF("%s-80211IF: FW load failed\n", dev->name); + return -ENODEV; + } + + mutex_lock(&dev->mutex); + if ((uccp420wlan_core_init(dev, ftm)) < 0) { + UCCP_DEBUG_80211IF("%s-80211IF: umac init failed\n", dev->name); + mutex_unlock(&dev->mutex); + return -ENODEV; + } + + INIT_DELAYED_WORK(&dev->roc_complete_work, uccp420_roc_complete_work); + + dev->state = STARTED; + memset(dev->params->pdout_voltage, 0, + sizeof(char) * MAX_AUX_ADC_SAMPLES); +#ifdef MULTI_CHAN_SUPPORT + dev->roc_off_chanctx_idx = -1; +#endif + mutex_unlock(&dev->mutex); + + return 0; +} + +static void stop(struct ieee80211_hw *hw) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + UCCP_DEBUG_80211IF("%s-80211IF:In stop\n", dev->name); + mutex_lock(&dev->mutex); + uccp420wlan_core_deinit(dev, ftm); + dev->state = STOPPED; + mutex_unlock(&dev->mutex); + + hal_ops.reset_hal_params(); + +} + +static int add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mac80211_dev *dev = hw->priv; + struct ieee80211_vif *v; + struct umac_vif *uvif; + int vif_index, iftype; + + mutex_lock(&dev->mutex); + iftype = vif->type; + v = vif; + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + vif->driver_flags |= IEEE80211_VIF_SUPPORTS_UAPSD; + + if (dev->current_vif_count == wifi->params.num_vifs) { + pr_err("%s: Exceeded Maximum supported VIF's cur:%d max: %d.\n", + __func__, + dev->current_vif_count, + wifi->params.num_vifs); + + mutex_unlock(&dev->mutex); + return -ENOTSUPP; + } + + if (!(iftype == NL80211_IFTYPE_STATION || + iftype == NL80211_IFTYPE_ADHOC || + iftype == NL80211_IFTYPE_AP)) { + pr_err("Invalid Interface type\n"); + return -ENOTSUPP; + } + + if (wifi->params.production_test) { + if (dev->active_vifs || iftype != NL80211_IFTYPE_ADHOC) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + } + + for (vif_index = 0; vif_index < wifi->params.num_vifs; vif_index++) { + if (!(dev->active_vifs & (1 << vif_index))) + break; + } + + /* This should never happen, we have taken care of this above */ + if (vif_index == wifi->params.num_vifs) { + pr_err("%s: All VIF's are busy: %pM\n", __func__, vif->addr); + mutex_unlock(&dev->mutex); + return -EINVAL; + } + + uvif = (struct umac_vif *)&v->drv_priv; + uvif->vif_index = vif_index; + uvif->vif = v; + uvif->dev = dev; + uvif->seq_no = 0; + uccp420wlan_vif_add(uvif); + dev->active_vifs |= (1 << vif_index); + dev->current_vif_count++; + + if (iftype == NL80211_IFTYPE_ADHOC) + dev->tx_last_beacon = 0; + + rcu_assign_pointer(dev->vifs[vif_index], v); + synchronize_rcu(); + + mutex_unlock(&dev->mutex); + + return 0; +} + +static void remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mac80211_dev *dev = hw->priv; + struct ieee80211_vif *v; + int vif_index; + + mutex_lock(&dev->mutex); + v = vif; + vif_index = ((struct umac_vif *)&v->drv_priv)->vif_index; + + uccp420wlan_vif_remove((struct umac_vif *)&v->drv_priv); + dev->active_vifs &= ~(1 << vif_index); + rcu_assign_pointer(dev->vifs[vif_index], NULL); + synchronize_rcu(); + wifi->params.sync[vif_index].status = 0; + dev->current_vif_count--; + mutex_unlock(&dev->mutex); + +} + + +static int config(struct ieee80211_hw *hw, + unsigned int changed) +{ + struct mac80211_dev *dev = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + unsigned int pri_chnl_num; + unsigned int freq_band; + unsigned int ch_width; + unsigned int center_freq = 0; + unsigned int center_freq1 = 0; + unsigned int center_freq2 = 0; + int i = 0; + int err = 0; + struct ieee80211_vif *vif = NULL; + + UCCP_DEBUG_80211IF("%s-80211IF:In config\n", dev->name); + + mutex_lock(&dev->mutex); + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + dev->txpower = conf->power_level; + uccp420wlan_prog_txpower(dev->txpower); + } + + /* Check for change in channel */ + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + center_freq = conf->chandef.chan->center_freq; + center_freq1 = conf->chandef.center_freq1; + center_freq2 = conf->chandef.center_freq2; + freq_band = conf->chandef.chan->band; + ch_width = conf->chandef.width; + + pri_chnl_num = ieee80211_frequency_to_channel(center_freq); + UCCP_DEBUG_80211IF("%s-80211IF:Primary Channel is %d\n", + dev->name, + pri_chnl_num); + + err = uccp420wlan_prog_channel(pri_chnl_num, + center_freq1, center_freq2, + ch_width, +#ifdef MULTI_CHAN_SUPPORT + 0, +#endif + freq_band); + + if (err) { + mutex_unlock(&dev->mutex); + return err; + } + if (conf->radar_enabled) { + UCCP_DEBUG_80211IF("RADAR Detection ENABLED on "); + UCCP_DEBUG_80211IF("PriChannel=%d with ch_width=%d\n", + pri_chnl_num, + ch_width); + + uccp420wlan_prog_radar_detect(RADAR_DETECT_OP_START); + } + } + + /* Check for change in Power save state */ + for (i = 0; i < MAX_VIFS; i++) { + if (!(changed & IEEE80211_CONF_CHANGE_PS)) + break; + + if (!(dev->active_vifs & (1 << i))) + continue; + + /* When ROC is in progress, do not mess with + * PS state + */ + if (dev->roc_params.roc_in_progress) + continue; + + if (wifi->params.disable_power_save) + continue; + + if (conf->flags & IEEE80211_CONF_PS) + dev->power_save = PWRSAVE_STATE_DOZE; + else + dev->power_save = PWRSAVE_STATE_AWAKE; + + UCCP_DEBUG_80211IF("%s-80211IF:PS state of VIF", dev->name); + UCCP_DEBUG_80211IF(" %d changed to %d\n", i, dev->power_save); + + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[i]); + rcu_read_unlock(); + + uccp420wlan_prog_ps_state(i, + vif->addr, + dev->power_save); + } + + /* TODO: Make this global config as it effects all VIF's */ + for (i = 0; i < MAX_VIFS; i++) { + if (!(changed & IEEE80211_CONF_CHANGE_SMPS)) + break; + + if (wifi->params.production_test == 1) + break; + + if (!(dev->active_vifs & (1 << i))) + continue; + + UCCP_DEBUG_80211IF("%s-80211IF:MIMO PS state of VIF %d -> %d\n", + dev->name, + i, + conf->smps_mode); + + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[i]); + rcu_read_unlock(); + + uccp420wlan_prog_vif_smps(i, + vif->addr, + conf->smps_mode); + } + + /* Check for change in Retry Limits */ + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) { + UCCP_DEBUG_80211IF("%s-80211IF:Retry Limits changed", + dev->name); + UCCP_DEBUG_80211IF(" to %d and %d\n", + conf->short_frame_max_tx_count, + conf->long_frame_max_tx_count); + } + + for (i = 0; i < MAX_VIFS; i++) { + if (!(changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS)) + break; + + if (!(dev->active_vifs & (1 << i))) + continue; + + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[i]); + rcu_read_unlock(); + + uccp420wlan_prog_short_retry(i, + vif->addr, + conf->short_frame_max_tx_count); + uccp420wlan_prog_long_retry(i, + vif->addr, + conf->long_frame_max_tx_count); + } + + mutex_unlock(&dev->mutex); + return 0; +} + + +static u64 prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct mac80211_dev *dev = hw->priv; + int i; + struct netdev_hw_addr *ha; + int mc_count = 0; + + if (dev->state != STARTED) + return 0; + + mc_count = netdev_hw_addr_list_count(mc_list); + { + if (mc_count > MCST_ADDR_LIMIT) { + mc_count = 0; + pr_warn("%s-80211IF:Disabling MCAST filter (cnt=%d)\n", + dev->name, mc_count); + goto out; + } + } + UCCP_DEBUG_80211IF("%s-80211IF: Multicast filter count", dev->name); + UCCP_DEBUG_80211IF("adding: %d removing: %d\n", mc_count, + dev->mc_filter_count); + + if (dev->mc_filter_count > 0) { + /* Remove all previous multicast addresses from the LMAC */ + for (i = 0; i < dev->mc_filter_count; i++) + uccp420wlan_prog_mcast_addr_cfg(dev->mc_filters[i], + WLAN_MCAST_ADDR_REM); + } + + i = 0; + + netdev_hw_addr_list_for_each(ha, mc_list) { + /* Prog the multicast address into the LMAC */ + uccp420wlan_prog_mcast_addr_cfg(ha->addr, WLAN_MCAST_ADDR_ADD); + memcpy(dev->mc_filters[i], ha->addr, 6); + i++; + } + + dev->mc_filter_count = mc_count; +out: + return mc_count; +} + + +static void configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *new_flags, + u64 mc_count) +{ + struct mac80211_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + + changed_flags &= SUPPORTED_FILTERS; + *new_flags &= SUPPORTED_FILTERS; + + if (dev->state != STARTED) { + mutex_unlock(&dev->mutex); + return; + } + + if ((*new_flags & FIF_ALLMULTI) || (mc_count == 0)) { + /* Disable the multicast filter in LMAC */ + UCCP_DEBUG_80211IF("%s-80211IF: Multicast filters disabled\n", + dev->name); + uccp420wlan_prog_mcast_filter_control(MCAST_FILTER_DISABLE); + } else if (mc_count) { + /* Enable the multicast filter in LMAC */ + UCCP_DEBUG_80211IF("%s-80211IF: Multicast filters enabled\n", + dev->name); + uccp420wlan_prog_mcast_filter_control(MCAST_FILTER_ENABLE); + } + + if (changed_flags == 0) + /* No filters which we support changed */ + goto out; + + if (wifi->params.production_test == 0) { + if (*new_flags & FIF_BCN_PRBRESP_PROMISC) { + /* Receive all beacons and probe responses */ + UCCP_DEBUG_80211IF("%s-80211IF: RCV ALL bcns\n", + dev->name); + uccp420wlan_prog_rcv_bcn_mode(RCV_ALL_BCNS); + } else { + /* Receive only network beacons and probe responses */ + UCCP_DEBUG_80211IF("%s-80211IF: RCV NW bcns\n", + dev->name); + uccp420wlan_prog_rcv_bcn_mode(RCV_ALL_NETWORK_ONLY); + } + } +out: + if (wifi->params.production_test == 1) { + UCCP_DEBUG_80211IF("%s-80211IF: RCV ALL bcns\n", dev->name); + uccp420wlan_prog_rcv_bcn_mode(RCV_ALL_BCNS); + } + + mutex_unlock(&dev->mutex); +} + + +static int conf_vif_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned short queue, + const struct ieee80211_tx_queue_params *txq_params) +{ + struct mac80211_dev *dev = hw->priv; + int vif_index, vif_active; + struct edca_params params; + struct ieee80211_vif *vif_local = NULL; + + mutex_lock(&dev->mutex); + + for (vif_index = 0; vif_index < wifi->params.num_vifs; vif_index++) { + if (!(dev->active_vifs & (1 << vif_index))) + continue; + + rcu_read_lock(); + vif_local = rcu_dereference(dev->vifs[vif_index]); + rcu_read_unlock(); + + if (ether_addr_equal(vif_local->addr, + vif->addr)) + break; + } + + if (WARN_ON(vif_index == wifi->params.num_vifs)) { + mutex_unlock(&dev->mutex); + return -EINVAL; + } + + vif_active = 0; + + if ((dev->active_vifs & (1 << vif_index))) + vif_active = 1; + + memset(¶ms, 0, sizeof(params)); + params.aifs = txq_params->aifs; + params.txop = txq_params->txop; + params.cwmin = txq_params->cw_min; + params.cwmax = txq_params->cw_max; + params.uapsd = txq_params->uapsd; + + uccp420wlan_vif_set_edca_params(queue, + (struct umac_vif *)&vif->drv_priv, + ¶ms, + vif_active); + mutex_unlock(&dev->mutex); + return 0; +} + + +static int set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + + struct umac_key sec_key; + unsigned int result = 0; + struct mac80211_dev *dev = hw->priv; + unsigned int cipher_type, key_type; + int vif_index; + struct umac_vif *uvif; + + uvif = ((struct umac_vif *)&vif->drv_priv); + + memset(&sec_key, 0, sizeof(struct umac_key)); + + switch (key_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + sec_key.key = key_conf->key; + cipher_type = CIPHER_TYPE_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + sec_key.key = key_conf->key; + cipher_type = CIPHER_TYPE_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + key_conf->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + /* We get the key in the following form: + * KEY (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + */ + sec_key.key = key_conf->key; + sec_key.tx_mic = key_conf->key + 16; + sec_key.rx_mic = key_conf->key + 24; + cipher_type = CIPHER_TYPE_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + sec_key.key = key_conf->key; + cipher_type = CIPHER_TYPE_CCMP; + break; + default: + result = -EOPNOTSUPP; + mutex_unlock(&dev->mutex); + goto out; + } + + vif_index = ((struct umac_vif *)&vif->drv_priv)->vif_index; + + mutex_lock(&dev->mutex); + + if (cmd == SET_KEY) { + key_conf->hw_key_idx = 0; /* Don't really use this */ + + /* This flag indicate that it requires IV generation */ + key_conf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + + + if (cipher_type == CIPHER_TYPE_WEP40 || + cipher_type == CIPHER_TYPE_WEP104) { + UCCP_DEBUG_CRYPTO("%s-80211IF: ADD IF KEY (WEP).", + dev->name); + UCCP_DEBUG_CRYPTO(" vif_index = %d,", vif_index); + UCCP_DEBUG_CRYPTO(" keyidx = %d, cipher_type = %d\n", + key_conf->keyidx, cipher_type); + + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_ADD, + key_conf->keyidx, + cipher_type, + &sec_key); + } else if (sta) { + sec_key.peer_mac = sta->addr; + + if (key_conf->flags & IEEE80211_KEY_FLAG_PAIRWISE) + key_type = KEY_TYPE_UCAST; + else + key_type = KEY_TYPE_BCAST; + UCCP_DEBUG_CRYPTO("%s-80211IF: ADD PEER KEY (WPA/WPA2)", + dev->name); + UCCP_DEBUG_CRYPTO(" vif_index = %d,", vif_index); + UCCP_DEBUG_CRYPTO(" keyidx = %d, keytype = %d,", + key_conf->keyidx, key_type); + UCCP_DEBUG_CRYPTO(" cipher_type = %d\n", cipher_type); + + uccp420wlan_prog_peer_key(vif_index, + vif->addr, + KEY_CTRL_ADD, + key_conf->keyidx, + key_type, + cipher_type, + &sec_key); + } else { + key_type = KEY_TYPE_BCAST; + + if (vif->type == NL80211_IFTYPE_STATION) { + sec_key.peer_mac = + (unsigned char *)vif->bss_conf.bssid; + + memcpy(uvif->bssid, + (vif->bss_conf.bssid), + ETH_ALEN); + UCCP_DEBUG_CRYPTO("%s-80211IF: ADD PEER KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-STA). vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d, keytype = %d", + key_conf->keyidx, key_type); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_peer_key(vif_index, + vif->addr, + KEY_CTRL_ADD, + key_conf->keyidx, + key_type, cipher_type, + &sec_key); + + } else if (vif->type == NL80211_IFTYPE_AP) { + UCCP_DEBUG_CRYPTO("%s-80211IF: ADD IF KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-AP). vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d", + key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_ADD, + key_conf->keyidx, + cipher_type, + &sec_key); + } else { + /* ADHOC */ + /* TODO: Check this works for IBSS RSN */ + UCCP_DEBUG_CRYPTO("%s-80211IF: ADD IF KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-IBSS).vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d", + key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_ADD, + key_conf->keyidx, + cipher_type, + &sec_key); + } + } + } else if (cmd == DISABLE_KEY) { + if ((cipher_type == CIPHER_TYPE_WEP40) || + (cipher_type == CIPHER_TYPE_WEP104)) { + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_DEL, + key_conf->keyidx, + cipher_type, + &sec_key); + UCCP_DEBUG_CRYPTO("%s-80211IF: DEL IF KEY (WEP).", + dev->name); + UCCP_DEBUG_CRYPTO(" vif_index = %d, keyidx = %d", + vif_index, key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", cipher_type); + } else if (sta) { + sec_key.peer_mac = sta->addr; + + if (key_conf->flags & IEEE80211_KEY_FLAG_PAIRWISE) + key_type = KEY_TYPE_UCAST; + else + key_type = KEY_TYPE_BCAST; + UCCP_DEBUG_CRYPTO("%s-80211IF: DEL IF KEY (WPA/WPA2).", + dev->name); + UCCP_DEBUG_CRYPTO(" vif_index = %d, keyidx = %d", + vif_index, key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", cipher_type); + + uccp420wlan_prog_peer_key(vif_index, + vif->addr, + KEY_CTRL_DEL, + key_conf->keyidx, + key_type, + cipher_type, + &sec_key); + } else { + if (vif->type == NL80211_IFTYPE_STATION) { + sec_key.peer_mac = uvif->bssid; + UCCP_DEBUG_CRYPTO("%s-80211IF: DEL IF KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-STA). vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d", + key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_peer_key(vif_index, + vif->addr, + KEY_CTRL_DEL, + key_conf->keyidx, + KEY_TYPE_BCAST, + cipher_type, + &sec_key); + + } else if (vif->type == NL80211_IFTYPE_AP) { + UCCP_DEBUG_CRYPTO("%s-80211IF: DEL IF KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-AP). vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d", + key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_DEL, + key_conf->keyidx, + cipher_type, + &sec_key); + } else { + UCCP_DEBUG_CRYPTO("%s-80211IF: DEL IF KEY ", + dev->name); + UCCP_DEBUG_CRYPTO("(BCAST-IBSS).vif_index = %d", + vif_index); + UCCP_DEBUG_CRYPTO(", keyidx = %d", + key_conf->keyidx); + UCCP_DEBUG_CRYPTO(", cipher_type = %d\n", + cipher_type); + + uccp420wlan_prog_if_key(vif_index, + vif->addr, + KEY_CTRL_DEL, + key_conf->keyidx, + cipher_type, + &sec_key); + } + } + } + + mutex_unlock(&dev->mutex); + +out: + return result; +} + + +static void bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + unsigned int changed) +{ + struct mac80211_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + + if (wifi->params.production_test || wifi->params.disable_beacon_ibss) { + /* Disable beacon generation when running pktgen + * for performance + */ + changed &= ~BSS_CHANGED_BEACON_INT; + changed &= ~BSS_CHANGED_BEACON_ENABLED; + } + + uccp420wlan_vif_bss_info_changed((struct umac_vif *)&vif->drv_priv, + bss_conf, + changed); + mutex_unlock(&dev->mutex); +} + + +static void setup_ht_cap(struct ieee80211_sta_ht_cap *ht_info) +{ + int i; + + memset(ht_info, 0, sizeof(*ht_info)); + ht_info->ht_supported = true; + pr_info("SETUP HT CALLED\n"); +#if 0 + ht_info->cap |= IEEE80211_HT_CAP_DSSSCCK40; +#endif + ht_info->cap = 0; + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + ht_info->cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + ht_info->cap |= IEEE80211_HT_CAP_LSIG_TXOP_PROT; + /*We support SMPS*/ + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + if (wifi->params.max_tx_streams != wifi->params.max_rx_streams) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((wifi->params.max_tx_streams - 1) + << IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } + + for (i = 0; i < wifi->params.max_rx_streams; i++) + ht_info->mcs.rx_mask[i] = 0xff; + ht_info->mcs.rx_mask[4] = 0x1; + + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED; +} + + +#define IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT 13 +#define IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT 16 +static void setup_vht_cap(struct ieee80211_sta_vht_cap *vht_info) +{ + if (!vht_support) + return; + + memset(vht_info, 0, sizeof(*vht_info)); + vht_info->vht_supported = true; + pr_info("SETUP VHT CALLED\n"); + + vht_info->cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 | + /*64KB Rx buffer size*/ + (3 << + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT) | +#if 0 + IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | + IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + (1 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT) | + (1 << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT) | +#endif + IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXLDPC | + IEEE80211_VHT_CAP_TXSTBC | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_HTC_VHT; + /* 1x1 */ + if ((wifi->params.max_tx_streams == 1) && + (wifi->params.max_rx_streams == 1)) { + vht_info->vht_mcs.rx_mcs_map = + ((IEEE80211_VHT_MCS_SUPPORT_0_7) << (2*0)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*1)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*2)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*3)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*4)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*5)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*6)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*7)); + } + + /*2x2 */ + if ((wifi->params.max_tx_streams == 2) && + (wifi->params.max_rx_streams == 2)) { + vht_info->vht_mcs.rx_mcs_map = + ((IEEE80211_VHT_MCS_SUPPORT_0_7) << (2*0)) | + ((IEEE80211_VHT_MCS_SUPPORT_0_7) << (2*1)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*2)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*3)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*4)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*5)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*6)) | + ((IEEE80211_VHT_MCS_NOT_SUPPORTED) << (2*7)); + } + + vht_info->vht_mcs.tx_mcs_map = vht_info->vht_mcs.rx_mcs_map; +} + + +static void init_hw(struct ieee80211_hw *hw) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + int num_if_comb = 0; + + /* Supported Interface Types and other Default values*/ + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + + hw->wiphy->iface_combinations = if_comb; + + num_if_comb = (sizeof(if_comb) / + sizeof(struct ieee80211_iface_combination)); + hw->wiphy->n_iface_combinations = num_if_comb; + + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, MFP_CAPABLE); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + + if (wifi->params.dot11a_support) + ieee80211_hw_set(hw, SPECTRUM_MGMT); + + ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK); + ieee80211_hw_set(hw, CONNECTION_MONITOR); + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + + hw->wiphy->max_scan_ssids = MAX_NUM_SSIDS; /* 4 */ + /* Low priority bg scan */ + hw->wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + hw->max_listen_interval = 10; + hw->wiphy->max_remain_on_channel_duration = 5000; /*ROC*/ + hw->offchannel_tx_hw_queue = WLAN_AC_VO; + hw->max_rates = 4; + hw->max_rate_tries = 5; + hw->queues = 4; + + /* Size */ + hw->extra_tx_headroom = 0; + hw->vif_data_size = sizeof(struct umac_vif); + hw->sta_data_size = sizeof(struct umac_sta); +#ifdef MULTI_CHAN_SUPPORT + hw->chanctx_data_size = sizeof(struct umac_chanctx); +#endif + + if (wifi->params.dot11g_support) { + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &band_2ghz; + setup_ht_cap(&hw->wiphy->bands[IEEE80211_BAND_2GHZ]->ht_cap); + } + + if (wifi->params.dot11a_support) { + if (vht_support) + setup_vht_cap(&band_5ghz.vht_cap); + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &band_5ghz; + setup_ht_cap(&hw->wiphy->bands[IEEE80211_BAND_5GHZ]->ht_cap); + } + + memset(hw->wiphy->addr_mask, 0, sizeof(hw->wiphy->addr_mask)); + + if (wifi->params.num_vifs == 1) { + hw->wiphy->addresses = NULL; + SET_IEEE80211_PERM_ADDR(hw, dev->if_mac_addresses[0].addr); + } else { + hw->wiphy->n_addresses = wifi->params.num_vifs; + hw->wiphy->addresses = dev->if_mac_addresses; + } + + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; +#ifdef notyet + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; +#endif + hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; + + if (!wifi->params.disable_power_save && + !wifi->params.disable_sm_power_save) { + /* SMPS Support both Static and Dynamic */ + hw->wiphy->features |= NL80211_FEATURE_STATIC_SMPS; + hw->wiphy->features |= NL80211_FEATURE_DYNAMIC_SMPS; + } + +#ifdef CONFIG_PM + hw->wiphy->wowlan = &uccp_wowlan_support; +#endif +} + + +static int ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size, bool amsdu) +{ + int ret = 0; + unsigned int val = 0; + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + UCCP_DEBUG_80211IF("%s-80211IF: ampdu action started\n", + ((struct mac80211_dev *)(hw->priv))->name); + /* TODO */ + switch (action) { + case IEEE80211_AMPDU_RX_START: + { + val = tid | TID_INITIATOR_AP; + dev->tid_info[val].tid_state = TID_STATE_AGGR_START; + dev->tid_info[val].ssn = *ssn; + uccp420wlan_prog_ba_session_data(1, + tid, + &dev->tid_info[val].ssn, + 1, + vif->addr, + (unsigned char *)(vif->bss_conf.bssid)); + } + break; + case IEEE80211_AMPDU_RX_STOP: + { + val = tid | TID_INITIATOR_AP; + dev->tid_info[val].tid_state = TID_STATE_AGGR_STOP; + uccp420wlan_prog_ba_session_data(0, + tid, + &dev->tid_info[val].ssn, + 1, + vif->addr, + (unsigned char *)(vif->bss_conf.bssid)); + } + break; + case IEEE80211_AMPDU_TX_START: + { + val = tid | TID_INITIATOR_STA; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + dev->tid_info[val].tid_state = TID_STATE_AGGR_START; + dev->tid_info[val].ssn = *ssn; + } + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + case IEEE80211_AMPDU_TX_STOP_CONT: + { + val = tid | TID_INITIATOR_STA; + dev->tid_info[val].tid_state = TID_STATE_AGGR_STOP; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + } + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + { + val = tid | TID_INITIATOR_STA; + dev->tid_info[val].tid_state = TID_STATE_AGGR_OPERATIONAL; + } + break; + default: + pr_err("%s: Invalid command, ignoring\n", + __func__); + } + return ret; +} + + +static int set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + /* Maximum no of antenna supported =2 */ + if (!tx_ant || (tx_ant & ~3) || !rx_ant || (rx_ant & ~3)) + return -EINVAL; + + dev->tx_antenna = (tx_ant & 3); + + return 0; +} + + +static int remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *channel, + int duration, + enum ieee80211_roc_type type) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + unsigned int pri_chnl_num = + ieee80211_frequency_to_channel(channel->center_freq); +#ifdef MULTI_CHAN_SUPPORT + struct umac_vif *uvif = (struct umac_vif *)vif->drv_priv; + struct umac_chanctx *off_chanctx = NULL; + int off_chanctx_id = 0, i = 0; + struct tx_config *tx = &dev->tx; + u32 hw_queue_map = 0; + struct ieee80211_chanctx_conf *vif_chanctx; + bool need_offchan = true; +#endif + + mutex_lock(&dev->mutex); + UCCP_DEBUG_ROC("%s:%d The Params are:", + __func__, + __LINE__); + UCCP_DEBUG_ROC(" channel:%d duration:%d type: %d\n", + ieee80211_frequency_to_channel(channel->center_freq), + duration, + type); + + if (dev->roc_params.roc_in_progress) { + UCCP_DEBUG_ROC("%s:%d Dropping roc...Busy\n", + __func__, + __LINE__); + mutex_unlock(&dev->mutex); + return -EBUSY; + } + +#ifdef MULTI_CHAN_SUPPORT + if (dev->num_active_chanctx == 2) { + UCCP_DEBUG_ROC("%s:%d RoC is not supported in TSMC Mode\n", + __func__, + __LINE__); + mutex_unlock(&dev->mutex); + return -ENOTSUPP; + } +#endif + + /* Inform FW that ROC is started: + * For pure TX we send OFFCHANNEL_TX so that driver can terminate ROC + * For Tx + Rx we use NORMAL, FW will terminate ROC based on duration. + */ + if (duration != 10 && type == ROC_TYPE_OFFCHANNEL_TX) + type = ROC_TYPE_NORMAL; + +#ifdef MULTI_CHAN_SUPPORT + /* uvif is in connected state + */ + if (uvif->chanctx) { + rcu_read_lock(); + + vif_chanctx = + rcu_dereference(dev->chanctx[uvif->chanctx->index]); + + /* AS ROC frames are MGMT frames, checking only for Primary + * Channel. + */ + if (vif_chanctx->def.chan->center_freq == channel->center_freq) + need_offchan = false; + + rcu_read_unlock(); + } + + UCCP_DEBUG_ROC("%s:%d need_offchan: %d\n", + __func__, + __LINE__, + need_offchan); + dev->roc_params.need_offchan = need_offchan; + + if (need_offchan) { + /* Different chan context than the uvif */ + off_chanctx = kmalloc(sizeof(struct umac_chanctx), + GFP_KERNEL); + + if (!off_chanctx) { + pr_err("%s: Unable to alloc mem for channel context\n", + __func__); + mutex_unlock(&dev->mutex); + return -ENOMEM; + } + + /** Currently OFFCHAN is limited to handling ROC case + * but it is meant for a generic case. + * ideally we should look for existing offchan context + * and re-use/create. + */ + for (i = 0; i < MAX_OFF_CHANCTX; i++) { + if (!dev->off_chanctx[i]) { + off_chanctx_id = i; + break; + } + } + + if (uvif->chanctx) { + ieee80211_stop_queues(hw); + + hw_queue_map = BIT(WLAN_AC_BK) | + BIT(WLAN_AC_BE) | + BIT(WLAN_AC_VI) | + BIT(WLAN_AC_VO) | + BIT(WLAN_AC_BCN); + + uccp420_flush_vif_queues(dev, + uvif, + uvif->chanctx->index, + hw_queue_map, + UMAC_VIF_CHANCTX_TYPE_OPER); + } + + + off_chanctx->index = OFF_CHANCTX_IDX_BASE + off_chanctx_id; + dev->roc_off_chanctx_idx = off_chanctx_id; + INIT_LIST_HEAD(&off_chanctx->vifs); + off_chanctx->nvifs = 0; + + if (uvif->chanctx) { + /* Delete the uvif from OP channel list */ + list_del_init(&uvif->list); + } + /* Add the vif to the off_chanctx */ + list_add_tail(&uvif->list, &off_chanctx->vifs); + off_chanctx->nvifs++; + rcu_assign_pointer(dev->off_chanctx[off_chanctx_id], + off_chanctx); + synchronize_rcu(); + + + /* Move the channel context */ + spin_lock_bh(&dev->chanctx_lock); + dev->curr_chanctx_idx = off_chanctx->index; + spin_unlock_bh(&dev->chanctx_lock); + } else { + /* Same channel context, just update off_chanctx + * to chanctx + */ + off_chanctx = uvif->chanctx; + + for (i = 0; i < MAX_OFF_CHANCTX; i++) { + if (!dev->off_chanctx[i]) { + off_chanctx_id = i; + break; + } + } + dev->roc_off_chanctx_idx = off_chanctx->index; + rcu_assign_pointer(dev->off_chanctx[off_chanctx_id], + off_chanctx); + synchronize_rcu(); + } + spin_lock_bh(&tx->lock); + uvif->off_chanctx = off_chanctx; + spin_unlock_bh(&tx->lock); +#endif + + uccp420wlan_prog_roc(ROC_START, pri_chnl_num, duration, type); + +#ifdef MULTI_CHAN_SUPPORT + if (uvif->chanctx) + ieee80211_wake_queues(hw); +#endif + + mutex_unlock(&dev->mutex); + + return 0; +} + + +static int cancel_remain_on_channel(struct ieee80211_hw *hw) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + int ret = 0; + + mutex_lock(&dev->mutex); + + if (dev->roc_params.roc_in_progress) { + dev->cancel_hw_roc_done = 0; + dev->cancel_roc = 1; + UCCP_DEBUG_ROC("%s:%d Cancelling HW ROC....\n", + __func__, __LINE__); + uccp420wlan_prog_roc(ROC_STOP, 0, 0, 0); + + mutex_unlock(&dev->mutex); + + if (!wait_for_cancel_hw_roc(dev)) { + UCCP_DEBUG_ROC("%s:%d Cancel HW ROC....done\n", + __func__, + __LINE__); + ret = 0; + } else { + UCCP_DEBUG_ROC("%s:%d Cancel HW ROC..timedout\n", + __func__, + __LINE__); + ret = -1; + } + } else { + mutex_unlock(&dev->mutex); + } + + return ret; +} + + +/* Needed in case of IBSS to send out probe responses when we are beaconing */ +static int tx_last_beacon(struct ieee80211_hw *hw) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + return dev->tx_last_beacon; +} + + +#ifdef CONFIG_PM +static int wait_for_econ_ps_cfg(struct mac80211_dev *dev) +{ + int count = 0; + char econ_ps_cfg_done = 0; + +check_econ_ps_cfg_complete: + mutex_lock(&dev->mutex); + econ_ps_cfg_done = dev->econ_ps_cfg_stats.completed; + mutex_unlock(&dev->mutex); + + if (!econ_ps_cfg_done && (count < PS_ECON_CFG_TIMEOUT_TICKS)) { + count++; + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + goto check_econ_ps_cfg_complete; + } + + if (!econ_ps_cfg_done) { + pr_warn("%s: Didn't get ECON_PS_CFG_DONE event\n", + __func__); + return -1; + } + + UCCP_DEBUG_80211IF("%s : Received ECON_PS_CFG_DONE event\n", + __func__); + return 0; +} + +static int img_resume(struct ieee80211_hw *hw) +{ + int i = 0, ret = 0; + int active_vif_index = -1; + struct mac80211_dev *dev = NULL; + + if (hw == NULL) { + pr_err("%s: Invalid parameters\n", + __func__); + return -EINVAL; + } + + dev = (struct mac80211_dev *)hw->priv; + + mutex_lock(&dev->mutex); + + for (i = 0; i < MAX_VIFS; i++) { + if (dev->active_vifs & (1 << i)) + active_vif_index = i; + } + + dev->econ_ps_cfg_stats.completed = 0; + dev->econ_ps_cfg_stats.result = 0; + + ret = uccp420wlan_prog_econ_ps_state(active_vif_index, + PWRSAVE_STATE_AWAKE); + if (ret) { + pr_err("%s : prog econ ps failed\n", + __func__); + mutex_unlock(&dev->mutex); + return ret; + } + + mutex_unlock(&dev->mutex); + + if (!wait_for_econ_ps_cfg(dev)) { + if (!dev->econ_ps_cfg_stats.result) { + UCCP_DEBUG_80211IF("%s: Successful\n", + __func__); + hal_ops.disable_irq_wake(); + img_suspend_status = 0; + return 0; + } + pr_warn("%s: Unable to Resume\n", __func__); + } + + return -ETIME; +} + + +static int img_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) +{ + int i = 0, ret = 0; + int active_vif_index = -1; + int count = 0; + struct mac80211_dev *dev = NULL; + struct ieee80211_vif *vif = NULL; + + if (hw == NULL) { + pr_err("%s: Invalid parameters\n", + __func__); + return -EINVAL; + } + + if (WARN_ON((wifi->params.hw_scan_status == HW_SCAN_STATUS_PROGRESS))) + return -EBUSY; + + dev = (struct mac80211_dev *)hw->priv; + + mutex_lock(&dev->mutex); + + for (i = 0; i < MAX_VIFS; i++) { + if (dev->active_vifs & (1 << i)) { + active_vif_index = i; + count++; + } + } + + if (count != 1) { + pr_err("%s: Economy mode supp only for single VIF(STA mode)\n", + __func__); + mutex_unlock(&dev->mutex); + return -ENOTSUPP; + } + + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[active_vif_index]); + rcu_read_unlock(); + + if (vif->type != NL80211_IFTYPE_STATION) { + pr_err("%s: VIF is not in STA Mode\n", + __func__); + mutex_unlock(&dev->mutex); + return -ENOTSUPP; + } + + dev->econ_ps_cfg_stats.completed = 0; + dev->econ_ps_cfg_stats.result = 0; + dev->econ_ps_cfg_stats.wake_trig = -1; + + ret = uccp420wlan_prog_econ_ps_state(active_vif_index, + PWRSAVE_STATE_DOZE); + if (ret) { + pr_err("%s : Error Occured\n", + __func__); + mutex_unlock(&dev->mutex); + + return ret; + } + + mutex_unlock(&dev->mutex); + + if (!wait_for_econ_ps_cfg(dev)) { + if (!dev->econ_ps_cfg_stats.result) { + UCCP_DEBUG_80211IF("%s: Successful\n", + __func__); + hal_ops.enable_irq_wake(); + img_suspend_status = 1; + return 0; + } + pr_info("%s: Unable to Suspend: Active Traffic.\n", __func__); + } + + return -ETIME; +} +#endif + + +int scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *ireq) +{ + struct umac_vif *uvif = (struct umac_vif *)vif->drv_priv; + struct scan_req scan_req = {0}; + int i = 0; + + struct cfg80211_scan_request *req; + + req = &ireq->req; + + scan_req.n_ssids = req->n_ssids; + scan_req.n_channels = req->n_channels; + scan_req.ie_len = req->ie_len; + + if (wifi->params.hw_scan_status != HW_SCAN_STATUS_NONE) + return -EBUSY; /* Already in HW SCAN State */ + + /* Keep track of HW Scan requests and compeltes */ + wifi->params.hw_scan_status = HW_SCAN_STATUS_PROGRESS; + + if (uvif->dev->params->production_test == 1) { + /* Drop scan, its just intended for IBSS + * and some data traffic + */ + if (wifi->params.hw_scan_status != HW_SCAN_STATUS_NONE) { + ieee80211_scan_completed(uvif->dev->hw, false); + wifi->params.hw_scan_status = HW_SCAN_STATUS_NONE; + } + + return 0; + } + + if (req->ie_len) + memcpy(scan_req.ie, req->ie, req->ie_len); + + for (i = 0; i < req->n_channels; i++) { + scan_req.center_freq[i] = req->channels[i]->center_freq; + scan_req.freq_max_power[i] = req->channels[i]->max_power; + scan_req.chan_flags[i] = req->channels[i]->flags; + /* The type of scan comes from mac80211 so its taken care of */ + } + + scan_req.p2p_probe = req->no_cck; + + /* For hostapd scan (40MHz) and scan_type=passive, n_ssids=0 + * and req->ssids is NULL + */ + if (req->n_ssids > 0) { + for (i = 0; i < req->n_ssids; i++) { + scan_req.ssids[i].ssid_len = req->ssids[i].ssid_len; + if (req->ssids[i].ssid_len > 0) + memcpy(scan_req.ssids[i].ssid, + req->ssids[i].ssid, + req->ssids[i].ssid_len); + } + } + + return uccp420wlan_scan(uvif->vif_index, &scan_req); +} + + +void uccp420wlan_scan_complete(void *context, + struct host_event_scanres *scan_res, + unsigned char *skb, + unsigned int len) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + /* DO NOT update the scan results through cfg80211 API's we just pass + * the beacons and probe responses up and mac80211 will inform cfg80211 + */ + if (scan_res->more_results == 0) { + UCCP_DEBUG_SCAN("Event Scan Complete from UCCP:"); + UCCP_DEBUG_SCAN(" More_results: 0, Scan is Completed\n"); + /* There can be a race where we receive remove_interface and + * abort the scan(1) + * But we get scan_complete from the FW(2), this check will make + * sure we are not calling scan_complete when we have already + * aborted the scan. Eg: Killing wpa_supplicant in middle of + * scanning + */ + if (wifi->params.hw_scan_status != HW_SCAN_STATUS_NONE) { + dev->stats->umac_scan_complete++; + ieee80211_scan_completed(dev->hw, false); + + /* Keep track of HW Scan requests and compeltes */ + wifi->params.hw_scan_status = HW_SCAN_STATUS_NONE; + } + } else { + UCCP_DEBUG_SCAN("Event Scan Complete from UCCP:\n"); + UCCP_DEBUG_SCAN("More_results: %d, Still Scanning\n", + scan_res->more_results); + + } +} + + +void cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct umac_vif *uvif = (struct umac_vif *)vif->drv_priv; + struct mac80211_dev *dev = NULL; + + dev = (struct mac80211_dev *)hw->priv; + + if (wifi->params.hw_scan_status == HW_SCAN_STATUS_PROGRESS) { + pr_info("Aborting pending scan request...\n"); + + dev->scan_abort_done = 0; + + if (uccp420wlan_scan_abort(uvif->vif_index)) + return; + + if (!wait_for_scan_abort(dev)) { + ieee80211_scan_completed(hw, true); + wifi->params.hw_scan_status = HW_SCAN_STATUS_NONE; + dev->stats->umac_scan_complete++; + return; + } + } +} + + +int set_rts_threshold(struct ieee80211_hw *hw, + u32 value) +{ + struct mac80211_dev *dev = NULL; + + dev = (struct mac80211_dev *)hw->priv; + /*if thres>=2347 (default case) hostapd sends down (u32) -1*/ + if (value > 65536) + dev->rts_threshold = 65536; + else + dev->rts_threshold = value; + return 0; + +} + + +int sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct umac_vif *uvif = (struct umac_vif *)vif->drv_priv; + struct peer_sta_info peer_st_info = {0}; + int i; + int result = 0; + struct mac80211_dev *dev = hw->priv; + struct umac_sta *usta = (struct umac_sta *)sta->drv_priv; + unsigned int peer_id = 0; + + for (i = 0; i < MAX_PEERS; i++) { + if (!dev->peers[i]) { + peer_id = i; + break; + } + } + + if (i == MAX_PEERS) { + pr_err("Exceeded Max STA limit(%d)\n", MAX_PEERS); + return -1; + } + + + for (i = 0; i < STA_NUM_BANDS; i++) + peer_st_info.supp_rates[i] = sta->supp_rates[i]; + + /* HT info */ + peer_st_info.ht_cap = sta->ht_cap.cap; + peer_st_info.ht_supported = sta->ht_cap.ht_supported; + peer_st_info.vht_supported = sta->vht_cap.vht_supported; + peer_st_info.vht_cap = sta->vht_cap.cap; + peer_st_info.ampdu_factor = sta->ht_cap.ampdu_factor; + peer_st_info.ampdu_density = sta->ht_cap.ampdu_density; + peer_st_info.rx_highest = sta->ht_cap.mcs.rx_highest; + peer_st_info.tx_params = sta->ht_cap.mcs.tx_params; + peer_st_info.uapsd_queues = sta->uapsd_queues; + + /* Will be used in enforcing rules during Aggregation */ + uvif->peer_ampdu_factor = (1 << (13 + sta->ht_cap.ampdu_factor)) - 1; + + if (sta->vht_cap.vht_supported) { + if (sta->vht_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE) + uvif->dev->params->vht_beamform_support = 1; + + } + + for (i = 0; i < HT_MCS_MASK_LEN; i++) + peer_st_info.rx_mask[i] = sta->ht_cap.mcs.rx_mask[i]; + + for (i = 0; i < ETH_ALEN; i++) + peer_st_info.addr[i] = sta->addr[i]; + + result = uccp420wlan_sta_add(uvif->vif_index, &peer_st_info); + + if (!result) { + rcu_assign_pointer(dev->peers[peer_id], sta); + synchronize_rcu(); + + usta->index = peer_id; +#ifdef MULTI_CHAN_SUPPORT + usta->chanctx = uvif->chanctx; + usta->vif_index = uvif->vif_index; +#endif + } + + return result; +} + + +int sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct umac_vif *uvif = (struct umac_vif *)vif->drv_priv; + struct peer_sta_info peer_st_info = {0}; + int i; + int result = 0; + struct mac80211_dev *dev = hw->priv; + struct umac_sta *usta = (struct umac_sta *)sta->drv_priv; + + for (i = 0; i < ETH_ALEN; i++) + peer_st_info.addr[i] = sta->addr[i]; + + result = uccp420wlan_sta_remove(uvif->vif_index, &peer_st_info); + + if (!result) { + rcu_assign_pointer(dev->peers[usta->index], NULL); + synchronize_rcu(); + + usta->index = -1; + } + + return result; +} + + +static int load_fw(struct ieee80211_hw *hw) +{ + int err = 0; + int i = 0; + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + const struct firmware *fw = NULL; + const char *bin_name[FWLDR_NUM_BINS] = {FWLDR_HW_BIN, + FWLDR_FW_BIN}; + + do { + err = request_firmware(&fw, bin_name[i], dev->dev); + + if (err) { + pr_err("Failed to get %s, Error = %d\n", + bin_name[i], + err); + break; + } + + err = fwldr_load_fw(fw->data, i); + + if (err == FWLDR_SUCCESS) + pr_info("%s is loaded\n", bin_name[i]); + else + pr_err("Loading of %s failed\n", bin_name[i]); + + release_firmware(fw); + + i++; + + } while ((i < FWLDR_NUM_BINS) && (!err)); + + return err; +} + +static void channel_switch_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_chan_def *chandef) +{ + pr_err("RECEIVED CHANNEL SWITCH BEACON\n"); +} + +#ifdef DFS_TEST +static void radar_detected(void) +{ + ieee80211_radar_detected(wifi->hw); +} +#endif + +#ifdef CONFIG_NL80211_TESTMODE +const struct nla_policy rpu_testmode_policy[RPU_TM_ATTR_MAX + 1] = { + [RPU_TM_ATTR_CMD] = { .type = NLA_U32 }, + [RPU_TM_ATTR_DUMP] = { .type = NLA_UNSPEC }, +}; + +#define CB_ARG_OFFSET_ID 3 +#define CB_ARG_OFFSET_DUMP_START 4 +#define CB_ARG_OFFSET_DUMP_LEN 5 +/*Assuming minium dump of MAX_NL_DUMP_LEN*/ +/* Control Buffer is used as below + * cb[3] ==> To identify First Command + * cb[4] ==> Storing DUMP Start + * cb[5] ==> Dump Len + */ + +static int rpu_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb, + struct netlink_callback *cb, void *data, int len) +{ + + int idx = 0; + int err; + int cmd = 0; + long dump_start = 0; + char *curr_dump; + unsigned long dump_len = cb->args[CB_ARG_OFFSET_DUMP_LEN], + curr_msg_len = MAX_NL_DUMP_LEN; + unsigned long no_of_msgs = dump_len/MAX_NL_DUMP_LEN; + struct nlattr *tb[RPU_TM_ATTR_MAX + 1]; + struct mac80211_dev *dev = (struct mac80211_dev *)hw->priv; + + mutex_lock(&dev->mutex); + idx = cb->args[CB_ARG_OFFSET_ID]; + if (cb->args[CB_ARG_OFFSET_DUMP_START]) + dump_start = cb->args[CB_ARG_OFFSET_DUMP_START]; + + /*MAX Message: Dump Over*/ + if (idx > no_of_msgs) { + if (dump_start) { + kfree((void *)dump_start); + cb->args[CB_ARG_OFFSET_DUMP_START] = 0; + dump_start = 0; + } + goto dump_fail; + } + + /*Get Dump only once per command*/ + if (!idx) { + err = nla_parse(tb, RPU_TM_ATTR_MAX, data, + len, rpu_testmode_policy); + if (err) + goto dump_fail; + if (!tb[RPU_TM_ATTR_CMD]) { + pr_err("%s: CMD Attribute not found\n", __func__); + goto dump_fail; + } + + cmd = nla_get_u32(tb[RPU_TM_ATTR_CMD]); + + switch (cmd) { + case RPU_TM_CMD_GRAM: + if (hal_ops.get_dump_gram(&dump_start)) + goto dump_fail; + break; + case RPU_TM_CMD_COREA: + if (hal_ops.get_dump_core(&dump_start, 0)) + goto dump_fail; + break; + case RPU_TM_CMD_COREB: + if (hal_ops.get_dump_core(&dump_start, 1)) + goto dump_fail; + break; + case RPU_TM_CMD_PERIP: + if (hal_ops.get_dump_perip(&dump_start)) + goto dump_fail; + break; + case RPU_TM_CMD_SYSBUS: + if (hal_ops.get_dump_sysbus(&dump_start)) + goto dump_fail; + break; + default: + pr_err("%s: no match\n", __func__); + } + + dump_len = hal_ops.get_dump_len(cmd); + cb->args[CB_ARG_OFFSET_DUMP_START] = dump_start; + cb->args[CB_ARG_OFFSET_DUMP_LEN] = dump_len; + no_of_msgs = dump_len/MAX_NL_DUMP_LEN; + } + + /*Last Message of the Dump*/ + if (idx == no_of_msgs) + curr_msg_len = (dump_len % MAX_NL_DUMP_LEN); + + curr_dump = ((char *)dump_start) + (MAX_NL_DUMP_LEN * idx); + + if (!curr_dump || (curr_dump < (char *)dump_start) || + (curr_dump > ((char *)dump_start + dump_len))) + goto dump_fail; + + if (curr_msg_len > skb_tailroom(skb)) + goto dump_fail; + + err = nla_put(skb, RPU_TM_ATTR_DUMP, curr_msg_len, curr_dump); + if (err) + goto dump_fail; + cb->args[CB_ARG_OFFSET_ID] = ++idx; + + mutex_unlock(&dev->mutex); + return 0; + +dump_fail: + mutex_unlock(&dev->mutex); + return -ENOBUFS; +} +#endif + +#ifdef MULTI_CHAN_SUPPORT +static int umac_chanctx_set_channel(struct mac80211_dev *dev, + struct umac_vif *uvif, + struct cfg80211_chan_def *chandef) +{ + unsigned int freq_band = 0; + unsigned int ch_width = 0; + int center_freq1 = 0; + int center_freq2 = 0; + unsigned int pri_chan; + int err = 0; + + pri_chan = ieee80211_frequency_to_channel(chandef->chan->center_freq); + center_freq1 = chandef->center_freq1; + center_freq2 = chandef->center_freq2; + + freq_band = chandef->chan->band; + ch_width = chandef->width; + DEBUG_LOG("%s: Primary Channel is: %d\n", __func__, pri_chan); + err = uccp420wlan_prog_channel(pri_chan, center_freq1, + center_freq2, + ch_width, + uvif->vif_index, + freq_band); + + if (!err) { + /* RPU expects to program the associated channel + * every time it changes, else it leads to + * disconnections. + */ + uccp420wlan_prog_vif_op_channel(uvif->vif_index, + uvif->vif->addr, + pri_chan); + } + + return err; +} + + +static int add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct mac80211_dev *dev = NULL; + struct umac_chanctx *ctx = NULL; + int chanctx_id = 0; + int i = 0; + + dev = hw->priv; + + UCCP_DEBUG_TSMC("GOT add chanctx\n"); + + for (i = 0; i < MAX_CHANCTX; i++) { + if (!dev->chanctx[i]) { + chanctx_id = i; + break; + } + } + + if (i == MAX_CHANCTX) { + pr_err("Exceeded Max chan contexts limit(%d)\n", MAX_CHANCTX); + return -1; + } + + UCCP_DEBUG_TSMC("%s: %d MHz\n", + __func__, + conf->def.chan->center_freq); + + mutex_lock(&dev->mutex); + + ctx = (struct umac_chanctx *)conf->drv_priv; + ctx->index = chanctx_id; + INIT_LIST_HEAD(&ctx->vifs); + ctx->nvifs = 0; + + rcu_assign_pointer(dev->chanctx[i], conf); + synchronize_rcu(); + + mutex_unlock(&dev->mutex); + return 0; +} + + +static void remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct mac80211_dev *dev = NULL; + struct umac_chanctx *ctx = NULL; + + dev = hw->priv; + ctx = (struct umac_chanctx *)conf->drv_priv; + UCCP_DEBUG_TSMC("GOT remove chanctx\n"); + + UCCP_DEBUG_TSMC("%s: %d MHz\n", + __func__, + conf->def.chan->center_freq); + + mutex_lock(&dev->mutex); + + /* Unassign_vif_chanctx should have been called to free all the assigned + * vifs before this call is called, hence we dont need to specifically + * free the vifs here + */ + rcu_assign_pointer(dev->chanctx[ctx->index], NULL); + synchronize_rcu(); + + ctx->index = -1; + + mutex_unlock(&dev->mutex); +} + + +static void change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf, + u32 changed) +{ + struct umac_vif *uvif = NULL; + struct mac80211_dev *dev = NULL; + struct umac_chanctx *ctx = NULL; + int i = 0; + int center_freq = 0; + int chan = 0; + int err = 0; + + dev = hw->priv; + ctx = (struct umac_chanctx *)conf->drv_priv; + + UCCP_DEBUG_TSMC("Got change_chanctx: %d\n", changed); + pr_err("%s: %d MHz\n", __func__, conf->def.chan->center_freq); + + if (changed & IEEE80211_CHANCTX_CHANGE_WIDTH || + changed & IEEE80211_CHANCTX_CHANGE_CHANNEL) { + pr_err("%s channel width = %d channel = %d\n", __func__, + conf->def.width, conf->def.center_freq1); + + center_freq = conf->def.chan->center_freq; + chan = ieee80211_frequency_to_channel(center_freq); + + list_for_each_entry(uvif, &ctx->vifs, list) { + err = umac_chanctx_set_channel(dev, uvif, &conf->def); + + if (err) { + pr_err("%s: Failed to set channel/width\n", + __func__); + return; + } + } + } + + if (changed & IEEE80211_CHANCTX_CHANGE_MIN_WIDTH) { + UCCP_DEBUG_TSMC("%s Minimum channel width = %d\n", __func__, + conf->min_def.width); + + center_freq = conf->min_def.chan->center_freq; + chan = ieee80211_frequency_to_channel(center_freq); + + list_for_each_entry(uvif, &ctx->vifs, list) { + err = umac_chanctx_set_channel(dev, uvif, + &conf->min_def); + + if (err) { + pr_err("%s: Failed to set channel/width\n", + __func__); + + return; + } + } + } + + /* TODO: Make this global config as it effects all VIF's */ + if (changed & IEEE80211_CHANCTX_CHANGE_RX_CHAINS) { + UCCP_DEBUG_TSMC("%s rx_chains_static=%d rx_chains_dynamic=%d\n", + __func__, conf->rx_chains_static, + conf->rx_chains_dynamic); + + list_for_each_entry(uvif, &ctx->vifs, list) { + for (i = 0; i < MAX_VIFS; i++) { + if (!(dev->active_vifs & (1 << i))) + continue; + + if (dev->vifs[i] && + !ether_addr_equal(dev->vifs[i]->addr, + uvif->vif->addr)) + continue; + + if (conf->rx_chains_static > 1) + uccp420wlan_prog_vif_smps(i, + uvif->vif->addr, + IEEE80211_SMPS_OFF); + else if (conf->rx_chains_dynamic > 1) + uccp420wlan_prog_vif_smps(i, + uvif->vif->addr, + IEEE80211_SMPS_DYNAMIC); + else + uccp420wlan_prog_vif_smps(i, + uvif->vif->addr, + IEEE80211_SMPS_STATIC); + } + } + } + if (changed & IEEE80211_CHANCTX_CHANGE_RADAR) { + + UCCP_DEBUG_80211IF("%s radar enabled =%d\n", + __func__, + conf->radar_enabled); + + if (conf->radar_enabled) + uccp420wlan_prog_radar_detect(RADAR_DETECT_OP_START); + else + uccp420wlan_prog_radar_detect(RADAR_DETECT_OP_STOP); + } +} + + +static int assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *conf) +{ + struct mac80211_dev *dev = NULL; + struct umac_vif *uvif = NULL; + struct umac_chanctx *ctx = NULL; + int prog_chanctx_time_info = 0; + int err = 0; + + dev = hw->priv; + uvif = (struct umac_vif *)vif->drv_priv; + ctx = (struct umac_chanctx *)conf->drv_priv; + UCCP_DEBUG_TSMC("Got assign_vif_chanctx\n"); + + DEBUG_LOG("%s: addr: %pM, type: %d, p2p: %d chan: %d MHz\n", + __func__, + vif->addr, + vif->type, + vif->p2p, + conf->def.chan->center_freq); + + mutex_lock(&dev->mutex); + + uvif->chanctx = ctx; + list_add_tail(&uvif->list, &ctx->vifs); + + prog_chanctx_time_info = !(ctx->nvifs); + ctx->nvifs++; + + /* If this is the first vif being assigned to the channel context then + * increment our count of the active channel contexts + */ + if (prog_chanctx_time_info) { + if (!dev->num_active_chanctx) + dev->curr_chanctx_idx = ctx->index; + + dev->num_active_chanctx++; + uccp420wlan_prog_chanctx_time_info(); + } + + err = umac_chanctx_set_channel(dev, uvif, &conf->def); + + mutex_unlock(&dev->mutex); + + return err; +} + + +static void unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *conf) +{ + struct mac80211_dev *dev = NULL; + struct umac_vif *uvif = NULL; + struct umac_chanctx *ctx = NULL; + u32 hw_queue_map = 0; + int i = 0; + + dev = hw->priv; + uvif = (struct umac_vif *)vif->drv_priv; + ctx = (struct umac_chanctx *)conf->drv_priv; + + UCCP_DEBUG_TSMC("Got unassign_vif_chanctx\n"); + + DEBUG_LOG("%s: addr: %pM, type: %d, p2p: %d chan: %d MHz\n", + __func__, + vif->addr, + vif->type, + vif->p2p, + conf->def.chan->center_freq); + + mutex_lock(&dev->mutex); + + /* We need to specifically handle flushing tx queues for the AP VIF + * here (for STA VIF, mac80211 handles this via flush_queues) + */ + if (vif->type == NL80211_IFTYPE_AP) { + /* Flush all queues for this VIF */ + for (i = 0; i < NUM_ACS; i++) + hw_queue_map |= BIT(i); + + uccp420_flush_vif_queues(dev, + uvif, + uvif->chanctx->index, + hw_queue_map, + UMAC_VIF_CHANCTX_TYPE_OPER); + } + + uvif->chanctx = NULL; + + list_del(&uvif->list); + ctx->nvifs--; + + if (!ctx->nvifs) { + dev->num_active_chanctx--; + + if (dev->num_active_chanctx) + uccp420wlan_prog_chanctx_time_info(); + } + + mutex_unlock(&dev->mutex); +} + + +static int switch_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, + enum ieee80211_chanctx_switch_mode mode) +{ + struct mac80211_dev *dev = NULL; + int ret = 0; + + UCCP_DEBUG_TSMC("Got switch_vif_chanctx\n"); + dev = hw->priv; + + /*TODO*/ + if (n_vifs > 1) + return -EOPNOTSUPP; + + pr_err("%s switch_vif_chanctx switch freq %hu->%hu width %d->%d\n", + __func__, + vifs[0].old_ctx->def.chan->center_freq, + vifs[0].new_ctx->def.chan->center_freq, + vifs[0].old_ctx->def.width, + vifs[0].new_ctx->def.width); + + switch (mode) { + case CHANCTX_SWMODE_SWAP_CONTEXTS: + unassign_vif_chanctx(hw, vifs[0].vif, vifs[0].old_ctx); + remove_chanctx(hw, vifs[0].old_ctx); + add_chanctx(hw, vifs[0].new_ctx); + assign_vif_chanctx(hw, vifs[0].vif, vifs[0].new_ctx); + break; + case CHANCTX_SWMODE_REASSIGN_VIF: + unassign_vif_chanctx(hw, vifs[0].vif, vifs[0].old_ctx); + assign_vif_chanctx(hw, vifs[0].vif, vifs[0].new_ctx); + break; + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + + +static void flush_queues(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u32 queues, + bool drop) +{ + struct mac80211_dev *dev = NULL; + struct umac_vif *uvif = NULL; + u32 hw_queue_map = 0; + int i = 0; + + dev = hw->priv; + + mutex_lock(&dev->mutex); + + if (!vif) + goto out; + + uvif = (struct umac_vif *)vif->drv_priv; + + if (!uvif->chanctx) + goto out; + + /* Convert the mac80211 queue map to our hw queue map */ + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + if (queues & BIT(i)) + hw_queue_map |= BIT(tx_queue_map(i)); + } + /* This op should not get called during ROC operation, so we can assume + * that the vif_chanctx_type will be UMAC_VIF_CHANCTX_TYPE_OPER. As for + * TSMC operation the VIF can only be associated to one channel context, + * so we pass uvif->chanctx->index as the parameter for chanctx_idx + */ + uccp420_flush_vif_queues(dev, + uvif, + uvif->chanctx->index, + hw_queue_map, + UMAC_VIF_CHANCTX_TYPE_OPER); + +out: + mutex_unlock(&dev->mutex); +} +#endif + + +static struct ieee80211_ops ops = { + .tx = tx, + .start = start, + .stop = stop, + .add_interface = add_interface, + .remove_interface = remove_interface, + .config = config, + .prepare_multicast = prepare_multicast, + .configure_filter = configure_filter, + .sw_scan_start = NULL, + .sw_scan_complete = NULL, + .get_stats = NULL, + .sta_notify = NULL, + .conf_tx = conf_vif_tx, + .bss_info_changed = bss_info_changed, + .set_tim = NULL, + .set_key = set_key, + .tx_last_beacon = tx_last_beacon, + .ampdu_action = ampdu_action, + .set_antenna = set_antenna, + .remain_on_channel = remain_on_channel, + .cancel_remain_on_channel = cancel_remain_on_channel, +#ifdef CONFIG_PM + .suspend = img_suspend, + .resume = img_resume, +#endif + .hw_scan = scan, + .cancel_hw_scan = cancel_hw_scan, + .set_rekey_data = NULL, + .set_rts_threshold = set_rts_threshold, + .sta_add = sta_add, + .sta_remove = sta_remove, + .channel_switch_beacon = channel_switch_beacon, + CFG80211_TESTMODE_DUMP(rpu_testmode_dump) +#ifdef MULTI_CHAN_SUPPORT + .add_chanctx = add_chanctx, + .remove_chanctx = remove_chanctx, + .change_chanctx = change_chanctx, + .assign_vif_chanctx = assign_vif_chanctx, + .unassign_vif_chanctx = unassign_vif_chanctx, + .switch_vif_chanctx = switch_vif_chanctx, + .flush = flush_queues, +#endif +}; + +static void uccp420wlan_exit(void) +{ + /* DEV Release */ + struct mac80211_dev *dev = (struct mac80211_dev *)wifi->hw->priv; + + if (wifi->hw) { + ieee80211_unregister_hw(wifi->hw); + device_release_driver(dev->dev); + device_destroy(hwsim_class, 0); + ieee80211_free_hw(wifi->hw); + wifi->hw = NULL; + } + + class_destroy(hwsim_class); +} + +static int uccp420wlan_init(void) +{ + struct ieee80211_hw *hw; + int error; + struct mac80211_dev *dev = NULL; + int i; + + /* Allocate new hardware device */ + hw = ieee80211_alloc_hw(sizeof(struct mac80211_dev), &ops); + + if (hw == NULL) { + pr_err("Failed to allocate memory for ieee80211_hw\n"); + error = -ENOMEM; + goto out; + } + + dev = (struct mac80211_dev *)hw->priv; + memset(dev, 0, sizeof(struct mac80211_dev)); + + hwsim_class = class_create(THIS_MODULE, "uccp420"); + + if (IS_ERR(hwsim_class)) { + pr_err("Failed to create the device class\n"); + error = PTR_ERR(hwsim_class); + goto out; + } + + /* Only 1 per physical intf*/ + dev->dev = device_create(hwsim_class, NULL, 0, hw, "uccwlan"); + + if (IS_ERR(dev->dev)) { + pr_err("uccwlan: device_create failed (%ld)\n", + PTR_ERR(dev->dev)); + error = -ENOMEM; + goto auto_dev_class_failed; + } + + dev->dev->driver = &img_uccp_driver.driver; + + if (device_is_registered(dev->dev)) { + error = device_bind_driver(dev->dev); + } else { + pr_err("Device is not registered\n"); + error = -ENODEV; + goto failed_hw; + } + + if (error != 0) { + pr_err("uccwlan: device_bind_driver failed (%d)\n", error); + goto failed_hw; + } + + pr_info("MAC ADDR: %pM\n", vif_macs); + SET_IEEE80211_DEV(hw, dev->dev); + + mutex_init(&dev->mutex); + spin_lock_init(&dev->bcast_lock); +#ifdef MULTI_CHAN_SUPPORT + spin_lock_init(&dev->chanctx_lock); +#endif + + spin_lock_init(&dev->roc_lock); + dev->state = STOPPED; + dev->active_vifs = 0; + dev->txpower = DEFAULT_TX_POWER; + dev->tx_antenna = DEFAULT_TX_ANT_SELECT; + dev->rts_threshold = DEFAULT_RTS_THRESHOLD; + strncpy(dev->name, UCCP_DRIVER_NAME, 11); + dev->name[11] = '\0'; + + for (i = 0; i < wifi->params.num_vifs; i++) + ether_addr_copy(dev->if_mac_addresses[i].addr, vif_macs[i]); + + /* Initialize HW parameters */ + init_hw(hw); + dev->hw = hw; + dev->params = &wifi->params; + dev->stats = &wifi->stats; + dev->umac_proc_dir_entry = wifi->umac_proc_dir_entry; + dev->current_vif_count = 0; + dev->stats->system_rev = system_rev; +#ifdef MULTI_CHAN_SUPPORT + dev->num_active_chanctx = 0; + + for (i = 0; i < MAX_VIFS; i++) + dev->vifs[i] = NULL; +#endif + + /*Register hardware*/ + error = ieee80211_register_hw(hw); + + /* Production test hack: Set all channel flags to 0 to allow IBSS + * creation in all channels + */ + if (wifi->params.production_test && !error) { + enum ieee80211_band band; + struct ieee80211_supported_band *sband; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + sband = hw->wiphy->bands[band]; + if (sband) + for (i = 0; i < sband->n_channels; i++) + sband->channels[i].flags = 0; + } + } + + if (!error) { + wifi->hw = hw; + goto out; + } else { + uccp420wlan_exit(); + goto out; + } + +failed_hw: + device_release_driver(dev->dev); + device_destroy(hwsim_class, 0); +auto_dev_class_failed: + class_destroy(hwsim_class); +out: + return error; +} + + +static char *uccp420_get_vif_name(int vif_idx) +{ + struct mac80211_dev *dev = ((struct mac80211_dev *)(wifi->hw->priv)); + struct wireless_dev *wdev = NULL; + struct ieee80211_vif *vif = NULL; + + if ((dev->active_vifs & (1 << vif_idx))) { + rcu_read_lock(); + vif = rcu_dereference(dev->vifs[vif_idx]); + rcu_read_unlock(); + + wdev = ieee80211_vif_to_wdev(vif); + return wdev->netdev->name; + } + + return NULL; +} + + +static int proc_read_config(struct seq_file *m, void *v) +{ + int i = 0; + int cnt = 0; + int rf_params_size = sizeof(wifi->params.rf_params) / + sizeof(wifi->params.rf_params[0]); + struct mac80211_dev *dev = ((struct mac80211_dev *)(wifi->hw->priv)); + + seq_puts(m, "************* Configurable Parameters ***********\n"); + seq_printf(m, "dot11g_support = %d\n", wifi->params.dot11g_support); + seq_printf(m, "dot11a_support = %d\n", wifi->params.dot11a_support); + seq_printf(m, "sensitivity = %d\n", wifi->params.ed_sensitivity); + seq_printf(m, "auto_sensitivity = %d\n", wifi->params.auto_sensitivity); + /*RF Input params*/ + seq_puts(m, "rf_params ="); + for (i = 0; i < rf_params_size; i++) + seq_printf(m, " %02X", wifi->params.rf_params[i]); + + seq_puts(m, "\n"); + + seq_puts(m, "rf_params_vpd ="); + for (i = 0; i < rf_params_size; i++) + seq_printf(m, " %02X", wifi->params.rf_params_vpd[i]); + + seq_puts(m, "\n"); + + seq_printf(m, "production_test = %d\n", wifi->params.production_test); + seq_printf(m, "bypass_vpd = %d\n", wifi->params.bypass_vpd); + seq_printf(m, "tx_fixed_mcs_indx = %d (%s)\n", + wifi->params.tx_fixed_mcs_indx, + (wifi->params.prod_mode_rate_flag & + ENABLE_VHT_FORMAT) ? + "VHT" : (wifi->params.prod_mode_rate_flag & + ENABLE_11N_FORMAT) ? "HT" : "Not Set"); + if (wifi->params.tx_fixed_rate > -1) { + if (wifi->params.tx_fixed_rate == 55) + seq_puts(m, "tx_fixed_rate = 5.5\n"); + else + seq_printf(m, "tx_fixed_rate = %d\n", + wifi->params.tx_fixed_rate); + } else + seq_printf(m, "tx_fixed_rate = %d\n", + wifi->params.tx_fixed_rate); + seq_printf(m, "num_spatial_streams (Per Frame) = %d\n", + wifi->params.num_spatial_streams); + seq_printf(m, "uccp_num_spatial_streams (UCCP Init) = %d\n", + wifi->params.uccp_num_spatial_streams); + seq_printf(m, "enable_early_agg_checks = %d\n", + wifi->params.enable_early_agg_checks); + seq_printf(m, "antenna_sel (UCCP Init) = %d\n", + wifi->params.antenna_sel); + seq_printf(m, "max_data_size = %d (%dK)\n", + wifi->params.max_data_size, + wifi->params.max_data_size/1024); + seq_printf(m, "max_tx_cmds = %d\n", + wifi->params.max_tx_cmds); + seq_printf(m, "disable_power_save (Disables all power save's) = %d\n", + wifi->params.disable_power_save); + seq_printf(m, "disable_sm_power_save (Disables MIMO PS only) = %d\n", + wifi->params.disable_sm_power_save); + seq_printf(m, "mgd_mode_tx_fixed_mcs_indx = %d (%s)\n", + wifi->params.mgd_mode_tx_fixed_mcs_indx, + (wifi->params.prod_mode_rate_flag & + ENABLE_VHT_FORMAT) ? + "VHT" : (wifi->params.prod_mode_rate_flag & + ENABLE_11N_FORMAT) ? "HT" : "Not Set"); + if (wifi->params.mgd_mode_tx_fixed_rate > -1) { + if (wifi->params.mgd_mode_tx_fixed_rate == 55) + seq_puts(m, "mgd_mode_tx_fixed_rate = 5.5\n"); + else + seq_printf(m, "mgd_mode_tx_fixed_rate = %d\n", + wifi->params.mgd_mode_tx_fixed_rate); + } else + seq_printf(m, "mgd_mode_tx_fixed_rate = %d\n", + wifi->params.mgd_mode_tx_fixed_rate); + + seq_printf(m, "num_vifs = %d\n", + wifi->params.num_vifs); + + seq_printf(m, "chnl_bw = %d\n", + wifi->params.chnl_bw); + + seq_printf(m, "prod_mode_chnl_bw_40_mhz = %d\n", + wifi->params.prod_mode_chnl_bw_40_mhz); + if (vht_support) + seq_printf(m, "prod_mode_chnl_bw_80_mhz = %d\n", + wifi->params.prod_mode_chnl_bw_80_mhz); + seq_printf(m, "sec_ch_offset_40_plus = %d\n", + wifi->params.sec_ch_offset_40_plus); + seq_printf(m, "sec_ch_offset_40_minus = %d\n", + wifi->params.sec_ch_offset_40_minus); + + if (vht_support) { + seq_printf(m, "sec_40_ch_offset_80_plus = %d\n", + wifi->params.sec_40_ch_offset_80_plus); + seq_printf(m, "sec_40_ch_offset_80_minus = %d\n", + wifi->params.sec_40_ch_offset_80_minus); + } + seq_printf(m, "rate_protection_type = %d (0: Disable, 1: Enable)\n", + wifi->params.rate_protection_type); + seq_puts(m, "Bits:80MHz-VHT-11N-SGI-40MHz-GF\n"); + seq_printf(m, "prod_mode_rate_flag = %d\n", + wifi->params.prod_mode_rate_flag); + seq_printf(m, "prod_mode_rate_preamble_type (0: Short, 1: Long) = %d\n", + wifi->params.prod_mode_rate_preamble_type); + seq_printf(m, "prod_mode_stbc_enabled = %d\n", + wifi->params.prod_mode_stbc_enabled); + seq_printf(m, "prod_mode_bcc_or_ldpc = %d\n", + wifi->params.prod_mode_bcc_or_ldpc); + seq_printf(m, "vht_beamformer_enable = %d\n", + wifi->params.vht_beamform_enable); + seq_printf(m, "vht_beamformer_period = %dms\n", + wifi->params.vht_beamform_period); + seq_printf(m, "bg_scan_enable = %d\n", + wifi->params.bg_scan_enable); + seq_puts(m, "bg_scan_channel_list ="); + + for (i = 0; i < wifi->params.bg_scan_num_channels; i++) { + if (wifi->params.bg_scan_channel_list[i]) + seq_printf(m, " %d", + wifi->params.bg_scan_channel_list[i]); + } + + seq_puts(m, "\n"); + seq_puts(m, "bg_scan_channel_flags ="); + + for (i = 0; i < wifi->params.bg_scan_num_channels; i++) { + if (wifi->params.bg_scan_channel_flags[i]) + seq_printf(m, " %d", + wifi->params.bg_scan_channel_flags[i]); + } + + seq_puts(m, "\n"); + seq_printf(m, "bg_scan_intval = %dms\n", + wifi->params.bg_scan_intval/1000); + + /*currently not used in LMAC, so don't export to user*/ +#if 0 + seq_printf(m, "bg_scan_chan_dur = %d\n", wifi->params.bg_scan_chan_dur); + seq_printf(m, "bg_scan_serv_chan_dur = %d\n", + wifi->params.bg_scan_serv_chan_dur); +#endif + seq_printf(m, "bg_scan_num_channels = %d\n", + wifi->params.bg_scan_num_channels); + seq_printf(m, "nw_selection = %d\n", + wifi->params.nw_selection); + seq_printf(m, "scan_type = %d (PASSIVE: 0, ACTIVE: 1)\n", + wifi->params.scan_type); +#ifdef PERF_PROFILING + seq_printf(m, "driver_tput = %d\n", + wifi->params.driver_tput); +#endif + seq_printf(m, "fw_loading = %d\n", wifi->params.fw_loading); + seq_printf(m, "bt_state = %d\n", wifi->params.bt_state); + + /* Beacon Time Stamp */ + if (dev->state == STARTED) { + for (cnt = 0; cnt < MAX_VIFS; cnt++) { + unsigned long long ts1; + unsigned long long bssid, atu; + int status; + unsigned int t2; + + spin_lock_bh(&tsf_lock); + ts1 = get_unaligned_le64(wifi->params.sync[cnt].ts1); + bssid = + get_unaligned_le64(wifi->params.sync[cnt].bssid); + status = wifi->params.sync[cnt].status; + atu = wifi->params.sync[cnt].atu; + t2 = wifi->params.sync[cnt].ts2; + spin_unlock_bh(&tsf_lock); + if (status) + seq_printf(m, + "sync=%s %d %llu %llu %llx t2=%u\n", + uccp420_get_vif_name(cnt), + status, + (unsigned long long)ts1, + atu, + (unsigned long long)bssid, + t2); + } + } + + seq_puts(m, "****** Production Test (or) FTM Parameters *******\n"); + seq_printf(m, "start_packet_gen = %d (-1: Infinite loop)\n", + wifi->params.pkt_gen_val); + seq_printf(m, "payload_length = %d bytes\n", + wifi->params.payload_length); + seq_printf(m, "start_prod_mode = channel: %d\n", + wifi->params.start_prod_mode); + seq_printf(m, "continuous_tx = %d\n", + wifi->params.cont_tx); + + if (ftm || wifi->params.production_test) + seq_printf(m, "set_tx_power = %d dB\n", + wifi->params.set_tx_power); + + seq_printf(m, "center_frequency = %d\n", + ieee80211_frequency_to_channel(dev->cur_chan.center_freq1)); + + if (ftm) + seq_printf(m, "aux_adc_chain_id = %d\n", + wifi->params.aux_adc_chain_id); + + seq_puts(m, "UCCP Runtime Debug Support Configuration.\n"); + seq_printf(m, "uccp_debug = %d.\n", uccp_debug); + if (uccp_debug == UCCP_DBG_DEFAULT) + seq_puts(m, "***uccp_debug: All debugs are disabled.\n"); + if (uccp_debug & UCCP_DEBUG_SCAN) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_SCAN\n"); + if (uccp_debug & UCCP_DEBUG_ROC) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_ROC\n"); + if (uccp_debug & UCCP_DEBUG_TX) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_TX\n"); + if (uccp_debug & UCCP_DEBUG_CORE) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_CORE\n"); + if (uccp_debug & UCCP_DEBUG_IF) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_IF\n"); + if (uccp_debug & UCCP_DEBUG_80211IF) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_80211IF\n"); + if (uccp_debug & UCCP_DEBUG_RX) + seq_puts(m, "***uccp_debug:UCCP_DEBUG_RX\n"); + if (uccp_debug & UCCP_DEBUG_HAL) + seq_puts(m, "***uccp_debug:UCCP_DEBUG_HAL\n"); + if (uccp_debug & UCCP_DEBUG_CRYPTO) + seq_puts(m, "***uccp_debug:UCCP_DEBUG_CRYPTO\n"); + if (uccp_debug & UCCP_DEBUG_DUMP_RX) + seq_puts(m, "***uccp_debug: DUMP_RX Enabled\n"); + if (uccp_debug & UCCP_DEBUG_DUMP_HAL) + seq_puts(m, "***uccp_debug: DUMP_HAL Enabled\n"); + if (uccp_debug & UCCP_DEBUG_TSMC) + seq_puts(m, "***uccp_debug: UCCP_DEBUG_TSMC Enabled\n"); + + seq_puts(m, "HELP: Add the values beside Module and\n"); + seq_puts(m, " echo uccp_debug= to enable logging\n"); + seq_puts(m, " for those modules.\n"); + seq_puts(m, "MODULE (Value): TSMC (4096), DUMP_HAL (1024), DUMP_RX (512),\n"); + seq_puts(m, " CRYPTO(256), HAL(128), RX(64),\n"); + seq_puts(m, " 80211IF(32), UMAC_IF(16), CORE(8),\n"); + seq_puts(m, " TX(4), ROC(2), SCAN(1),\n"); + + seq_puts(m, "To see the updated stats\n"); + seq_puts(m, "please run: echo get_stats=1 > /proc/uccp420/params\n"); + seq_puts(m, "To see the cleared phy stats\n"); + seq_puts(m, "please run: echo clear_stats=1 > /proc/uccp420/params\n"); + seq_puts(m, "************* VERSION ***********\n"); + seq_printf(m, "UCCP_DRIVER_VERSION = %s\n", UCCP_DRIVER_VERSION); + + if (wifi->hw && + (((struct mac80211_dev *)(wifi->hw->priv))->state != STARTED)) { + seq_printf(m, "LMAC_VERSION = %s\n", "UNKNOWN"); + seq_printf(m, "Firmware version = %s\n", "UNKNOWN"); + } else { + seq_printf(m, "LMAC_VERSION = %s\n", + wifi->stats.uccp420_lmac_version); + seq_printf(m, "Firmware version= %d.%d\n", + (wifi->stats.uccp420_lmac_version[0] - '0'), + (wifi->stats.uccp420_lmac_version[2] - '0')); + } + + return 0; +} + + +static int proc_read_phy_stats(struct seq_file *m, void *v) +{ + + int i = 0; + + seq_puts(m, "************* BB Stats ***********\n"); + seq_printf(m, "ed_cnt=%d\n", + wifi->stats.ed_cnt); + seq_printf(m, "mpdu_cnt=%d\n", + wifi->stats.mpdu_cnt); + seq_printf(m, "ofdm_crc32_pass_cnt=%d\n", + wifi->stats.ofdm_crc32_pass_cnt); + seq_printf(m, "ofdm_crc32_fail_cnt=%d\n", + wifi->stats.ofdm_crc32_fail_cnt); + seq_printf(m, "dsss_crc32_pass_cnt=%d\n", + wifi->stats.dsss_crc32_pass_cnt); + seq_printf(m, "dsss_crc32_fail_cnt=%d\n", + wifi->stats.dsss_crc32_fail_cnt); + seq_printf(m, "mac_id_pass_cnt=%d\n", + wifi->stats.mac_id_pass_cnt); + seq_printf(m, "mac_id_fail_cnt=%d\n", + wifi->stats.mac_id_fail_cnt); + seq_printf(m, "ofdm_corr_pass_cnt=%d\n", + wifi->stats.ofdm_corr_pass_cnt); + seq_printf(m, "ofdm_corr_fail_cnt=%d\n", + wifi->stats.ofdm_corr_fail_cnt); + seq_printf(m, "dsss_corr_pass_cnt=%d\n", + wifi->stats.dsss_corr_pass_cnt); + seq_printf(m, "dsss_corr_fail_cnt=%d\n", + wifi->stats.dsss_corr_fail_cnt); + seq_printf(m, "ofdm_s2l_fail_cnt=%d\n", + wifi->stats.ofdm_s2l_fail_cnt); + seq_printf(m, "lsig_fail_cnt=%d\n", + wifi->stats.lsig_fail_cnt); + seq_printf(m, "htsig_fail_cnt=%d\n", + wifi->stats.htsig_fail_cnt); + seq_printf(m, "vhtsiga_fail_cnt=%d\n", + wifi->stats.vhtsiga_fail_cnt); + seq_printf(m, "vhtsigb_fail_cnt=%d\n", + wifi->stats.vhtsigb_fail_cnt); + seq_printf(m, "nonht_ofdm_cnt=%d\n", + wifi->stats.nonht_ofdm_cnt); + seq_printf(m, "nonht_dsss_cnt=%d\n", + wifi->stats.nonht_dsss_cnt); + seq_printf(m, "mm_cnt=%d\n", + wifi->stats.mm_cnt); + seq_printf(m, "gf_cnt=%d\n", + wifi->stats.gf_cnt); + seq_printf(m, "vht_cnt=%d\n", + wifi->stats.vht_cnt); + seq_printf(m, "aggregation_cnt=%d\n", + wifi->stats.aggregation_cnt); + seq_printf(m, "non_aggregation_cnt=%d\n", + wifi->stats.non_aggregation_cnt); + seq_printf(m, "ndp_cnt=%d\n", + wifi->stats.ndp_cnt); + seq_printf(m, "ofdm_ldpc_cnt=%d\n", + wifi->stats.ofdm_ldpc_cnt); + seq_printf(m, "ofdm_bcc_cnt=%d\n", + wifi->stats.ofdm_bcc_cnt); + seq_printf(m, "midpacket_cnt=%d\n", + wifi->stats.midpacket_cnt); + seq_printf(m, "dsss_sfd_fail_cnt=%d\n", + wifi->stats.dsss_sfd_fail_cnt); + seq_printf(m, "dsss_hdr_fail_cnt=%d\n", + wifi->stats.dsss_hdr_fail_cnt); + seq_printf(m, "dsss_short_preamble_cnt=%d\n", + wifi->stats.dsss_short_preamble_cnt); + seq_printf(m, "dsss_long_preamble_cnt=%d\n", + wifi->stats.dsss_long_preamble_cnt); + seq_printf(m, "sifs_event_cnt=%d\n", + wifi->stats.sifs_event_cnt); + seq_printf(m, "cts_cnt=%d\n", + wifi->stats.cts_cnt); + seq_printf(m, "ack_cnt=%d\n", + wifi->stats.ack_cnt); + seq_printf(m, "sifs_no_resp_cnt=%d\n", + wifi->stats.sifs_no_resp_cnt); + seq_printf(m, "unsupported_cnt=%d\n", + wifi->stats.unsupported_cnt); + seq_printf(m, "l1_corr_fail_cnt=%d\n", + wifi->stats.l1_corr_fail_cnt); + seq_printf(m, "sifs_crc_exit_cnt=%d\n", + wifi->stats.sifs_crc_exit_cnt); + seq_printf(m, "low_energy_event_cnt=%d\n", + wifi->stats.low_energy_event_cnt); + seq_printf(m, "deagg_error_cnt=%d\n", + wifi->stats.deagg_error_cnt); + seq_printf(m, "nsymbols_error_cnt=%d\n", + wifi->stats.nsymbols_error_cnt); + seq_printf(m, "mcs32_cnt=%d\n", + wifi->stats.mcs32_cnt); + seq_printf(m, "ndpa_cnt=%d\n", + wifi->stats.ndpa_cnt); + seq_printf(m, "lsig_duration_error_cnt=%d\n", + wifi->stats.lsig_duration_error_cnt); + seq_printf(m, "rts_cnt=%d\n", + wifi->stats.rts_cnt); + seq_printf(m, "non_ht_cts_cnt=%d\n", + wifi->stats.non_ht_cts_cnt); + seq_printf(m, "rxp_active_exit_cnt=%d\n", + wifi->stats.rxp_active_exit_cnt); + seq_printf(m, "beamform_feedback_cnt=%d\n", + wifi->stats.beamform_feedback_cnt); + seq_printf(m, "self_cts_cnt=%d\n", + wifi->stats.self_cts_cnt); + seq_printf(m, "pop_master_cnt=%d\n", + wifi->stats.pop_master_cnt); + seq_printf(m, "pop_error_cnt=%d\n", + wifi->stats.pop_error_cnt); + seq_printf(m, "multicast_cnt=%d\n", + wifi->stats.multicast_cnt); + seq_printf(m, "tx_ed_abort_cnt=%d\n", + wifi->stats.tx_ed_abort_cnt); + seq_printf(m, "mcp_cts_cnt=%d\n", + wifi->stats.mcp_cts_cnt); + seq_printf(m, "deagg_q_post_cnt=%d\n", + wifi->stats.deagg_q_post_cnt); + seq_printf(m, "rxp_active_exit_dsss_cnt=%d\n", + wifi->stats.rxp_active_exit_dsss_cnt); + seq_printf(m, "rxp_extreme_error_cnt=%d\n", + wifi->stats.rxp_extreme_error_cnt); + seq_printf(m, "aci_fail_cnt=%d\n", + wifi->stats.aci_fail_cnt); + /* TX stats*/ + seq_printf(m, "tx_pkts_from_lmac = %d\n", + wifi->stats.tx_pkts_from_lmac); + seq_printf(m, "tx_pkts_tx2tx = %d\n", + wifi->stats.tx_pkts_tx2tx); + seq_printf(m, "tx_pkts_from_rx = %d\n", + wifi->stats.tx_pkts_from_rx); + seq_printf(m, "tx_pkts_ofdm = %d\n", + wifi->stats.tx_pkts_ofdm); + seq_printf(m, "tx_pkts_dsss = %d\n", + wifi->stats.tx_pkts_dsss); + seq_printf(m, "tx_pkts_reached_end_of_fsm = %d\n", + wifi->stats.tx_pkts_reached_end_of_fsm); + seq_printf(m, "tx_unsupported_modulation = %d\n", + wifi->stats.tx_unsupported_modulation); + seq_printf(m, "tx_latest_pkt_from_lmac_or_sifs = %d\n", + wifi->stats.tx_latest_pkt_from_lmac_or_sifs); + seq_printf(m, "tx_abort_bt_confirm_cnt = %d\n", + wifi->stats.tx_abort_bt_confirm_cnt); + seq_printf(m, "tx_abort_txstart_timeout_cnt = %d\n", + wifi->stats.tx_abort_txstart_timeout_cnt); + seq_printf(m, "tx_abort_midBT_cnt = %d\n", + wifi->stats.tx_abort_mid_bt_cnt); + seq_printf(m, "tx_abort_dac_underrun_cnt = %d\n", + wifi->stats.tx_abort_dac_underrun_cnt); + seq_printf(m, "tx_ofdm_symbols_master = %d\n", + wifi->stats.tx_ofdm_symbols_master); + seq_printf(m, "tx_ofdm_symbols_slave1 = %d\n", + wifi->stats.tx_ofdm_symbols_slave1); + seq_printf(m, "tx_ofdm_symbols_slave2 = %d\n", + wifi->stats.tx_ofdm_symbols_slave2); + seq_printf(m, "tx_dsss_symbols = %d\n", + wifi->stats.tx_dsss_symbols); + + seq_puts(m, "************* RF Stats ***********\n"); + /*RF output data*/ + seq_puts(m, "rf_calib_data ="); + for (i = 0; i < wifi->stats.rf_calib_data_length; i++) + seq_printf(m, "%02X", wifi->stats.rf_calib_data[i]); + + seq_puts(m, "\n"); + return 0; +} + +static int proc_read_mac_stats(struct seq_file *m, void *v) +{ + unsigned int index; + unsigned int total_samples = 0; + unsigned int total_value = 0; + int total_rssi_samples = 0; + int total_rssi_value = 0; + struct mac80211_dev *dev = NULL; + + if (ftm) { + for (index = 0; index < MAX_AUX_ADC_SAMPLES; index++) { + if (!wifi->params.pdout_voltage[index]) + continue; + + total_value += wifi->params.pdout_voltage[index]; + total_samples++; + } + } + + for (index = 0; index < MAX_RSSI_SAMPLES; index++) { + + if (!wifi->params.production_test) + break; + + if (!wifi->params.rssi_average[index]) + continue; + + total_rssi_value += wifi->params.rssi_average[index]; + total_rssi_samples++; + } + + seq_puts(m, "************* UMAC STATS ***********\n"); + seq_printf(m, "rx_packet_mgmt_count = %d\n", + wifi->stats.rx_packet_mgmt_count); + seq_printf(m, "rx_packet_data_count = %d\n", + wifi->stats.rx_packet_data_count); + seq_printf(m, "tx_packet_count(HT MCS0) = %d\n", + wifi->stats.ht_tx_mcs0_packet_count); + seq_printf(m, "tx_packet_count(HT MCS1) = %d\n", + wifi->stats.ht_tx_mcs1_packet_count); + seq_printf(m, "tx_packet_count(HT MCS2) = %d\n", + wifi->stats.ht_tx_mcs2_packet_count); + seq_printf(m, "tx_packet_count(HT MCS3) = %d\n", + wifi->stats.ht_tx_mcs3_packet_count); + seq_printf(m, "tx_packet_count(HT MCS4) = %d\n", + wifi->stats.ht_tx_mcs4_packet_count); + seq_printf(m, "tx_packet_count(HT MCS5) = %d\n", + wifi->stats.ht_tx_mcs5_packet_count); + seq_printf(m, "tx_packet_count(HT MCS6) = %d\n", + wifi->stats.ht_tx_mcs6_packet_count); + seq_printf(m, "tx_packet_count(HT MCS7) = %d\n", + wifi->stats.ht_tx_mcs7_packet_count); + + if (wifi->params.uccp_num_spatial_streams == 2) { + seq_printf(m, "tx_packet_count(HT MCS8) = %d\n", + wifi->stats.ht_tx_mcs8_packet_count); + seq_printf(m, "tx_packet_count(HT MCS9) = %d\n", + wifi->stats.ht_tx_mcs9_packet_count); + seq_printf(m, "tx_packet_count(HT MCS10) = %d\n", + wifi->stats.ht_tx_mcs10_packet_count); + seq_printf(m, "tx_packet_count(HT MCS11) = %d\n", + wifi->stats.ht_tx_mcs11_packet_count); + seq_printf(m, "tx_packet_count(HT MCS12) = %d\n", + wifi->stats.ht_tx_mcs12_packet_count); + seq_printf(m, "tx_packet_count(HT MCS13) = %d\n", + wifi->stats.ht_tx_mcs13_packet_count); + seq_printf(m, "tx_packet_count(HT MCS14) = %d\n", + wifi->stats.ht_tx_mcs14_packet_count); + seq_printf(m, "tx_packet_count(HT MCS15) = %d\n", + wifi->stats.ht_tx_mcs15_packet_count); + } + if (vht_support) { + seq_printf(m, "tx_packet_count(VHT MCS0) = %d\n", + wifi->stats.vht_tx_mcs0_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS1) = %d\n", + wifi->stats.vht_tx_mcs1_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS2) = %d\n", + wifi->stats.vht_tx_mcs2_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS3) = %d\n", + wifi->stats.vht_tx_mcs3_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS4) = %d\n", + wifi->stats.vht_tx_mcs4_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS5) = %d\n", + wifi->stats.vht_tx_mcs5_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS6) = %d\n", + wifi->stats.vht_tx_mcs6_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS7) = %d\n", + wifi->stats.vht_tx_mcs7_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS8) = %d\n", + wifi->stats.vht_tx_mcs8_packet_count); + seq_printf(m, "tx_packet_count(VHT MCS9) = %d\n", + wifi->stats.vht_tx_mcs9_packet_count); + } + seq_printf(m, "tx_cmds_from_stack= %d\n", + wifi->stats.tx_cmds_from_stack); + seq_printf(m, "tx_dones_to_stack= %d\n", + wifi->stats.tx_dones_to_stack); + seq_printf(m, "tx_noagg_not_addr= %d\n", + wifi->stats.tx_noagg_not_addr); + seq_printf(m, "tx_noagg_not_ampdu= %d\n", + wifi->stats.tx_noagg_not_ampdu); + seq_printf(m, "tx_noagg_not_qos= %d\n", + wifi->stats.tx_noagg_not_qos); + seq_printf(m, "oustanding_cmd_cnt = %d\n", + wifi->stats.outstanding_cmd_cnt); + seq_printf(m, "gen_cmd_send_count = %d\n", + wifi->stats.gen_cmd_send_count); + seq_printf(m, "umac_scan_req = %d\n", + wifi->stats.umac_scan_req); + seq_printf(m, "umac_scan_complete = %d\n", + wifi->stats.umac_scan_complete); + seq_printf(m, "tx_cmd_send_count_single = %d\n", + wifi->stats.tx_cmd_send_count_single); + seq_printf(m, "tx_cmd_send_count_multi = %d\n", + wifi->stats.tx_cmd_send_count_multi); + seq_printf(m, "tx_cmd_send_count_beacon_q = %d\n", + wifi->stats.tx_cmd_send_count_beaconq); + seq_printf(m, "tx_done_recv_count = %d\n", + wifi->stats.tx_done_recv_count); + + dev = (struct mac80211_dev *)(wifi->hw->priv); + seq_printf(m, "tx_buff_pool_map = %ld\n", + dev->tx.buf_pool_bmp[0]); + { + int i, j; + struct sk_buff_head *pend_pkt_q; + + seq_puts(m, "Pending Qs status\n"); + for (i = 0; i < MAX_PEERS; i++) { + if (!dev->peers[i]) + continue; + + for (j = 0; j < WLAN_AC_MAX_CNT; j++) { + spin_lock_bh(&dev->tx.lock); + pend_pkt_q = &dev->tx.pending_pkt[0][i][j]; + if (skb_queue_len(pend_pkt_q)) + seq_printf(m, "ac:%d peer:%d = %d\n", + j, + i, + skb_queue_len(pend_pkt_q)); + spin_unlock_bh(&dev->tx.lock); + } + } + seq_puts(m, "\n"); + } + + if (ftm) + seq_printf(m, "pdout_val = %d (total samples: %d)\n", + total_samples ? (total_value/total_samples) : 0, + total_samples); + if (wifi->params.production_test) + seq_printf(m, + "rssi_average = %d dBm (total rssi samples: %d)\n", + total_rssi_samples ? + (total_rssi_value/total_rssi_samples) : 0, + total_rssi_samples); + + seq_puts(m, "************* LMAC STATS ***********\n"); + seq_printf(m, "roc_start =%d\n", + wifi->stats.roc_start); + seq_printf(m, "roc_stop =%d\n", + wifi->stats.roc_stop); + seq_printf(m, "roc_complete =%d\n", + wifi->stats.roc_complete); + seq_printf(m, "roc_stop_complete =%d\n", + wifi->stats.roc_stop_complete); + /* TX related */ + seq_printf(m, "tx_cmd_cnt =%d\n", + wifi->stats.tx_cmd_cnt); + seq_printf(m, "tx_done_cnt =%d\n", + wifi->stats.tx_done_cnt); + seq_printf(m, "tx_edca_trigger_cnt =%d\n", + wifi->stats.tx_edca_trigger_cnt); + seq_printf(m, "tx_edca_isr_cnt =%d\n", + wifi->stats.tx_edca_isr_cnt); + seq_printf(m, "tx_start_cnt =%d\n", + wifi->stats.tx_start_cnt); + seq_printf(m, "tx_abort_cnt =%d\n", + wifi->stats.tx_abort_cnt); + seq_printf(m, "tx_abort_isr_cnt =%d\n", + wifi->stats.tx_abort_isr_cnt); + seq_printf(m, "tx_underrun_cnt =%d\n", + wifi->stats.tx_underrun_cnt); + seq_printf(m, "tx_rts_cnt =%d\n", + wifi->stats.tx_rts_cnt); + seq_printf(m, "tx_ampdu_cnt =%d\n", + wifi->stats.tx_ampdu_cnt); + seq_printf(m, "tx_mpdu_cnt =%d\n", + wifi->stats.tx_mpdu_cnt); + + /* RX related */ + seq_printf(m, "rx_isr_cnt =%d\n", + wifi->stats.rx_isr_cnt); + seq_printf(m, "rx_ack_cts_to_cnt =%d\n", + wifi->stats.rx_ack_cts_to_cnt); + seq_printf(m, "rx_cts_cnt =%d\n", + wifi->stats.rx_cts_cnt); + seq_printf(m, "rx_ack_resp_cnt =%d\n", + wifi->stats.rx_ack_resp_cnt); + seq_printf(m, "rx_ba_resp_cnt =%d\n", + wifi->stats.rx_ba_resp_cnt); + seq_printf(m, "rx_fail_in_ba_bitmap_cnt =%d\n", + wifi->stats.rx_fail_in_ba_bitmap_cnt); + seq_printf(m, "rx_circular_buffer_free_cnt =%d\n", + wifi->stats.rx_circular_buffer_free_cnt); + seq_printf(m, "rx_mic_fail_cnt =%d\n", + wifi->stats.rx_mic_fail_cnt); + + /* HAL related */ + seq_printf(m, "hal_cmd_cnt =%d\n", + wifi->stats.hal_cmd_cnt); + seq_printf(m, "hal_event_cnt =%d\n", + wifi->stats.hal_event_cnt); + seq_printf(m, "hal_ext_ptr_null_cnt =%d\n", + wifi->stats.hal_ext_ptr_null_cnt); + + return 0; + +} + + +void uccp420wlan_reinit(void) +{ + + if (wifi->hw) + uccp420wlan_exit(); + uccp420wlan_init(); + uccp_reinit = 1; +} +static ssize_t proc_write_config(struct file *file, + const char __user *buffer, + size_t count, + loff_t *ppos) +{ + char buf[(RF_PARAMS_SIZE * 2) + 50]; + unsigned long val; + long sval; + unsigned int rate = wifi->params.prod_mode_rate_flag; + unsigned int b40 = wifi->params.prod_mode_chnl_bw_40_mhz; + unsigned int b80 = wifi->params.prod_mode_chnl_bw_80_mhz; + struct mac80211_dev *dev = wifi->hw->priv; + + if (count >= sizeof(buf)) + count = sizeof(buf) - 1; + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + buf[count] = '\0'; + + if (param_get_val(buf, "dot11a_support=", &val)) { + if (((val == 0) || (val == 1)) && + (wifi->params.dot11a_support != val)) { + wifi->params.dot11a_support = val; + + if ((wifi->params.dot11g_support == 0) && + (wifi->params.dot11a_support == 0)) { + pr_err("Invalid parameter value. Both bands can't be disabled, at least 1 is needed\n"); + } else { + uccp420wlan_reinit(); + pr_info("Re-initializing UMAC ..with 2.4GHz support %s and 5GHz support %s\n", + wifi->params.dot11g_support == 0 ? + "disabled" : "enabled", + wifi->params.dot11a_support == 0 ? + "disabled" : "enabled"); + + } + } else + pr_err("Invalid parameter value\n"); + } else if (param_get_val(buf, "dot11g_support=", &val)) { + if (((val == 0) || (val == 1)) && + (wifi->params.dot11g_support != val)) { + wifi->params.dot11g_support = val; + + if ((wifi->params.dot11g_support == 0) && + (wifi->params.dot11a_support == 0)) { + pr_err("Invalid parameter value. Both bands can't be disabled, at least 1 is needed\n"); + } else { + uccp420wlan_reinit(); + pr_info("Re-initializing UMAC ..with 2.4GHz support %s and 5GHz support %s\n", + wifi->params.dot11g_support == 0 ? + "disabled" : "enabled", + wifi->params.dot11a_support == 0 ? + "disabled" : "enabled"); + + } + } else + pr_err("Invalid parameter value\n"); + } else if (param_get_sval(buf, "sensitivity=", &sval)) { + /*if (sval > -51 || sval < -96 || (sval % 3 != 0))*/ + /* pr_err("Invalid parameter value\n");*/ + /*else*/ + wifi->params.ed_sensitivity = sval; + } else if (param_get_val(buf, "auto_sensitivity=", &val)) { + if ((val == 0) || (val == 1)) + wifi->params.auto_sensitivity = val; + else + pr_err("Invalid parameter value.\n"); + } else if (param_get_val(buf, "production_test=", &val)) { + if ((val == 0) || (val == 1)) { + if (wifi->params.production_test != val) { + if (wifi->params.production_test) + wifi->params.num_vifs = 1; + + wifi->params.production_test = val; + + uccp420wlan_reinit(); + pr_err("Re-initializing UMAC ..\n"); + } + } else + pr_err("Invalid parameter value\n"); + } else if (param_get_val(buf, "bypass_vpd=", &val)) { + if ((val == 0) || (val == 1)) { + if (wifi->params.bypass_vpd != val) + wifi->params.bypass_vpd = val; + } else + pr_err("Invalid parameter value\n"); + } else if (param_get_val(buf, "num_vifs=", &val)) { + if (val > 0 && val <= MAX_VIFS) { + if (wifi->params.num_vifs != val) { + uccp420wlan_reinit(); + pr_err("Re-initializing UMAC ..\n"); + wifi->params.num_vifs = val; + } + } + } else if (param_get_match(buf, "rf_params=")) { + conv_str_to_byte(wifi->params.rf_params, + strstr(buf, "=") + 1, + RF_PARAMS_SIZE); + } else if (param_get_val(buf, "rx_packet_mgmt_count=", &val)) { + wifi->stats.rx_packet_mgmt_count = val; + } else if (param_get_val(buf, "rx_packet_data_count=", &val)) { + wifi->stats.rx_packet_data_count = val; + } else if (param_get_val(buf, "pdout_val=", &val)) { + wifi->stats.pdout_val = val; + } else if (param_get_val(buf, "get_stats=", &val)) { + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + uccp420wlan_prog_mib_stats(); + } else if (param_get_val(buf, "max_data_size=", &val)) { + if (wifi->params.max_data_size != val) { + if ((wifi->params.max_data_size >= 2 * 1024) && + (wifi->params.max_data_size <= (12 * 1024))) { + wifi->params.max_data_size = val; + + uccp420wlan_reinit(); + pr_err("Re-initalizing UCCP420 with %ld as max data size\n", + val); + + } else + pr_err("Invalid Value for max data size: should be (2K-12K)\n"); + } + } else if (param_get_val(buf, "max_tx_cmds=", &val)) { + int max_tx_cmd_limit = 0; + + if (vht_support) + max_tx_cmd_limit = MAX_SUBFRAMES_IN_AMPDU_VHT; + else + max_tx_cmd_limit = MAX_SUBFRAMES_IN_AMPDU_HT; + + if (val >= 1 && val <= max_tx_cmd_limit) + wifi->params.max_tx_cmds = val; + else + pr_err("Please enter value between 1 and %d\n", + max_tx_cmd_limit); + } else if (param_get_val(buf, "disable_power_save=", &val)) { + if ((val == 0) || (val == 1)) { + if (val != wifi->params.disable_power_save) { + wifi->params.disable_power_save = val; + + uccp420wlan_reinit(); + pr_err("Re-initalizing UCCP420 with global powerave %s\n", + val ? "DISABLED" : "ENABLED"); + } + } + } else if (param_get_val(buf, "disable_sm_power_save=", &val)) { + if ((val == 0) || (val == 1)) { + if (val != wifi->params.disable_sm_power_save) { + wifi->params.disable_sm_power_save = val; + + uccp420wlan_reinit(); + pr_err("Re-initalizing UCCP420 with smps %s\n", + val ? "DISABLED" : "ENABLED"); + + } + } + } else if (param_get_val(buf, "uccp_num_spatial_streams=", &val)) { + if (val > 0 && val <= min(MAX_TX_STREAMS, MAX_RX_STREAMS)) { + if (val != wifi->params.uccp_num_spatial_streams) { + wifi->params.uccp_num_spatial_streams = val; + wifi->params.num_spatial_streams = val; + wifi->params.max_tx_streams = val; + wifi->params.max_rx_streams = val; + uccp420wlan_reinit(); + pr_err("Re-initalizing UCCP420 with %ld spatial streams\n", + val); + } + } else + pr_err("Invalid parameter value: Allowed Range: 1 to %d\n", + min(MAX_TX_STREAMS, MAX_RX_STREAMS)); + } else if (param_get_val(buf, "enable_early_agg_checks=", &val)) { + if ((val == 0) || (val == 1)) { + if (val != wifi->params.enable_early_agg_checks) + wifi->params.enable_early_agg_checks = val; + } else + pr_err("Invalid parameter value: Allowed: 0/1\n"); + } else if (param_get_val(buf, "antenna_sel=", &val)) { + if (val == 1 || val == 2) { + if (val != wifi->params.antenna_sel) { + wifi->params.antenna_sel = val; + uccp420wlan_reinit(); + pr_err("Re-initalizing UCCP420 with %ld antenna selection\n", + val); + } + } else + pr_err("Invalid parameter value: Allowed Values: 1 or 2\n"); + } else if (param_get_val(buf, "num_spatial_streams=", &val)) { + if (val > 0 && val <= wifi->params.uccp_num_spatial_streams) + wifi->params.num_spatial_streams = val; + else + pr_err("Invalid parameter value, should be less than or equal to uccp_num_spatial_streams\n"); + } else if (param_get_sval(buf, "mgd_mode_tx_fixed_mcs_indx=", &sval)) { + if (wifi->params.mgd_mode_tx_fixed_rate == -1) { + + int mcs_indx = wifi->params.mgd_mode_tx_fixed_mcs_indx; + + if (vht_support && (wifi->params.prod_mode_rate_flag & + ENABLE_VHT_FORMAT)) { + if ((sval >= -1) && (sval <= 9)) { + /* Get_rate will do the MCS holes + * validation + */ + mcs_indx = sval; + } else + pr_err("Invalid parameter value.\n"); + } else { + if (wifi->params.num_spatial_streams == 2) { + if ((sval >= -1) && (sval <= 15)) + mcs_indx = sval; + else + pr_err("Invalid MIMO HT MCS: %ld\n", + sval); + } + if (wifi->params.num_spatial_streams == 1) { + if ((sval >= -1) && (sval <= 7)) + mcs_indx = sval; + else + pr_err("Invalid SISO HT MCS: %ld\n", + sval); + } + } + + wifi->params.mgd_mode_tx_fixed_mcs_indx = mcs_indx; + } else + pr_err("Fixed rate other than MCS index is currently set\n"); + } else if (param_get_sval(buf, "mgd_mode_tx_fixed_rate=", &sval)) { + if (wifi->params.mgd_mode_tx_fixed_mcs_indx == -1) { + int tx_fixed_rate = wifi->params.mgd_mode_tx_fixed_rate; + + if (wifi->params.dot11g_support == 1 && + ((sval == 1) || + (sval == 2) || + (sval == 55) || + (sval == 11))) { + tx_fixed_rate = sval; + } else if ((sval == 6) || + (sval == 9) || + (sval == 12) || + (sval == 18) || + (sval == 24) || + (sval == 36) || + (sval == 48) || + (sval == 54) || + (sval == -1)) { + tx_fixed_rate = sval; + } else { + pr_err("Invalid parameter value.\n"); + return count; + } + wifi->params.mgd_mode_tx_fixed_rate = tx_fixed_rate; + } else + pr_err("MCS data rate(index) is currently set\n"); + } else if (param_get_sval(buf, "tx_fixed_mcs_indx=", &sval)) { + if (wifi->params.production_test != 1) { + pr_err("Only can be set in production mode.\n"); + goto error; + } + + if (sval == -1) { + wifi->params.tx_fixed_mcs_indx = -1; + goto error; + } + + if (wifi->params.tx_fixed_rate != -1) { + pr_err("Fixed rate other than MCS index is currently set\n"); + goto error; + } + if (vht_support && (rate & ENABLE_VHT_FORMAT)) { + if ((sval >= -1) && (sval <= 9)) { + if ((b40 == 0) && (b80 == 0) && (sval == 9)) { + pr_err("Invalid VHT MCS: 20MHZ-MCS9.\n"); + /*Reset to Default*/ + wifi->params.tx_fixed_mcs_indx = 7; + } else + wifi->params.tx_fixed_mcs_indx = sval; + } else + pr_err("Invalid parameter value.\n"); + } else if (vht_support && (rate & ENABLE_11N_FORMAT)) { + if (wifi->params.num_spatial_streams == 2) { + if ((sval >= -1) && (sval <= 15)) + wifi->params.tx_fixed_mcs_indx = sval; + else + pr_err("Invalid MIMO HT MCS: %ld\n", + sval); + } else if (wifi->params.num_spatial_streams == 1) { + if ((sval >= -1) && (sval <= 7)) + wifi->params.tx_fixed_mcs_indx = sval; + else + pr_err("Invalid SISO HT MCS: %ld\n", + sval); + } + } else + pr_err("MCS Setting is invalid for Legacy, please set prod_mode_rate_flag first.\n"); + + } else if (param_get_sval(buf, "tx_fixed_rate=", &sval)) { + if (wifi->params.production_test != 1) { + pr_err("Only can be set in production mode.\n"); + goto error; + } + + if (sval == -1) { + wifi->params.tx_fixed_rate = -1; + goto error; + } + if (wifi->params.tx_fixed_mcs_indx != -1) { + pr_err("MCS Index is currently set.\n"); + goto error; + } + + if ((wifi->params.dot11g_support == 1) && + ((sval == 1) || + (sval == 2) || + (sval == 55) || + (sval == 11))) { + wifi->params.tx_fixed_rate = sval; + } else if ((sval == 6) || + (sval == 9) || + (sval == 12) || + (sval == 18) || + (sval == 24) || + (sval == 36) || + (sval == 48) || + (sval == 54) || + (sval == -1)) { + wifi->params.tx_fixed_rate = sval; + } else { + pr_err("Invalid parameter value: tx_fixed_rate=%ld\n", + sval); + goto error; + } + } else if (param_get_val(buf, "chnl_bw=", &val)) { + if (((val == 0) || + (vht_support && (val == 2)) || + (val == 1))) { + wifi->params.chnl_bw = val; + + uccp420wlan_reinit(); + pr_err("Re-initializing UMAC ..\n"); + } else + pr_err("Invalid parameter value.\n"); + } else if (param_get_val(buf, "prod_mode_chnl_bw_40_mhz=", &val)) { + + do { + if (wifi->params.production_test != 1) { + pr_err("Can be set in only in production mode.\n"); + break; + } + + if (!((val == 0) || (val == 1))) { + pr_err("Invalid parameter value.\n"); + break; + } + + wifi->params.prod_mode_chnl_bw_40_mhz = val; + + if (!vht_support) + break; + + wifi->params.prod_mode_chnl_bw_80_mhz = 0; + } while (0); + + } else if (vht_support && + param_get_val(buf, "prod_mode_chnl_bw_80_mhz=", &val)) { + if (wifi->params.production_test == 1) { + if ((val == 0) || (val == 1)) { + wifi->params.prod_mode_chnl_bw_40_mhz = 0; + wifi->params.prod_mode_chnl_bw_80_mhz = val; + } else + pr_err("Invalid parameter value.\n"); + } else + pr_err("Can be set in only in production mode.\n"); + } else if (param_get_val(buf, "sec_ch_offset_40_plus=", &val)) { + do { + if (wifi->params.production_test != 1) { + pr_err("Can be set in only in production mode.\n"); + break; + } + + if (val == 0) { + wifi->params.sec_ch_offset_40_plus = 0; + goto error; + } + + if (!((wifi->params.prod_mode_chnl_bw_40_mhz == 1) + || (vht_support && + (wifi->params.prod_mode_chnl_bw_80_mhz == 1)) + )) { + pr_err("Can be set only when prod_mode_chnl_bw_40_mhz is set.\n"); + break; + } + + + if (wifi->params.sec_ch_offset_40_minus == 1) { + pr_err("Can be set only when sec_ch_offset_40_minus is not set\n"); + break; + } + + if (!((val == 0) || (val == 1))) { + pr_err("Invalid parameter value.\n"); + break; + } + + wifi->params.sec_ch_offset_40_plus = val; + + } while (0); + + } else if (param_get_val(buf, "sec_ch_offset_40_minus=", &val)) { + do { + if (wifi->params.production_test != 1) { + pr_err("Can be set in only in production mode.\n"); + break; + } + + if (val == 0) { + wifi->params.sec_ch_offset_40_minus = 0; + goto error; + } + + if (!((wifi->params.prod_mode_chnl_bw_40_mhz == 1) + || (vht_support && + (wifi->params.prod_mode_chnl_bw_80_mhz == 1)) + )) { + pr_err("Can be set only when prod_mode_chnl_bw_40_mhz is set.\n"); + break; + } + + + if (wifi->params.sec_ch_offset_40_plus == 1) { + pr_err("Can be set only when sec_ch_offset_40_plus is not set\n"); + break; + } + + if (!((val == 0) || (val == 1))) { + pr_err("Invalid parameter value.\n"); + break; + } + + wifi->params.sec_ch_offset_40_minus = val; + + } while (0); + + } else if (vht_support && + param_get_val(buf, "sec_40_ch_offset_80_plus=", &val)) { + do { + if (wifi->params.production_test != 1) { + pr_err("Can be set in only in production mode.\n"); + break; + } + + if (val == 0) { + wifi->params.sec_40_ch_offset_80_plus = 0; + goto error; + } + + if (!(wifi->params.prod_mode_chnl_bw_80_mhz == 1)) { + pr_err("Can be set only when prod_mode_chnl_bw_80_mhz is set\n"); + break; + } + + + if (wifi->params.sec_40_ch_offset_80_minus == 1) { + pr_err("Can be set only when sec_40_ch_offset_80_minus is not set\n"); + break; + } + + if (!((val == 0) || (val == 1))) { + pr_err("Invalid parameter value.\n"); + break; + } + + wifi->params.sec_40_ch_offset_80_plus = val; + + } while (0); + + } else if (vht_support && + (param_get_val(buf, "sec_40_ch_offset_80_minus=", &val))) { + do { + if (wifi->params.production_test != 1) { + pr_err("Can be set in only in production mode.\n"); + break; + } + + if (val == 0) { + wifi->params.sec_40_ch_offset_80_minus = 0; + goto error; + } + if (!(wifi->params.prod_mode_chnl_bw_80_mhz == 1)) { + pr_err("Can be set if prod_mode_chnl_bw_80_mhz is set\n"); + break; + } + + + if (wifi->params.sec_40_ch_offset_80_plus == 1) { + pr_err("Can be set only when sec_40_ch_offset_80_plus is not set\n"); + break; + } + + if (!((val == 0) || (val == 1))) { + pr_err("Invalid parameter value.\n"); + break; + } + + wifi->params.sec_40_ch_offset_80_minus = val; + + } while (0); + } else if (param_get_val(buf, "prod_mode_rate_flag=", &val)) { + do { + /*Only first 6 flags are defined currently*/ + if (val > 63) + pr_err("Invalid parameter value"); + + if ((val & ENABLE_VHT_FORMAT) && + (val & ENABLE_11N_FORMAT)) { + pr_err("Cannot set HT and VHT both."); + break; + } + + if ((val & ENABLE_CHNL_WIDTH_40MHZ) && + (val & ENABLE_CHNL_WIDTH_80MHZ)) { + pr_err("Cannot set 40 and 80 both."); + break; + } + + if ((wifi->params.uccp_num_spatial_streams == 1) && + (val & ENABLE_SGI) && + (val & ENABLE_GREEN_FIELD)) { + pr_err("Cannot set GreenField when SGI is enabled for SISO"); + break; + } + + wifi->params.prod_mode_rate_flag = val; + } while (0); + + } else if (param_get_val(buf, "rate_protection_type=", &val)) { + /* 0 is None, 1 is RTS/CTS, 2 is for CTS2SELF */ + if ((val == 0) || (val == 1) /*|| (val == 2)*/) + wifi->params.rate_protection_type = val; + else + pr_err("Invalid parameter value"); + } else if (param_get_val(buf, "prod_mode_rate_preamble_type=", &val)) { + /*0 is short, 1 is Long*/ + if ((val == 0) || (val == 1)) + wifi->params.prod_mode_rate_preamble_type = val; + else + pr_err("Invalid parameter value"); + } else if (param_get_val(buf, "prod_mode_stbc_enabled=", &val)) { + if (val <= 1) + wifi->params.prod_mode_stbc_enabled = val; + else + pr_err("Invalid parameter value\n"); + } else if (param_get_val(buf, "prod_mode_bcc_or_ldpc=", &val)) { + if (val <= 1) + wifi->params.prod_mode_bcc_or_ldpc = val; + else + pr_err("Invalid parameter value\n"); + } else if (param_get_val(buf, "reset_hal_params=", &val)) { + if (dev->state != STARTED) { + if (val != 1) + pr_err("Invalid parameter value\n"); + else + hal_ops.reset_hal_params(); + } else + pr_err("HAL parameters reset can be done only when all interface are down\n"); + } else if (param_get_val(buf, "vht_beamformer_enable=", &val)) { + do { + int vht_beamform_period; + + if (wifi->params.vht_beamform_enable == val) + break; + + if (!((val == VHT_BEAMFORM_ENABLE) || + (val == VHT_BEAMFORM_DISABLE))) { + pr_err("Invalid VHT Beamforming Enable value should be 1 or 0\n"); + break; + } + + wifi->params.vht_beamform_enable = val; + + /* If not associated, it will be sent upon + * association + */ + if (!wifi->params.is_associated) + break; + + if (!wifi->params.vht_beamform_support) { + pr_err("Peer doesn't support VHT Beamformee.\n"); + break; + } + + vht_beamform_period = wifi->params.vht_beamform_period; + + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + uccp420wlan_prog_vht_bform(val, vht_beamform_period); + } while (0); + + } else if (param_get_val(buf, "vht_beamformer_period=", &val)) { + + do { + int vht_beamform_enable; + + if (wifi->params.vht_beamform_enable != + VHT_BEAMFORM_ENABLE) { + pr_err("VHT Beamforming is disabled, please enable it first\n"); + break; + } + + if (wifi->params.vht_beamform_period == val) + break; + + if (!((val > 100) || (val < 10000))) { + pr_err("Invalid VHT Beamforming Period must be between 100-10000ms\n"); + break; + } + + wifi->params.vht_beamform_period = val; + + /* If not associated, it will be sent upon + * association + */ + if (!wifi->params.is_associated) + break; + + if (!wifi->params.vht_beamform_support) { + pr_err("Peer doesn't support VHT Beamformee.\n"); + break; + } + + vht_beamform_enable = wifi->params.vht_beamform_enable; + + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + uccp420wlan_prog_vht_bform(vht_beamform_enable, val); + } while (0); + + } else if (param_get_val(buf, "bg_scan_enable=", &val)) { + if (wifi->params.bg_scan_enable != val) { + if ((val == 1) || (val == 0)) { + wifi->params.bg_scan_enable = val; + + uccp420wlan_reinit(); + pr_err("Re-initializing UMAC ..\n"); + } else + pr_err("Invalid bg_scan_enable value should be 1 or 0\n"); + } + } else if (param_get_match(buf, "bg_scan_channel_list=")) { + conv_str_to_byte(wifi->params.bg_scan_channel_list, + strstr(buf, "=") + 1, + 50); + } else if (param_get_match(buf, "bg_scan_channel_flags=")) { + conv_str_to_byte(wifi->params.bg_scan_channel_flags, + strstr(buf, "=") + 1, + 50); + } else if (param_get_val(buf, "bg_scan_intval=", &val)) { + if ((val >= 1000) && (val <= 60000)) + wifi->params.bg_scan_intval = val * 1000;/* us */ + else + pr_err("Invalid bgscan duration/interval value should be between 1000 to 60000 ms.\n"); +#if 0 + /*currently not used in LMAC, so don't export to user*/ + } else if (param_get_val(buf, "bg_scan_chan_dur =", &val)) { + if ((val >= 100) && (val <= 1000)) + wifi->params.bg_scan_chan_dur = val; + else + pr_err("Invalid chan duration value should be between 100 to 1000.\n"); + } else if (param_get_val(buf, "bg_scan_serv_chan_dur =", &val)) { + if ((val >= 100) && (val <= 1000)) + wifi->params.bg_scan_serv_chan_dur = val; + else + pr_err("Invalid serv chan duration value should be between 100 to 1000.\n"); +#endif + } else if (param_get_val(buf, "bg_scan_num_channels=", &val)) { + wifi->params.bg_scan_num_channels = val; + } else if (param_get_val(buf, "nw_selection=", &val)) { + + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + if ((val == 1) || (val == 0)) { + wifi->params.nw_selection = val; + pr_err("in nw_selection\n"); + uccp420wlan_prog_nw_selection(1, vif_macs[0]); + } else + pr_err("Invalid nw selection value should be 1 or 0\n"); + } else if (param_get_val(buf, "scan_type=", &val)) { + if ((val == 0) || (val == 1)) + wifi->params.scan_type = val; + else + pr_err("Invalid scan type value %d, should be 0 or 1\n", + (unsigned int)val); + } else if (ftm && param_get_val(buf, "aux_adc_chain_id=", &val)) { + + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + memset(wifi->params.pdout_voltage, 0, + sizeof(char) * MAX_AUX_ADC_SAMPLES); + if ((val == AUX_ADC_CHAIN1) || (val == AUX_ADC_CHAIN2)) { + wifi->params.aux_adc_chain_id = val; + uccp420wlan_prog_aux_adc_chain(val); + } else + pr_err("Invalid chain id %d, should be %d or %d\n", + (unsigned int) val, + AUX_ADC_CHAIN1, + AUX_ADC_CHAIN2); + } else if (param_get_val(buf, "continuous_tx=", &val)) { + if (wifi->params.production_test != 1) { + pr_err("continuous_tx: Can be set in only in production mode.\n"); + goto error; + } + + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + if (val == 0 || val == 1) { + wifi->params.cont_tx = val; + uccp420wlan_prog_cont_tx(val); + } else + pr_err("Invalid tx_continuous parameter\n"); + } else if (param_get_val(buf, "start_prod_mode=", &val)) { + unsigned int pri_chnl_num = 0; + unsigned int freq_band = IEEE80211_BAND_5GHZ; + int center_freq = 0; + + if (wifi->params.production_test != 1) { + pr_err("start_prod_mode: Can be set in only in production mode.\n"); + goto error; + } + + if (wifi->params.init_prod) { + pr_err("Production Test is already initialized.\n"); + goto error; + } + + pri_chnl_num = val; + wifi->params.start_prod_mode = val; + tasklet_init(&dev->proc_tx_tasklet, packet_generation, + (unsigned long)dev); + if (pri_chnl_num < 15) + freq_band = IEEE80211_BAND_2GHZ; + else + freq_band = IEEE80211_BAND_5GHZ; + + center_freq = + ieee80211_channel_to_frequency(pri_chnl_num, + freq_band); + + if ((wifi->params.fw_loading == 1) && + load_fw(dev->hw)) { + pr_err("%s: Firmware loading failed\n", + dev->name); + goto error; + } + + if (!uccp420wlan_core_init(dev, ftm)) { + uccp420wlan_prog_vif_ctrl(0, + dev->if_mac_addresses[0].addr, + IF_MODE_STA_IBSS, + IF_ADD); + + proc_bss_info_changed( + dev->if_mac_addresses[0].addr, + val); + + uccp420wlan_prog_channel(pri_chnl_num, + center_freq, + 0, + 0, + /*It will be overwritten anyway*/ +#ifdef MULTI_CHAN_SUPPORT + 0, +#endif + freq_band); + skb_queue_head_init(&dev->tx.proc_tx_list[0]); + wifi->params.init_prod = 1; + dev->state = STARTED; + uccp_reinit = 0; + } else { + pr_err("RPU Initialization Failed\n"); + wifi->params.init_prod = 0; + } + + } else if (param_get_sval(buf, "stop_prod_mode=", &sval)) { + + if (!wifi->params.init_prod) { + DEBUG_LOG("Prod mode is not initialized\n"); + goto error; + } + + tasklet_kill(&dev->proc_tx_tasklet); +#if 0 + /* Todo: Enabling this causes RPU Lockup, + * need to debug + */ + uccp420wlan_prog_vif_ctrl(0, + dev->if_mac_addresses[0].addr, + IF_MODE_STA_IBSS, + IF_REM); +#endif + if (!uccp_reinit) + stop(wifi->hw); + + wifi->params.start_prod_mode = 0; + wifi->params.pkt_gen_val = 1; + wifi->params.init_prod = 0; + wifi->params.init_pkt_gen = 0; + } else if (param_get_sval(buf, "start_packet_gen=", &sval)) { + + + if (!wifi->params.init_prod) { + pr_err("NEW Production Mode is not Initialized\n"); + goto error; + } + + if (wifi->params.init_pkt_gen) { + pr_err("packet gen is already running\n"); + goto error; + } + + if (wifi->params.tx_fixed_mcs_indx == -1 && + wifi->params.tx_fixed_rate == -1) { + pr_err("Either tx_fixed_mcs_index Or tx_fixed_rate should be set, both can't be NULL.\n"); + goto error; + } + + wifi->params.init_pkt_gen = 1; + + wifi->params.pkt_gen_val = sval; + if (sval != 0) + tasklet_schedule(&dev->proc_tx_tasklet); + + } else if (param_get_sval(buf, "stop_packet_gen=", &sval)) { + + if (!wifi->params.init_prod) { + DEBUG_LOG("NEW Production Mode is not Initialized\n"); + goto error; + } + + wifi->params.pkt_gen_val = 1; + wifi->params.init_pkt_gen = 0; + tasklet_kill(&dev->proc_tx_tasklet); + } else if (param_get_val(buf, "payload_length=", &val)) { + wifi->params.payload_length = val; + } else if (param_get_sval(buf, "set_tx_power=", &sval)) { + if (wifi->params.production_test != 1 && !ftm) { + pr_err("set_tx_power: Can be set in only in FTM/production mode.\n"); + goto error; + } + + if (!wifi->params.init_prod) { + DEBUG_LOG("NEW Production Mode is not Initialized\n"); + goto error; + } + + memset(wifi->params.pdout_voltage, 0, + sizeof(char) * MAX_AUX_ADC_SAMPLES); + wifi->params.set_tx_power = sval; + uccp420wlan_prog_txpower(sval); +#ifdef PERF_PROFILING + } else if (param_get_val(buf, "driver_tput=", &val)) { + if ((val == 1) || (val == 0)) + wifi->params.driver_tput = val; + else + pr_err("Invalid driver_tput value should be 1 or 0\n"); +#endif + } else if (param_get_val(buf, "fw_loading=", &val)) { + wifi->params.fw_loading = val; + } else if (param_get_val(buf, "bt_state=", &val)) { + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + + if (val == 0 || val == 1) { + if (val != wifi->params.bt_state) { + wifi->params.bt_state = val; + uccp420wlan_prog_btinfo(val); + } + } else + pr_err("Invalid parameter value: Allowed values: 0 or 1\n"); + } else if (param_get_val(buf, "clear_stats=", &val)) { + if (dev->state != STARTED) { + pr_err("Interface is not initialized\n"); + goto error; + } + uccp420wlan_prog_clear_stats(); + } else if (param_get_val(buf, "disable_beacon_ibss=", &val)) { + if ((val == 1) || (val == 0)) + wifi->params.disable_beacon_ibss = val; + else + pr_err("Invalid driver_tput value should be 1 or 0\n"); +#ifdef DFS_TEST + } else if (param_get_val(buf, "radar=", &val)) { + if (val == 1) + radar_detected(); + else + pr_err("Invalid parameter value.\n"); +#endif + } else if (param_get_val(buf, "uccp_debug=", &val)) { + uccp_debug = val; + } else + pr_err("Invalid parameter name: %s\n", buf); +error: + return count; +} + + +static int proc_open_config(struct inode *inode, struct file *file) +{ + return single_open(file, proc_read_config, NULL); +} + + +static int proc_open_phy_stats(struct inode *inode, struct file *file) +{ + return single_open(file, proc_read_phy_stats, NULL); +} + + +static int proc_open_mac_stats(struct inode *inode, struct file *file) +{ + return single_open(file, proc_read_mac_stats, NULL); +} + + +static const struct file_operations params_fops_config = { + .open = proc_open_config, + .read = seq_read, + .llseek = seq_lseek, + .write = proc_write_config, + .release = single_release +}; + +static const struct file_operations params_fops_phy_stats = { + .open = proc_open_phy_stats, + .read = seq_read, + .llseek = seq_lseek, + .write = NULL, + .release = single_release +}; +static const struct file_operations params_fops_mac_stats = { + .open = proc_open_mac_stats, + .read = seq_read, + .llseek = seq_lseek, + .write = NULL, + .release = single_release +}; +static int proc_init(struct proc_dir_entry ***main_dir_entry) +{ + struct proc_dir_entry *entry; + int err = 0; + unsigned int i = 0; + /*2.4GHz and 5 GHz PD and TX-PWR calibration params*/ + unsigned char rf_params[RF_PARAMS_SIZE * 2]; + + strncpy(rf_params, + "1E00000000002426292A2C2E3237393F454A52576066000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F00000000002426292A2C2E3237393F454A52576066000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F000000002B2C3033373A3D44474D51575A61656B6F0808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808", + (RF_PARAMS_SIZE * 2)); + + wifi = kzalloc(sizeof(struct wifi_dev), GFP_KERNEL); + if (!wifi) { + err = -ENOMEM; + goto out; + } + + wifi->umac_proc_dir_entry = proc_mkdir("uccp420", NULL); + if (!wifi->umac_proc_dir_entry) { + pr_err("Failed to create proc dir\n"); + err = -ENOMEM; + goto proc_dir_fail; + } + + entry = proc_create("params", 0644, wifi->umac_proc_dir_entry, + ¶ms_fops_config); + if (!entry) { + pr_err("Failed to create proc entry\n"); + err = -ENOMEM; + goto proc_entry1_fail; + } + + entry = proc_create("phy_stats", 0444, wifi->umac_proc_dir_entry, + ¶ms_fops_phy_stats); + if (!entry) { + pr_err("Failed to create proc entry\n"); + err = -ENOMEM; + goto proc_entry2_fail; + } + + entry = proc_create("mac_stats", 0444, wifi->umac_proc_dir_entry, + ¶ms_fops_mac_stats); + if (!entry) { + pr_err("Failed to create proc entry\n"); + err = -ENOMEM; + goto proc_entry3_fail; + } + + /* Initialize WLAN params */ + memset(&wifi->params, 0, sizeof(struct wifi_params)); + + /* TODO: Make this a struct */ + memset(wifi->params.rf_params, 0xFF, sizeof(wifi->params.rf_params)); + conv_str_to_byte(wifi->params.rf_params, rf_params, RF_PARAMS_SIZE); + + if (!rf_params_vpd) + rf_params_vpd = wifi->params.rf_params; + + memcpy(wifi->params.rf_params_vpd, rf_params_vpd, RF_PARAMS_SIZE); + + wifi->params.is_associated = 0; + wifi->params.ed_sensitivity = -89; + wifi->params.auto_sensitivity = 1; + wifi->params.dot11a_support = 1; + wifi->params.dot11g_support = 1; + wifi->params.num_vifs = 2; + + /* Check, if required add it */ + wifi->params.tx_fixed_mcs_indx = -1; + wifi->params.tx_fixed_rate = -1; + wifi->params.num_spatial_streams = min(MAX_TX_STREAMS, MAX_RX_STREAMS); + wifi->params.uccp_num_spatial_streams = min(MAX_TX_STREAMS, + MAX_RX_STREAMS); + wifi->params.antenna_sel = 1; + + if (num_streams_vpd > 0) + wifi->params.uccp_num_spatial_streams = num_streams_vpd; + + wifi->params.enable_early_agg_checks = 1; + wifi->params.bt_state = 1; + wifi->params.mgd_mode_tx_fixed_mcs_indx = -1; + wifi->params.mgd_mode_tx_fixed_rate = -1; + if (vht_support) + wifi->params.chnl_bw = WLAN_80MHZ_OPERATION; + else + wifi->params.chnl_bw = WLAN_20MHZ_OPERATION; + wifi->params.max_tx_streams = MAX_TX_STREAMS; + wifi->params.max_rx_streams = MAX_RX_STREAMS; + wifi->params.max_data_size = 8 * 1024; + + wifi->params.vht_beamform_enable = VHT_BEAMFORM_DISABLE; + wifi->params.vht_beamform_period = 1000; /* ms */ + wifi->params.vht_beamform_support = 0; + if (vht_support) + wifi->params.max_tx_cmds = MAX_SUBFRAMES_IN_AMPDU_VHT; + else + wifi->params.max_tx_cmds = MAX_SUBFRAMES_IN_AMPDU_HT; + wifi->params.disable_power_save = 0; + wifi->params.disable_sm_power_save = 0; + wifi->params.rate_protection_type = 0; /* Disable protection by def */ + wifi->params.prod_mode_rate_preamble_type = 1; /* LONG */ + wifi->params.prod_mode_stbc_enabled = 0; + wifi->params.prod_mode_bcc_or_ldpc = 0; + wifi->params.bg_scan_enable = 0; + memset(wifi->params.bg_scan_channel_list, 0, 50); + memset(wifi->params.bg_scan_channel_flags, 0, 50); + + if (wifi->params.dot11g_support) { + wifi->params.bg_scan_num_channels = 3; + + wifi->params.bg_scan_channel_list[i] = 1; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + + wifi->params.bg_scan_channel_list[i] = 6; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + + wifi->params.bg_scan_channel_list[i] = 11; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + } + + if (wifi->params.dot11a_support) { + wifi->params.bg_scan_num_channels += 4; + + wifi->params.bg_scan_channel_list[i] = 36; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + + wifi->params.bg_scan_channel_list[i] = 40; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + + wifi->params.bg_scan_channel_list[i] = 44; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + + wifi->params.bg_scan_channel_list[i] = 48; + wifi->params.bg_scan_channel_flags[i++] = ACTIVE; + } + + wifi->params.disable_beacon_ibss = 0; + wifi->params.pkt_gen_val = -1; + wifi->params.init_pkt_gen = 0; + wifi->params.payload_length = 4000; + wifi->params.start_prod_mode = 0; + wifi->params.init_prod = 0; + wifi->params.bg_scan_intval = 5000 * 1000; /* Once in 5 seconds */ + wifi->params.bg_scan_chan_dur = 300; /* Channel spending time */ + wifi->params.bg_scan_serv_chan_dur = 100; /* Oper chan spending time */ + wifi->params.nw_selection = 0; + wifi->params.scan_type = ACTIVE; + wifi->params.hw_scan_status = HW_SCAN_STATUS_NONE; + wifi->params.fw_loading = 1; + + **main_dir_entry = wifi->umac_proc_dir_entry; + return err; + +proc_entry3_fail: + remove_proc_entry("phy_stats", wifi->umac_proc_dir_entry); +proc_entry2_fail: + remove_proc_entry("params", wifi->umac_proc_dir_entry); +proc_entry1_fail: + remove_proc_entry("uccp420", NULL); +proc_dir_fail: + kfree(wifi); +out: + return err; + +} + +static void proc_exit(void) +{ + /* This is created in hal_init */ + remove_proc_entry("hal_stats", wifi->umac_proc_dir_entry); + remove_proc_entry("mac_stats", wifi->umac_proc_dir_entry); + remove_proc_entry("phy_stats", wifi->umac_proc_dir_entry); + remove_proc_entry("params", wifi->umac_proc_dir_entry); + remove_proc_entry("uccp420", NULL); + kfree(wifi); +} + + +int _uccp420wlan_80211if_init(struct proc_dir_entry **main_dir_entry) +{ + int error; + + error = proc_init(&main_dir_entry); + if (error) + return error; + + error = uccp420wlan_init(); + + return error; +} + +void _uccp420wlan_80211if_exit(void) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)wifi->hw->priv; + + if (wifi && wifi->hw) { + /* We can safely call stop as mac80211 + * will not call stop because of new + * production mode. + */ + if (dev && wifi->params.init_prod) + stop(wifi->hw); + uccp420wlan_exit(); + proc_exit(); + } +} diff --git a/drivers/net/wireless/uccp420wlan/src/core.c b/drivers/net/wireless/uccp420wlan/src/core.c new file mode 100644 index 00000000000000..22e0e22775b697 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/core.c @@ -0,0 +1,1314 @@ +/* + * File Name : core.c + * + * This file contains the source functions for UMAC core + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "core.h" + +#define UMAC_PRINT(fmt, args...) pr_debug(fmt, ##args) + +#define UCCP_DEBUG_CORE(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_CORE) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_RX(fmt, ...) \ +do { \ + if ((uccp_debug & UCCP_DEBUG_RX) && net_ratelimit()) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + + +#define UCCP_DEBUG_DUMP_RX(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_DUMP_RX) \ + print_hex_dump(KERN_DEBUG, fmt, ##__VA_ARGS__); \ +} while (0) + + +#define DUMP_RX (uccp_debug & UCCP_DEBUG_DUMP_RX) + +spinlock_t tsf_lock; + +unsigned char bss_addr[6] = {72, 14, 29, 35, 31, 52}; +static int is_robust_mgmt(struct sk_buff *skb) +{ + /*TODO: mmie struture not being used now. Uncomment once in use */ +#if 0 + struct ieee80211_mmie *mmie; +#endif + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + + if (skb->len < 24) + return 0; + + if (!((hdr->frame_control & IEEE80211_FCTL_FTYPE) == + IEEE80211_FTYPE_MGMT)) + return 0; + + /* Not a BIP frame */ + if (((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_DISASSOC) || + ((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_DEAUTH) || + ((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_ACTION)) { + if (((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_ACTION)) { + u8 *category; + + category = ((u8 *) hdr) + 24; + + if (*category == WLAN_CATEGORY_PUBLIC || + *category == WLAN_CATEGORY_HT || + *category == WLAN_CATEGORY_SELF_PROTECTED || + *category == WLAN_CATEGORY_VENDOR_SPECIFIC) + return 0; + } + } else { + return 0; + } + + if ((hdr->addr1[0] & 0x01)) { +#if 0 + if (skb->len < (24 + sizeof(*mmie))) + return 0; + + mmie = (struct ieee80211_mmie *)(skb->data + skb->len - + sizeof(*mmie)); + if (mmie->element_id != 76 || mmie->length != sizeof(*mmie) - 2) + /* #define WLAN_EID_MMIE = 76;*/ + return 0; +#endif + } else { + if (hdr->frame_control & IEEE80211_FCTL_PROTECTED) + return 0; + } + return 1; +} + + +int wait_for_scan_abort(struct mac80211_dev *dev) +{ + int count; + + count = 0; + +check_scan_abort_complete: + if (!dev->scan_abort_done && (count < SCAN_ABORT_TIMEOUT_TICKS)) { + current->state = TASK_INTERRUPTIBLE; + + if (0 == schedule_timeout(1)) + count++; + + goto check_scan_abort_complete; + } + + if (!dev->scan_abort_done) { + UMAC_PRINT("%s-UMAC: No SCAN_ABORT_DONE after %ld ticks\n", + dev->name, SCAN_ABORT_TIMEOUT_TICKS); + return -1; + } + + UCCP_DEBUG_SCAN("%s-UMAC: Scan abort complete after %d timer ticks\n", + dev->name, + count); + + return 0; + +} + +int wait_for_cancel_hw_roc(struct mac80211_dev *dev) +{ + int count = 0; + +check_cancel_hw_roc_complete: + if (!dev->cancel_hw_roc_done && (count < CANCEL_HW_ROC_TIMEOUT_TICKS)) { + current->state = TASK_INTERRUPTIBLE; + if (0 == schedule_timeout(1)) + count++; + goto check_cancel_hw_roc_complete; + } + + if (!dev->cancel_hw_roc_done) { + pr_err("%s-UMAC: Warning: Didn't get CANCEL_HW_ROC_DONE after %ld timer ticks\n", + dev->name, + CANCEL_HW_ROC_TIMEOUT_TICKS); + return -1; + } + + UCCP_DEBUG_ROC("%s-UMAC: Cancel HW RoC complet after %d timer ticks\n", + dev->name, + count); + + return 0; + +} + +int wait_for_channel_prog_complete(struct mac80211_dev *dev) +{ + int count; + + count = 0; + +check_ch_prog_complete: + if (!dev->chan_prog_done && (count < CH_PROG_TIMEOUT_TICKS)) { + current->state = TASK_INTERRUPTIBLE; + + if (0 == schedule_timeout(1)) + count++; + + goto check_ch_prog_complete; + } + + if (!dev->chan_prog_done) { + UMAC_PRINT("%s-UMAC: No channel prog done after %ld ticks\n", + dev->name, CH_PROG_TIMEOUT_TICKS); + return -1; + } + + UCCP_DEBUG_CORE("%s-UMAC: Channel Prog Complete after %d timer ticks\n", + dev->name, count); + + return 0; + +} + +int wait_for_tx_queue_flush_complete(struct mac80211_dev *dev, + unsigned int queue) +{ + int count = 0; + +check_tx_queue_flush_complete: + if (dev->tx.outstanding_tokens[queue] && + (count < QUEUE_FLUSH_TIMEOUT_TICKS)) { + current->state = TASK_INTERRUPTIBLE; + if (0 == schedule_timeout(1)) + count++; + goto check_tx_queue_flush_complete; + } + + if (dev->tx.outstanding_tokens[queue]) { + pr_err("%s-UMAC: Warning: Tx Queue %d flush failed pending: %d after %ld timer ticks\n", + dev->name, + queue, + dev->tx.outstanding_tokens[queue], + QUEUE_FLUSH_TIMEOUT_TICKS); + return -1; + } + + UCCP_DEBUG_ROC("%s-UMAC:", dev->name); + UCCP_DEBUG_ROC("Flushed Tx queue %d successfully in %d timer ticks\n", + queue, + count); + + return 0; + +} + + +int wait_for_reset_complete(struct mac80211_dev *dev) +{ + int count; + + count = 0; + +check_reset_complete: + if (!dev->reset_complete && (count < RESET_TIMEOUT_TICKS)) { + current->state = TASK_INTERRUPTIBLE; + + if (0 == schedule_timeout(1)) + count++; + + goto check_reset_complete; + } + + if (!dev->reset_complete) { + UMAC_PRINT("%s-UMAC: No reset complete after %ld ticks\n", + dev->name, RESET_TIMEOUT_TICKS); + return -1; + } + + UMAC_PRINT("%s-UMAC: Reset complete after %d timer ticks\n", + dev->name, count); + return 0; + +} + + +#ifdef PERF_PROFILING +static void driver_tput_timer_expiry(unsigned long data) +{ + struct umac_vif *uvif = (struct umac_vif *)data; + + if (uvif->dev->stats->rx_packet_data_count) { + pr_info("The RX packets/sec are: %d\n", + uvif->dev->stats->rx_packet_data_count); + + uvif->dev->stats->rx_packet_data_count = 0; + } + + if (uvif->dev->stats->tx_cmd_send_count_single) { + pr_info("The TX packets/sec single are: %d\n", + uvif->dev->stats->tx_cmd_send_count_single); + + uvif->dev->stats->tx_cmd_send_count_single = 0; + } + + if (uvif->dev->stats->tx_cmd_send_count_multi) { + pr_info("The TX packets/sec multi are: %d\n", + uvif->dev->stats->tx_cmd_send_count_multi); + + uvif->dev->stats->tx_cmd_send_count_multi = 0; + } + + mod_timer(&uvif->driver_tput_timer, jiffies + msecs_to_jiffies(1000)); + +} +#endif + +void proc_bss_info_changed(unsigned char *mac_addr, int value) +{ + int temp = 0, i = 0, j = 0; + + get_random_bytes(&j, sizeof(j)); + for (i = 5; i > 0; i--) { + j = j % (i+1); + temp = bss_addr[i]; + bss_addr[i] = bss_addr[j]; + bss_addr[j] = temp; + } + uccp420wlan_prog_vif_bssid(0, mac_addr, bss_addr); + +} + +void packet_generation(unsigned long data) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)data; + unsigned char *mac_addr = dev->if_mac_addresses[0].addr; + struct ieee80211_hdr hdr = {0}; + struct sk_buff *skb; + unsigned char broad_addr[6] = {0xff, 0xff, 0xff, + 0xff, 0xff, 0xff}; + u16 hdrlen = 26; + + /*LOOP_START*/ + /*PREPARE_SKB_LIST and SEND*/ + + skb = alloc_skb(dev->params->payload_length + hdrlen, + GFP_ATOMIC); + ether_addr_copy(hdr.addr1, broad_addr); + ether_addr_copy(hdr.addr2, mac_addr); + ether_addr_copy(hdr.addr3, bss_addr); + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_DATA); + memcpy(skb_put(skb, hdrlen), &hdr, hdrlen); + memset(skb_put(skb, dev->params->payload_length), 0xAB, + dev->params->payload_length); + + /*LOOP_END*/ + skb_queue_tail(&dev->tx.proc_tx_list[0], skb); + uccp420wlan_proc_tx(); + +} + +static void vif_bcn_timer_expiry(unsigned long data) +{ + struct umac_vif *uvif = (struct umac_vif *)data; + struct sk_buff *skb, *temp; + struct sk_buff_head bcast_frames; + + if (uvif->vif->bss_conf.enable_beacon == false) + return; + + if (uvif->vif->type == NL80211_IFTYPE_AP) { + temp = skb = ieee80211_beacon_get(uvif->dev->hw, uvif->vif); + + if (!skb) { + /* No beacon, so dont transmit braodcast frames*/ + goto reschedule_timer; + } + + skb_queue_head_init(&bcast_frames); + skb->priority = 1; + skb_queue_tail(&bcast_frames, skb); + + skb = ieee80211_get_buffered_bc(uvif->dev->hw, uvif->vif); + + while (skb) { + /* Hack: skb->priority is used to indicate more + * frames + */ + skb->priority = 1; + skb_queue_tail(&bcast_frames, skb); + temp = skb; + skb = ieee80211_get_buffered_bc(uvif->dev->hw, + uvif->vif); + } + + if (temp) + temp->priority = 0; + + spin_lock_bh(&uvif->dev->bcast_lock); + + while ((skb = skb_dequeue(&bcast_frames))) { + /* For a Beacon queue we will let the frames pass + * through irrespective of the current channel context. + * The FW will take care of transmitting them in the + * appropriate channel. Hence pass the interfaces + * channel context instead of the actual current channel + * context. + */ + uccp420wlan_tx_frame(skb, + NULL, + uvif->dev, +#ifdef MULTI_CHAN_SUPPORT + uvif->chanctx->index, +#endif + true); + } + + spin_unlock_bh(&uvif->dev->bcast_lock); + + if (uvif->vif->csa_active) { + if (ieee80211_csa_is_complete(uvif->vif)) + ieee80211_csa_finish(uvif->vif); + } + } else { + skb = ieee80211_beacon_get(uvif->dev->hw, uvif->vif); + + if (!skb) + goto reschedule_timer; + + /* For a Beacon queue we will let the frames pass through + * irrespective of the current channel context. The FW will take + * care of transmitting them in the appropriate channel. Hence + * pass the interfaces channel context instead of the actual + * current channel context. + */ + uccp420wlan_tx_frame(skb, + NULL, + uvif->dev, +#ifdef MULTI_CHAN_SUPPORT + uvif->chanctx->index, +#endif + true); + + } +reschedule_timer: + return; + +} + + +int uccp420wlan_core_init(struct mac80211_dev *dev, unsigned int ftm) +{ + + UCCP_DEBUG_CORE("%s-UMAC: Init called\n", dev->name); + spin_lock_init(&tsf_lock); + uccp420wlan_lmac_if_init(dev, dev->name); + + /* Enable the LMAC, set defaults and initialize TX */ + dev->reset_complete = 0; + + UMAC_PRINT("%s-UMAC: Reset (ENABLE)\n", dev->name); + + if (hal_ops.start()) + goto lmac_deinit; + + if (hal_ops.init_bufs(NUM_TX_DESCS, + NUM_RX_BUFS_2K, + NUM_RX_BUFS_12K, + dev->params->max_data_size) < 0) + goto hal_stop; + + if (ftm) + uccp420wlan_prog_reset(LMAC_ENABLE, LMAC_MODE_FTM); + else + uccp420wlan_prog_reset(LMAC_ENABLE, LMAC_MODE_NORMAL); + + if (wait_for_reset_complete(dev) < 0) + goto hal_deinit_bufs; + + + uccp420wlan_prog_btinfo(dev->params->bt_state); + uccp420wlan_prog_global_cfg(512, /* Rx MSDU life time in msecs */ + 512, /* Tx MSDU life time in msecs */ + dev->params->ed_sensitivity, + dev->params->auto_sensitivity, + dev->params->rf_params); + + uccp420wlan_prog_txpower(dev->txpower); + uccp420wlan_tx_init(dev); + + return 0; +hal_deinit_bufs: + hal_ops.deinit_bufs(); +hal_stop: + hal_ops.stop(); +lmac_deinit: + uccp420wlan_lmac_if_deinit(); + return -1; +} + + +void uccp420wlan_core_deinit(struct mac80211_dev *dev, unsigned int ftm) +{ + UCCP_DEBUG_CORE("%s-UMAC: De-init called\n", dev->name); + + /* De initialize tx and disable LMAC*/ + uccp420wlan_tx_deinit(dev); + + /* Disable the LMAC */ + dev->reset_complete = 0; + UMAC_PRINT("%s-UMAC: Reset (DISABLE)\n", dev->name); + + if (ftm) + uccp420wlan_prog_reset(LMAC_DISABLE, LMAC_MODE_FTM); + else + uccp420wlan_prog_reset(LMAC_DISABLE, LMAC_MODE_NORMAL); + + + wait_for_reset_complete(dev); + + uccp420_lmac_if_free_outstnding(); + + hal_ops.stop(); + hal_ops.deinit_bufs(); + + uccp420wlan_lmac_if_deinit(); +} + + +void uccp420wlan_vif_add(struct umac_vif *uvif) +{ + unsigned int type; + struct ieee80211_conf *conf = &uvif->dev->hw->conf; + + UCCP_DEBUG_CORE("%s-UMAC: Add VIF %d Type = %d\n", + uvif->dev->name, + uvif->vif_index, + uvif->vif->type); + + uvif->config.atim_window = uvif->config.bcn_lost_cnt = + uvif->config.aid = 0; + + switch (uvif->vif->type) { + case NL80211_IFTYPE_STATION: + type = IF_MODE_STA_BSS; + uvif->noa_active = 0; + skb_queue_head_init(&uvif->noa_que); + spin_lock_init(&uvif->noa_que.lock); + break; + case NL80211_IFTYPE_ADHOC: + type = IF_MODE_STA_IBSS; + init_timer(&uvif->bcn_timer); + uvif->bcn_timer.data = (unsigned long)uvif; + uvif->bcn_timer.function = vif_bcn_timer_expiry; + spin_lock_init(&uvif->noa_que.lock); + break; + case NL80211_IFTYPE_AP: + type = IF_MODE_AP; + init_timer(&uvif->bcn_timer); + uvif->bcn_timer.data = (unsigned long)uvif; + uvif->bcn_timer.function = vif_bcn_timer_expiry; + spin_lock_init(&uvif->noa_que.lock); + break; + default: + WARN_ON(1); + return; + } + +#ifdef PERF_PROFILING + /* Timer to print stats for tput*/ + init_timer(&uvif->driver_tput_timer); + uvif->driver_tput_timer.data = (unsigned long)uvif; + uvif->driver_tput_timer.function = driver_tput_timer_expiry; +#endif + uccp420wlan_prog_vif_ctrl(uvif->vif_index, + uvif->vif->addr, + type, + IF_ADD); + + /* Reprogram retry counts */ + uccp420wlan_prog_short_retry(uvif->vif_index, uvif->vif->addr, + conf->short_frame_max_tx_count); + + uccp420wlan_prog_long_retry(uvif->vif_index, uvif->vif->addr, + conf->long_frame_max_tx_count); + + if (uvif->vif->type == NL80211_IFTYPE_AP) { + /* Program the EDCA params */ + unsigned int queue; + unsigned int aifs; + unsigned int txop; + unsigned int cwmin; + unsigned int cwmax; + unsigned int uapsd; + + for (queue = 0; queue < 4; queue++) { + aifs = uvif->config.edca_params[queue].aifs; + txop = uvif->config.edca_params[queue].txop; + cwmin = uvif->config.edca_params[queue].cwmin; + cwmax = uvif->config.edca_params[queue].cwmax; + uapsd = uvif->config.edca_params[queue].uapsd; + + uccp420wlan_prog_txq_params(uvif->vif_index, + uvif->vif->addr, + queue, + aifs, + txop, + cwmin, + cwmax, + uapsd); + } + } +} + + +void uccp420wlan_vif_remove(struct umac_vif *uvif) +{ + struct sk_buff *skb; + unsigned int type; + + UCCP_DEBUG_CORE("%s-UMAC: Remove VIF %d called\n", + uvif->dev->name, + uvif->vif_index); + + switch (uvif->vif->type) { + case NL80211_IFTYPE_STATION: + type = IF_MODE_STA_BSS; + break; + case NL80211_IFTYPE_ADHOC: + type = IF_MODE_STA_IBSS; + del_timer(&uvif->bcn_timer); + break; + case NL80211_IFTYPE_AP: + type = IF_MODE_AP; + del_timer(&uvif->bcn_timer); + break; + default: + WARN_ON(1); + return; + } + +#ifdef PERF_PROFILING + del_timer(&uvif->driver_tput_timer); +#endif + + spin_lock_bh(&uvif->noa_que.lock); + + while ((skb = __skb_dequeue(&uvif->noa_que))) + dev_kfree_skb(skb); + + spin_unlock_bh(&uvif->noa_que.lock); + + uccp420wlan_prog_vif_ctrl(uvif->vif_index, + uvif->vif->addr, + type, + IF_REM); + +} + + +void uccp420wlan_vif_set_edca_params(unsigned short queue, + struct umac_vif *uvif, + struct edca_params *params, + unsigned int vif_active) +{ + switch (queue) { + case 0: + queue = 3; /* Voice */ + break; + case 1: + queue = 2; /* Video */ + break; + case 2: + queue = 1; /* Best effort */ + break; + case 3: + queue = 0; /* Back groud */ + break; + } + + UCCP_DEBUG_CORE("%s-UMAC:Set EDCA params for VIF %d,", + uvif->dev ? uvif->dev->name : 0, uvif->vif_index); + UCCP_DEBUG_CORE(" Values: %d, %d, %d, %d, %d\n", + queue, params->aifs, params->txop, + params->cwmin, params->cwmax); + + if (uvif->dev->params->production_test == 0) { + /* arbitration interframe space [0..255] */ + uvif->config.edca_params[queue].aifs = params->aifs; + + /* maximum burst time in units of 32 usecs, 0 meaning disabled*/ + uvif->config.edca_params[queue].txop = params->txop; + + /* minimum contention window in units of 2^n-1 */ + uvif->config.edca_params[queue].cwmin = params->cwmin; + + /* maximum contention window in units of 2^n-1 */ + uvif->config.edca_params[queue].cwmax = params->cwmax; + uvif->config.edca_params[queue].uapsd = params->uapsd; + } else { + uvif->config.edca_params[queue].aifs = 3; + uvif->config.edca_params[queue].txop = 0; + uvif->config.edca_params[queue].cwmin = 0; + uvif->config.edca_params[queue].cwmax = 0; + uvif->config.edca_params[queue].uapsd = 0; + } + + /* For the AP case, EDCA params are set before ADD interface is called. + * Since this is not supported, we simply store the params and program + * them to the LMAC after the interface is added + */ + if (!vif_active) + return; + + /* Program the txq parameters into the LMAC */ + uccp420wlan_prog_txq_params(uvif->vif_index, + uvif->vif->addr, + queue, + params->aifs, + params->txop, + params->cwmin, + params->cwmax, + params->uapsd); + +} + + +void uccp420wlan_vif_bss_info_changed(struct umac_vif *uvif, + struct ieee80211_bss_conf *bss_conf, + unsigned int changed) +{ + unsigned int bcn_int = 0; + unsigned long bcn_tim_val = 0; + unsigned int caps = 0; + int center_freq = 0; + int chan = 0; + unsigned int bform_enable = 0; + unsigned int bform_per = 0; + + UCCP_DEBUG_CORE("%s-CORE: BSS INFO changed %d, %d, %d\n", + uvif->dev->name, uvif->vif_index, uvif->vif->type, changed); + + + if (changed & BSS_CHANGED_BSSID) + uccp420wlan_prog_vif_bssid(uvif->vif_index, uvif->vif->addr, + (unsigned char *)bss_conf->bssid); + + if (changed & BSS_CHANGED_BASIC_RATES) { + if (bss_conf->basic_rates) + uccp420wlan_prog_vif_basic_rates(uvif->vif_index, + uvif->vif->addr, + bss_conf->basic_rates); + else + uccp420wlan_prog_vif_basic_rates(uvif->vif_index, + uvif->vif->addr, + 0x153); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + unsigned int queue = 0; + unsigned int aifs = 0; + unsigned int txop = 0; + unsigned int cwmin = 0; + unsigned int cwmax = 0; + unsigned int uapsd = 0; + + uccp420wlan_prog_vif_short_slot(uvif->vif_index, + uvif->vif->addr, + bss_conf->use_short_slot); + + for (queue = 0; queue < WLAN_AC_MAX_CNT; queue++) { + aifs = uvif->config.edca_params[queue].aifs; + txop = uvif->config.edca_params[queue].txop; + cwmin = uvif->config.edca_params[queue].cwmin; + cwmax = uvif->config.edca_params[queue].cwmax; + uapsd = uvif->config.edca_params[queue].uapsd; + + if (uvif->config.edca_params[queue].cwmin != 0) + uccp420wlan_prog_txq_params(uvif->vif_index, + uvif->vif->addr, + queue, + aifs, + txop, + cwmin, + cwmax, + uapsd); + } + } + + switch (uvif->vif->type) { + case NL80211_IFTYPE_STATION: + if (changed & BSS_CHANGED_ASSOC) { + center_freq = bss_conf->chandef.chan->center_freq; + chan = ieee80211_frequency_to_channel(center_freq); + bform_enable = uvif->dev->params->vht_beamform_enable; + bform_per = uvif->dev->params->vht_beamform_period; + + if (bss_conf->assoc) { + UCCP_DEBUG_CORE("%s-CORE: AID %d,", + uvif->dev->name, bss_conf->aid); + UCCP_DEBUG_CORE(" CAPS 0x%04x\n", + bss_conf->assoc_capability | + (bss_conf->qos << 9)); + + uccp420wlan_prog_vif_conn_state(uvif->vif_index, + uvif->vif->addr, + STA_CONN); + + uccp420wlan_prog_vif_aid(uvif->vif_index, + uvif->vif->addr, + bss_conf->aid); + + uccp420wlan_prog_vif_op_channel(uvif->vif_index, + uvif->vif->addr, + chan); + + caps = (bss_conf->assoc_capability | + (bss_conf->qos << 9)); + + uccp420wlan_prog_vif_assoc_cap(uvif->vif_index, + uvif->vif->addr, + caps); + + if (uvif->dev->params->vht_beamform_support) + uccp420wlan_prog_vht_bform(bform_enable, + bform_per); + + uvif->noa_active = 0; + uvif->dev->params->is_associated = 1; + +#ifdef PERF_PROFILING + mod_timer(&uvif->driver_tput_timer, + jiffies + msecs_to_jiffies(1000)); +#endif + } else { + uvif->dev->params->is_associated = 0; + + uccp420wlan_prog_vif_conn_state(uvif->vif_index, + uvif->vif->addr, + STA_DISCONN); + + uccp420wlan_prog_vht_bform(VHT_BEAMFORM_DISABLE, + bform_per); + uvif->dev->params-> + sync[uvif->vif_index].status = 0; + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + uccp420wlan_prog_vif_beacon_int(uvif->vif_index, + uvif->vif->addr, + bss_conf->beacon_int); + + } + + if (changed & BSS_CHANGED_BEACON_INFO) { + uccp420wlan_prog_vif_dtim_period(uvif->vif_index, + uvif->vif->addr, + bss_conf->dtim_period); + + } + + break; + case NL80211_IFTYPE_ADHOC: + if (changed & BSS_CHANGED_BEACON_ENABLED) { + if (uvif->vif->bss_conf.enable_beacon == true) { + + bcn_int = bss_conf->beacon_int; + bcn_tim_val = msecs_to_jiffies(bcn_int - 10); + + mod_timer(&uvif->bcn_timer, + jiffies + bcn_tim_val); + } else { + del_timer(&uvif->bcn_timer); + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + bcn_int = bss_conf->beacon_int; + bcn_tim_val = msecs_to_jiffies(bcn_int - 10); + + if (uvif->vif->bss_conf.enable_beacon == true) { + mod_timer(&uvif->bcn_timer, + jiffies + bcn_tim_val); + + uccp420wlan_prog_vif_beacon_int(uvif->vif_index, + uvif->vif->addr, + bcn_int); + } + } + + break; + case NL80211_IFTYPE_AP: + if (changed & BSS_CHANGED_BEACON_ENABLED) { + if (uvif->vif->bss_conf.enable_beacon == true) { + bcn_int = uvif->vif->bss_conf.beacon_int; + bcn_tim_val = msecs_to_jiffies(bcn_int - 10); + + mod_timer(&uvif->bcn_timer, + jiffies + bcn_tim_val); + + } else { + del_timer(&uvif->bcn_timer); + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + bcn_int = bss_conf->beacon_int; + bcn_tim_val = msecs_to_jiffies(bcn_int - 10); + + if (uvif->vif->bss_conf.enable_beacon == true) { + mod_timer(&uvif->bcn_timer, + jiffies + bcn_tim_val); + + uccp420wlan_prog_vif_beacon_int(uvif->vif_index, + uvif->vif->addr, + bcn_int); + } + } + + break; + default: + WARN_ON(1); + return; + } + +} + + +void uccp420wlan_reset_complete(char *lmac_version, void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + memcpy(dev->stats->uccp420_lmac_version, lmac_version, 5); + dev->stats->uccp420_lmac_version[5] = '\0'; + dev->reset_complete = 1; +} + + +void uccp420wlan_mib_stats(struct umac_event_mib_stats *mib_stats, + void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + memcpy(&dev->stats->ed_cnt, + &mib_stats->ed_cnt, + sizeof(struct umac_event_mib_stats)); + +} + +void uccp420wlan_mac_stats(struct umac_event_mac_stats *mac_stats, + void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + /* TX related */ + dev->stats->roc_start = mac_stats->roc_start; + dev->stats->roc_stop = mac_stats->roc_stop; + dev->stats->roc_complete = mac_stats->roc_complete; + dev->stats->roc_stop_complete = mac_stats->roc_stop_complete; + dev->stats->tx_cmd_cnt = mac_stats->tx_cmd_cnt; + dev->stats->tx_done_cnt = mac_stats->tx_done_cnt; + dev->stats->tx_edca_trigger_cnt = mac_stats->tx_edca_trigger_cnt; + dev->stats->tx_edca_isr_cnt = mac_stats->tx_edca_isr_cnt; + dev->stats->tx_start_cnt = mac_stats->tx_start_cnt; + dev->stats->tx_abort_cnt = mac_stats->tx_abort_cnt; + dev->stats->tx_abort_isr_cnt = mac_stats->tx_abort_isr_cnt; + dev->stats->tx_underrun_cnt = mac_stats->tx_underrun_cnt; + dev->stats->tx_rts_cnt = mac_stats->tx_rts_cnt; + dev->stats->tx_ampdu_cnt = mac_stats->tx_ampdu_cnt; + dev->stats->tx_mpdu_cnt = mac_stats->tx_mpdu_cnt; + + /* RX related */ + dev->stats->rx_isr_cnt = mac_stats->rx_isr_cnt; + dev->stats->rx_ack_cts_to_cnt = mac_stats->rx_ack_cts_to_cnt; + dev->stats->rx_cts_cnt = mac_stats->rx_cts_cnt; + dev->stats->rx_ack_resp_cnt = mac_stats->rx_ack_resp_cnt; + dev->stats->rx_ba_resp_cnt = mac_stats->rx_ba_resp_cnt; + dev->stats->rx_fail_in_ba_bitmap_cnt = + mac_stats->rx_fail_in_ba_bitmap_cnt; + dev->stats->rx_circular_buffer_free_cnt = + mac_stats->rx_circular_buffer_free_cnt; + dev->stats->rx_mic_fail_cnt = mac_stats->rx_mic_fail_cnt; + + /* HAL related */ + dev->stats->hal_cmd_cnt = mac_stats->hal_cmd_cnt; + dev->stats->hal_event_cnt = mac_stats->hal_event_cnt; + dev->stats->hal_ext_ptr_null_cnt = mac_stats->hal_ext_ptr_null_cnt; +} +void uccp420wlan_rf_calib_data(struct umac_event_rf_calib_data *rf_data, + void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + if (rf_data->rf_calib_data_length > MAX_RF_CALIB_DATA) { + printk_once("%s: RF calib data exceeded the max size: %d\n", + __func__, + MAX_RF_CALIB_DATA); + return; + } + dev->stats->rf_calib_data_length = rf_data->rf_calib_data_length; + memset(dev->stats->rf_calib_data, 0x00, + MAX_RF_CALIB_DATA); + memcpy(dev->stats->rf_calib_data, rf_data->rf_calib_data, + rf_data->rf_calib_data_length); +} +void uccp420wlan_noa_event(int event, struct umac_event_noa *noa, void *context, + struct sk_buff *skb) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + struct ieee80211_vif *vif; + struct umac_vif *uvif; + bool transmit = false; +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx = -1; +#endif + + rcu_read_lock(); + + vif = (struct ieee80211_vif *)rcu_dereference(dev->vifs[noa->if_index]); + + if (vif == NULL) { + rcu_read_unlock(); + return; + } + + uvif = (struct umac_vif *)vif->drv_priv; + + spin_lock_bh(&uvif->noa_que.lock); + + if (event == FROM_TX) { + if (uvif->noa_active) { + if (!uvif->noa_tx_allowed || skb_peek(&uvif->noa_que)) + __skb_queue_tail(&uvif->noa_que, skb); + else + transmit = true; + } else + transmit = true; + } else if (event == FROM_TX_DONE) { + if (uvif->noa_active && uvif->noa_tx_allowed) { + skb = __skb_dequeue(&uvif->noa_que); + + if (skb) + transmit = true; + } + } else { /* event = FROM_EVENT_NOA */ + + uvif->noa_active = noa->noa_active; + + if (uvif->noa_active) { + UCCP_DEBUG_CORE("%s: noa active = %d, ", + dev->name, noa->noa_active); + UCCP_DEBUG_CORE("ap_present = %d\n", + noa->ap_present); + + uvif->noa_tx_allowed = noa->ap_present; + + if (uvif->noa_tx_allowed) { + skb = __skb_dequeue(&uvif->noa_que); + if (skb) + transmit = true; + } + } else { + UCCP_DEBUG_CORE("%s: noa active = %d\n", + dev->name, noa->noa_active); + + uvif->noa_tx_allowed = 1; + + /* Can be done in a better way. For now, just flush the + * NoA Queue + */ + while ((skb = __skb_dequeue(&uvif->noa_que))) + dev_kfree_skb_any(skb); + } + } + + spin_unlock_bh(&uvif->noa_que.lock); + + rcu_read_unlock(); + + if (transmit) { +#ifdef MULTI_CHAN_SUPPORT + spin_lock_bh(&dev->chanctx_lock); + curr_chanctx_idx = dev->curr_chanctx_idx; + spin_unlock_bh(&dev->chanctx_lock); +#endif + uccp420wlan_tx_frame(skb, + NULL, + dev, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + false); + } +} + +#if 0 +/* Beacon Time Stamp */ +static unsigned int get_real_ts2(unsigned int t2, unsigned int delta) +{ + unsigned int td = 0; + unsigned int clocks = 0; + unsigned int clock_mask = 0, tck_num = 0, tck_denom = 0; + + if (get_evt_timer_freq) { + get_evt_timer_freq(&clock_mask, &tck_num, &tck_denom); + } else { + clock_mask = CLOCK_MASK; + tck_num = TICK_NUMRATOR; + tck_denom = TICK_DENOMINATOR; + } + + clocks = delta * tck_num; + /* clocks = clocks / tck_denom; */ + do_div(clocks, tck_denom); + + if (t2 >= clocks) + td = t2 - clocks; + else + td = clock_mask + (t2 + 1) - clocks; + + return td & clock_mask; +} +#endif + + +void uccp420wlan_rx_frame(struct sk_buff *skb, void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + struct wlan_rx_pkt *rx = (struct wlan_rx_pkt *)(skb->data); + struct ieee80211_hdr *hdr; + struct ieee80211_rx_status rx_status; + struct ieee80211_supported_band *band = NULL; + int i; + static unsigned int rssi_index; + struct ieee80211_vif *vif = NULL; + + /* Remove RX control information: + * unused more_cmd_data in RX direction is used to indicate QoS/Non-Qos + * frames + */ + if (rx->hdr.more_cmd_data == 0) { + /* Non-QOS case*/ + skb_pull(skb, sizeof(struct wlan_rx_pkt)); + } else { + /* Qos Case: The UCCP overwrites the 2 reserved bytes with data + * to maintain the 4 byte alignment of total length and 2 byte + * alignment + * of starting address (as expected by mac80211). + */ + skb_pull(skb, sizeof(struct wlan_rx_pkt) - 2); + skb_trim(skb, skb->len - 2); + } + + hdr = (struct ieee80211_hdr *)skb->data; + + /* Stats for debugging */ + if (ieee80211_is_data(hdr->frame_control)) { + dev->stats->rx_packet_data_count++; + +#ifdef PERF_PROFILING + if (dev->params->driver_tput == 1) { + dev_kfree_skb_any(skb); + return; + } +#endif + } else if (ieee80211_is_mgmt(hdr->frame_control)) { + dev->stats->rx_packet_mgmt_count++; + } + + memset(&rx_status, 0, sizeof(struct ieee80211_rx_status)); + + if (rx->channel < 15) + rx_status.band = IEEE80211_BAND_2GHZ; + else + rx_status.band = IEEE80211_BAND_5GHZ; + + rx_status.freq = ieee80211_channel_to_frequency(rx->channel, + rx_status.band); + rx_status.signal = rx->rssi; + + /* RSSI Average for Production Mode*/ + if (dev->params->production_test == 1) { + dev->params->rssi_average[rssi_index++] = (char)(rx->rssi); + if (rssi_index >= MAX_RSSI_SAMPLES) + rssi_index = 0; + } + + rx_status.antenna = 0; + + if (rx->rate_flags & ENABLE_VHT_FORMAT) { + /* Rate */ + if ((rx->rate_or_mcs & MARK_RATE_AS_MCS_INDEX) != 0x80) { + UCCP_DEBUG_RX("Invalid VHT MCS Information\n"); + rx->rate_or_mcs = 0;/*default to MCS0*/ + } else { + rx_status.rate_idx = (rx->rate_or_mcs & 0x7f); + } + + /* NSS */ + if (!rx->nss || rx->nss > 8) + rx_status.vht_nss = 1; + else + rx_status.vht_nss = rx->nss; + + /* CBW */ + if (rx->rate_flags & ENABLE_CHNL_WIDTH_80MHZ) + rx_status.flag |= RX_VHT_FLAG_80MHZ; + else if (rx->rate_flags & ENABLE_CHNL_WIDTH_40MHZ) + rx_status.flag |= RX_FLAG_40MHZ; + + /* SGI */ + if (rx->rate_flags & ENABLE_SGI) + rx_status.flag |= RX_FLAG_SHORT_GI; + + rx_status.flag |= RX_FLAG_VHT; + } else if (rx->rate_flags & ENABLE_11N_FORMAT) { + /* Rate */ + if ((rx->rate_or_mcs & MARK_RATE_AS_MCS_INDEX) != 0x80) { + UCCP_DEBUG_RX("Invalid HT MCS Information\n"); + rx->rate_or_mcs = 0;/*default to MCS0*/ + } else { + rx_status.rate_idx = (rx->rate_or_mcs & 0x7f); + } + + /* CBW */ + if (rx->rate_flags & ENABLE_CHNL_WIDTH_40MHZ) + rx_status.flag |= RX_FLAG_40MHZ; + + /* SGI */ + if (rx->rate_flags & ENABLE_SGI) + rx_status.flag |= RX_FLAG_SHORT_GI; + + /* HT Greenfield */ + if (rx->rate_flags & ENABLE_GREEN_FIELD) + rx_status.flag |= RX_FLAG_HT_GF; + + rx_status.flag |= RX_FLAG_HT; + } else { + band = dev->hw->wiphy->bands[rx_status.band]; + + if (!WARN_ON_ONCE(!band)) { + for (i = 0; i < band->n_bitrates; i++) { + if (rx->rate_or_mcs == + band->bitrates[i].hw_value) { + rx_status.rate_idx = i; + break; + } + } + } else { + UCCP_DEBUG_DUMP_RX(" ", DUMP_PREFIX_NONE, 16, 1, + rx, sizeof(struct wlan_rx_pkt), 1); + dev_kfree_skb_any(skb); + return; + } + } + + /* Remove this once hardware supports bip(11w) is available*/ + if (!is_robust_mgmt(skb)) + rx_status.flag |= RX_FLAG_DECRYPTED; + + rx_status.flag |= RX_FLAG_MMIC_STRIPPED; + + if (rx->rx_pkt_status == RX_MIC_FAILURE_TKIP) { + rx_status.flag |= RX_FLAG_MMIC_ERROR; + } else if (rx->rx_pkt_status == RX_MIC_FAILURE_CCMP) { + /*Drop the Frame*/ + dev_kfree_skb_any(skb); + return; + } + + if (((hdr->frame_control & IEEE80211_FCTL_FTYPE) == + IEEE80211_FTYPE_MGMT) && + ((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_BEACON)) { + rx_status.mactime = get_unaligned_le64(rx->timestamp); + rx_status.flag |= RX_FLAG_MACTIME_START; + } + + /* Beacon Time Stamp */ + if (((hdr->frame_control & IEEE80211_FCTL_FTYPE) == + IEEE80211_FTYPE_MGMT) && + ((hdr->frame_control & IEEE80211_FCTL_STYPE) == + IEEE80211_STYPE_BEACON)) { + for (i = 0; i < MAX_VIFS; i++) { + vif = NULL; + vif = dev->vifs[i]; + if (vif && + ether_addr_equal(hdr->addr2, vif->bss_conf.bssid)) { + unsigned int ts2; + unsigned int ldelta; + + spin_lock(&tsf_lock); + dev->params->sync[i].status = 1; + memcpy(dev->params->sync[i].bssid, + vif->bss_conf.bssid, 6); + memcpy(dev->params->sync[i].ts1, + &rx->reserved, 8); + memcpy(&ts2, &rx->reserved[8], 4); + memcpy(&dev->params->sync[i].ts2, + &rx->reserved[8], 4); + memcpy(&ldelta, &rx->reserved[12], 4); + dev->params->sync[i].atu = 0; + /* ts2 = get_real_ts2(ts2, ldelta); */ + if (frc_to_atu) { + frc_to_atu(ts2, + &dev->params->sync[i].atu, 0); + dev->params->sync[i].atu -= ldelta * 1000; + } + spin_unlock(&tsf_lock); + } + } + } + + UCCP_DEBUG_RX(KERN_DEBUG + "%s-RX: RX frame, length = %d, RSSI = %d, rate = %d\n", + dev->name, + rx->pkt_length, + rx->rssi, + rx->rate_or_mcs); + + UCCP_DEBUG_DUMP_RX(" ", + DUMP_PREFIX_NONE, 16, 1, + skb->data, skb->len, 1); + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx(dev->hw, skb); +} + + +void uccp420wlan_ch_prog_complete(int event, + struct umac_event_ch_prog_complete *prog_ch, + void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + + dev->chan_prog_done = 1; +} + diff --git a/drivers/net/wireless/uccp420wlan/src/fwldr.c b/drivers/net/wireless/uccp420wlan/src/fwldr.c new file mode 100644 index 00000000000000..a7903356115b89 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/fwldr.c @@ -0,0 +1,1190 @@ +/* + * File Name : fwldr.c + * + * This file contains contains functions related to firmware loading + * functionality. + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include +#include + +#include + +struct fwload_priv *fpriv, fpv; + +static unsigned short fwldr_read_le2(unsigned char *buf); +static unsigned int fwldr_read_le4(unsigned char *buf); +static unsigned fwldr_virt_to_linear_off(unsigned page_size, + unsigned offset); + +static void fwldr_soft_reset(unsigned int thrd_num); + +static void fwldr_load_mem(unsigned int dst_addr, + unsigned int len, + unsigned char *src_buf); + +static void fwldr_start_thrd(unsigned int thrd_number, + unsigned int stack_ptr, + unsigned int program_ctr, + unsigned int catch_state_addr); + +static void fwldr_stop_thrd(unsigned int thrd_num); + +static void fwldr_zero_mem(unsigned int dst_addr, + unsigned int len); + +static unsigned int fwldr_config_read(unsigned int dst_addr); + +static void fwldr_config_write(unsigned int dst_addr, + unsigned int val); + +static enum uccp_mem_region fwldr_chk_region(unsigned int src_addr, + int length); + +static int fwldr_parser(const unsigned char *fw_data); + +static int fwldr_wait_for_completion(void); + +static void dir_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len); + +static void dir_mem_set(unsigned int addr, + unsigned char data, + unsigned int len); + +static void dir_mem_write(unsigned int addr, + unsigned int data); + +static void core_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len); + +static void core_mem_set(unsigned int addr, + unsigned int data, + unsigned int len); + +/* dir_mem_cpy + * + * Perform a memcpy of 'len' bytes from 'src_addr' to the UCCP memory location + * pointed by 'dst_addr'. + * + * dst_addr is always a 4 byte aligned address + * data is always a 4 byte aligned address + * len is always a multiple of 4 when dst_addr is of type 0xB4xxxxxx + * len may NOT be a multiple of 4 when dst_addr is of type 0xB7xxxxxx + * + * + * When dst_addr is of type 0xB4xxxxxx, perform only 32 bit writes to these + * locations + * + */ +static void dir_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len) +{ + int i; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + unsigned int *data_addr = (unsigned int *)data; + unsigned char *gram_byte_addr, *data_byte_addr; + + if ((fpriv->gram_b4_addr == NULL) && (base == UCCP_GRAM_MSB)) { + + /* The HAL didn't provide a virtual address for 0xB4xxxxxx alias + * Convert into 0xB7 and do the writes by ignoring MSB of the + * 32-bit word + */ + + addr &= 0x00FFFFFF; + addr |= 0xB7000000; + + data_byte_addr = (unsigned char *)data_addr; + gram_byte_addr = (void *)(fpriv->gram_addr + (offset / 4) * 3); + + hal_ops.set_mem_region(addr); + + if (len % 4 == 0) { + for (i = 0; i < len / 4; i++) { + memcpy(gram_byte_addr, data_byte_addr + 1, 3); + gram_byte_addr += 3; + data_byte_addr += 4; + } + } else { + fwldr_dbg_err("%s:Unexpected length(base:%lx)\n", + __func__, base); + } + + } else { + + hal_ops.set_mem_region(addr); + if (len % 4 == 0) { + for (i = 0; i < len / 4; i++) { + fwload_uccp_write(fpriv, base, offset, + data_addr[i]); + offset += 4; + } + } else { + if (base == UCCP_GRAM_PACKED) + memcpy((void *)(fpriv->gram_addr + offset), + (void *)data_addr, len); + else + fwldr_dbg_err("%s:Unexpected length(%lx)\n", + __func__, base); + } + } +} + + +/* dir_mem_set + * + * Perform a memset of 'len' bytes with value of 'val' to the UCCP memory + * location pointed by 'dst_addr'. + * + * dst_addr is always a 4 byte aligned address + * len is always a multiple of 4 when dst_addr is of type 0xB4xxxxxx + * len may NOT be a multiple of 4 when dst_addr is of type 0xB7xxxxxx + * + * + * When dst_addr is of type 0xB4xxxxxx, perform only 32 bit writes to these + * locations + * + */ +static void dir_mem_set(unsigned int addr, + unsigned char data, + unsigned int len) +{ + int i; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + unsigned char *gram_byte_addr; + + if ((fpriv->gram_b4_addr == NULL) && (base == UCCP_GRAM_MSB)) { + + /* The HAL didn't provide a virtual address for 0xB4xxxxxx alias + * Convert into 0xB7 and do the writes by ignoring MSB of the + * 32-bit word + */ + + addr &= 0x00FFFFFF; + addr |= 0xB7000000; + + gram_byte_addr = (void *)(fpriv->gram_addr + (offset / 4) * 3); + + hal_ops.set_mem_region(addr); + + if (len % 4 == 0) { + memset(gram_byte_addr, data, (len / 4) * 3); + } else { + fwldr_dbg_err("%s :Unexpected length (base : %lx)\n", + __func__, base); + } + + } else { + + hal_ops.set_mem_region(addr); + + if (len % 4 == 0) { + for (i = 0; i <= len / 4; i++) { + fwload_uccp_write(fpriv, base, offset, data); + offset += 4; + } + } else if (base == UCCP_GRAM_PACKED) { + memset((void *)(fpriv->gram_addr + offset), + data, len); + } else { + fwldr_dbg_err("%s: Unexpected length (base %lx)\n", + __func__, base); + } + } +} + +/* Perform 'len' 32 bit reads from a UCCP memory location 'addr' + * 'addr' is always a 4 byte aligned address + */ +void dir_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len) +{ + int i = 0; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + + hal_ops.set_mem_region(addr); + + for (i = 0; i <= len / 4; i++) { + fwload_uccp_read(fpriv, base, offset, data+i); + offset += 4; + } +} + +/* 32 bit write to UCCP memory location 'addr' + * 'addr' is always a 4 byte aligned address + */ +static void dir_mem_write(unsigned int addr, + unsigned int data) +{ + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + + hal_ops.set_mem_region(addr); + fwload_uccp_write(fpriv, base, offset, data); +} + + +static void core_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len) +{ + unsigned int i = 0; + unsigned int *src_data = (unsigned int *)data; + unsigned int flag = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (len > 1) + flag = SLAVE_BLOCK_WRITE; + else + flag = SLAVE_SINGLE_WRITE; + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | flag)); + + for (i = 0; i < len / 4; i++) { + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (data != NULL) + dir_mem_write(MSLVDATAT, src_data[i]); + else + dir_mem_write(MSLVDATAT, 0x00); + } + +} + + +static void core_mem_set(unsigned int addr, + unsigned int data, + unsigned int len) +{ + unsigned int flag = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (len > 1) + flag = SLAVE_BLOCK_WRITE; + else + flag = SLAVE_SINGLE_WRITE; + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | flag)); + dir_mem_write(MSLVDATAT, data); +} + + +void core_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len) +{ + unsigned int i = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | SLAVE_BLOCK_READ)); + + for (i = 0; i < len-1; i++) { + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_read(MSLVDATAT, &data[i], 1); + } + + /* Read the last word */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_read(MSLVDATAX, &data[len-1], 1); + + +} + +int rpudump_init(void) +{ + fpriv = &fpv; + + hal_ops.request_mem_regions(&fpriv->gram_addr, + &fpriv->sysbus_addr, + &fpriv->gram_b4_addr); + return 0; +} + +int fwldr_load_fw(const unsigned char *fw_data, int i) +{ + struct fwldr_cfg_rw rw_v; + int err = FWLDR_SUCCESS; + + fpriv = &fpv; + hal_ops.request_mem_regions(&fpriv->gram_addr, + &fpriv->sysbus_addr, + &fpriv->gram_b4_addr); + + + fwldr_soft_reset(LTP_THREAD_NUM); + + memset(&rw_v, 0, sizeof(rw_v)); + rw_v.addr = UCCP_GRAM_BASE + UCCP_THRD_EXEC_SIG_OFFSET; + rw_v.val = 0x00; + fwldr_config_write(rw_v.addr, rw_v.val); + + err = fwldr_parser(fw_data); + + if (err != FWLDR_SUCCESS) { + pr_err("FW load failed\n"); + return err; + } + + if (!fwldr_wait_for_completion()) { + pr_err("FW load timed out waiting for completion\n"); + return FWLDR_FAIL; + } + if (!i) + fwldr_stop_thrd(LTP_THREAD_NUM); + return err; +} + + +static void fwldr_load_mem(unsigned int dst_addr, + unsigned int len, + unsigned char *src_buf) +{ + enum uccp_mem_region mem_region = UCCP_MEM_ERR; + int i = 0; + + mem_region = fwldr_chk_region(dst_addr, len); + + fwldr_dbg_info("%s dst_addr = 0x%X, length = 0x%X, srcaddr = 0x%X\n", + __func__, dst_addr, len, (unsigned int)src_buf); + + fwldr_dbg_info("Dump upto 16 bytes\n"); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned\n"); + + for (i = 0; i < 16; i += 2) + fwldr_dbg_dump("0x%X \t 0x%X\n", src_buf[i], src_buf[i + 1]); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_cpy(dst_addr, src_buf, len); + break; + + case UCCP_MEM_DIRECT: + dir_mem_cpy(dst_addr, src_buf, len); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped writing\n"); + break; + } +} + + +static void fwldr_start_thrd(unsigned int thrd_num, + unsigned int stack_ptr, + unsigned int prog_ctr, + unsigned int catch_state_addr) +{ + fwldr_dbg_info("%s PC = 0x%X,\tSP = 0x%X\n", + __func__, prog_ctr, stack_ptr); + + /* Program Counter */ + core_mem_set(MTX_TXUXXRXDT, prog_ctr, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_PC_REG_IND_ADDR, 1); + + /* Stack Pointer */ + core_mem_set(MTX_TXUXXRXDT, stack_ptr, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_A0STP_REG_IND_ADDR, 1); + + /* Thread Enable */ + core_mem_set(MTX_TXENABLE_ADDR, MTX_START_EXECUTION, 1); + + fwldr_dbg_info("Thread %d is Enabled\n", thrd_num); +} + + +static void fwldr_stop_thrd(unsigned int thrd_num) +{ + unsigned int val; + + /* Thread Disable */ + core_mem_set(MTX_TXENABLE_ADDR, MTX_STOP_EXECUTION, 1); + + core_mem_read(MTX_TXENABLE_ADDR, &val, 1); + + while ((val & 0x2) != 0x2) { + core_mem_read(MTX_TXENABLE_ADDR, &val, 1); + + fwldr_dbg_info("%s val = 0x%X\n", __func__, val); + + } + + fwldr_dbg_info("TXENABLE = 0x%X\n", val); + fwldr_dbg_info("Thread %d is Stopped\n", thrd_num); +} + + +static void fwldr_zero_mem(unsigned int dst_addr, + unsigned int len) +{ + int mem_region = 0; + + fwldr_dbg_info("%s DestAddr = 0x%X, length = 0x%X\n", + __func__, dst_addr, len); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, len); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_cpy(dst_addr, NULL, len); + break; + + case UCCP_MEM_DIRECT: + dir_mem_set(dst_addr, 0x00, len); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped setting\n"); + break; + } +} + + +static unsigned int fwldr_config_read(unsigned int dst_addr) +{ + int mem_region = 0; + int val = 0; + + fwldr_dbg_info("%s dst_addr = 0x%X\n", __func__, dst_addr); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, 0); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_read(dst_addr, &val, 1); + return val; + + case UCCP_MEM_DIRECT: + dir_mem_read(dst_addr, &val, 1); + return val; + + default: + fwldr_dbg_err("Region unknown. Skipped reading\n"); + return 0; + } + + return 0; +} + + +static void fwldr_config_write(unsigned int dst_addr, + unsigned int val) +{ + int mem_region = 0; + + fwldr_dbg_info("%s dst_addr = 0x%X,\tValue = 0x%X\n", + __func__, dst_addr, val); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, 0); + + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_set(dst_addr, val, 1); + break; + + case UCCP_MEM_DIRECT: + dir_mem_write(dst_addr, val); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped writing\n"); + break; + } + +} + + +static enum uccp_mem_region fwldr_chk_region(unsigned int src_addr, int len) +{ + unsigned int dst_addr = src_addr + len; + + if (((src_addr >= 0x03000000) && (src_addr <= 0x04FFFFFF)) || + ((src_addr >= 0x02009000) && (src_addr <= 0x0203BFFF)) || + ((src_addr >= 0x80000000) && (src_addr <= 0x87FFFFFF))) { + if (len != 0) { + if (((dst_addr >= 0x03000000) && + (dst_addr <= 0x04FFFFFF)) || + ((dst_addr >= 0x02009000) && + (dst_addr <= 0x0203BFFF)) || + ((dst_addr >= 0x80000000) && + (dst_addr <= 0x87FFFFFF))) + return UCCP_MEM_CORE; + else + return UCCP_MEM_ERR; + } + + return UCCP_MEM_CORE; + } else if ((src_addr & 0xFF000000) == 0xB0000000) { + return UCCP_MEM_ERR; + } else { + return UCCP_MEM_DIRECT; + } +} + + +static void fwldr_soft_reset(unsigned int thrd_num) +{ + unsigned int val, temp; + unsigned int retries = 3; + + /* If the thread is running, then stop it and clear the registers, + * otherwise do nothing + */ + core_mem_read(MTX_TXENABLE_ADDR, &val, 1); + + fwldr_dbg_info("Resetting UCCP420\n"); + + /* Soft Reset */ + dir_mem_read(MSLVSRST, &val, 1); + dir_mem_write(MSLVSRST, (val | 1)); + + /* Wait for 16 core clock cycles. Core runs at 320MHz */ + udelay(10); + + /* Clear the Soft Reset */ + dir_mem_write(MSLVSRST, (val & 0xFFFFFFFE)); + + /* Give additional 20 ms for the DA to do its own reset */ + mdelay(20); + + /* Clear the Minim Bit in PrivExt */ + core_mem_set(MTX_TXPRIVEXT_ADDR, 0, 1); + + /* Set the PCX value i to 0 */ + core_mem_set(MTX_TXUXXRXDT, 0, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_PCX_REG_IND_ADDR, 1); + + + /* Clear TXPOLL{I} to clear TXSTAT{I} + * Writing 0xFFFFFFFF clears TXSTATI, but TXMASKI must + * be all set too for this to work. + */ + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXMASK_REG_IND_ADDR, 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXMASKI_REG_IND_ADDR, 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXPOLL_REG_IND_ADDR, 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXPOLLI_REG_IND_ADDR, 1); + + /* Clear TXMASK and TXMASKI */ + core_mem_set(MTX_TXUXXRXDT, 0x0, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXMASK_REG_IND_ADDR, 1); + + + core_mem_set(MTX_TXUXXRXDT, 0x0, 1); + core_mem_set(MTX_TXUXXRXRQ, MTX_TXMASKI_REG_IND_ADDR, 1); + + /* Ensure all kicks are cleared */ + core_mem_set(MTX_TXUXXRXRQ, + (MTX_TXPOLLI_REG_IND_ADDR | REG_IND_READ_FLAG), + 1); + + core_mem_read(MTX_TXUXXRXDT, &temp, 1); + + while (temp && retries--) { + core_mem_set(MTX_TXUXXRXDT, 0x2, 1); + + core_mem_set(MTX_TXUXXRXRQ, + MTX_TXPOLLI_REG_IND_ADDR, + 1); + + core_mem_set(MTX_TXUXXRXRQ, + (MTX_TXPOLLI_REG_IND_ADDR | + REG_IND_READ_FLAG), + 1); + + core_mem_read(MTX_TXUXXRXDT, &temp, 1); + } + + /* Reset TXSTATUS */ + core_mem_set(MTX_TXSTATUS_ADDR, 0x00020000, 1); + + fwldr_dbg_info("Soft Reset core\n"); +} + + +/* Reads a 16-bit little endian value from the specified position in a buffer */ +static unsigned short fwldr_read_le2(unsigned char *buf) +{ + unsigned short val = 0; + + val = buf[0]; + val |= buf[1] << 8; + + return val; +} + + +/* Reads a 32-bit little endian value from the specified position in a buffer */ +static unsigned int fwldr_read_le4(unsigned char *buf) +{ + unsigned int val = 0; + + val = buf[0]; + val |= buf[1] << 8; + val |= buf[2] << 16; + val |= buf[3] << 24; + + return val; +} + + +/* Converts a virtual (paged) offset to a linear (non-paged) offset */ +static unsigned fwldr_virt_to_linear_off(unsigned page_size, unsigned offset) +{ + static unsigned virt_page_size; + + unsigned val = offset; + + if (page_size) { + if (virt_page_size == 0) { + virt_page_size = 1; + while (virt_page_size < page_size) + virt_page_size <<= 1; + } + + val = ((offset / virt_page_size) * page_size) + + (offset % virt_page_size); + } + + return val; +} + + +static int fwldr_parser(const unsigned char *fw_data) +{ + int quit = 0; + signed int nxt = 0; + signed int file_offset = 0; + signed int page_size = 0; + signed int orig_offset = 0; + signed int prev_offset = 0; + struct fwldr_bootdevhdr boot_dev_hdr; + char info_buf[256]; + char *cfg_str = NULL; + char cfg_buf[256]; + char *cfg_buf_ptr = NULL; + struct fwldr_sec_ldr_l1_record l1_rec; + struct fwldr_load_mem_info lm_v; + struct fwldr_cfg_rw rw_v; + unsigned char l1_buf[FWLDR_L1_MAXSIZE]; + signed int seek_off; + char *str_curr = NULL; + char *str_end = NULL; + char *str_new = NULL; + int buf_len = 0; + int res = FWLDR_SUCCESS; + + /* Lets really do it */ + memcpy(&boot_dev_hdr, fw_data, sizeof(struct fwldr_bootdevhdr)); + + fwldr_dbg_info("DevID: 0x%08X\n", boot_dev_hdr.dev_id); + fwldr_dbg_info("SLCode: 0x%08X\n", boot_dev_hdr.sl_code); + fwldr_dbg_info("SLData: 0x%08X\n", boot_dev_hdr.sl_data); + fwldr_dbg_info("PLCtrl: 0x%04X\n", boot_dev_hdr.pl_ctrl); + fwldr_dbg_info("CRC: 0x%04X\n", boot_dev_hdr.CRC); + fwldr_dbg_info("%d", sizeof(boot_dev_hdr)); + fwldr_dbg_info("\n"); + + file_offset = fwldr_virt_to_linear_off(page_size, boot_dev_hdr.sl_code); + orig_offset = fwldr_virt_to_linear_off(page_size, boot_dev_hdr.sl_data); + + nxt = sizeof(struct fwldr_bootdevhdr); + + do { + unsigned char sec_ldr_code[FWLDR_PLRCRD_BYTES]; + + memcpy(&sec_ldr_code, + fw_data + file_offset, + FWLDR_PLRCRD_BYTES); + + nxt = fwldr_read_le4(&sec_ldr_code[FWLDR_PLRCRD_DATA_BYTES]); + + file_offset = fwldr_virt_to_linear_off(page_size, + nxt); + } while (nxt); + + file_offset = orig_offset; + + while (!quit) { + unsigned char *l2_buf = NULL, *l2_blk = NULL; + unsigned int l2_len = 0U; + + memcpy(&l1_buf, + fw_data + file_offset, + FWLDR_L1_MAXSIZE); + + l1_rec.cmd_tag = fwldr_read_le2(&l1_buf[FWLDR_L1_CMD_OFF]); + l1_rec.len = fwldr_read_le2(&l1_buf[FWLDR_L1_LEN_OFF]); + l1_rec.nxt = fwldr_read_le4(&l1_buf[FWLDR_L1_NXT_OFF]); + + if ((l1_rec.len > FWLDR_L1_MAXSIZE) || + (l1_rec.len < FWLDR_L1_L2OFF_OFF) || + (l1_rec.len < FWLDR_L1_L2LEN_OFF)) { + fwldr_dbg_err("Maximum L1 length exceeded\n"); + res = FWLDR_FAIL; + break; + } + + /* Extract generic L1 fields */ + l1_rec.l2_offset = fwldr_read_le4(&l1_buf[l1_rec.len - + FWLDR_L1_L2OFF_OFF]); + + l1_rec.l2_len = fwldr_read_le2(&l1_buf[l1_rec.len - + FWLDR_L1_L2LEN_OFF]); + + if (l1_rec.l2_len > FWLDR_L2_BASIC_SIZE) { + /* Read the L2 data */ + seek_off = fwldr_virt_to_linear_off(page_size, + l1_rec.l2_offset); + + if (l1_rec.l2_len > FWLDR_L2_MAXSIZE) { + fwldr_dbg_err("Maximum L2 length exceeded\n"); + res = FWLDR_FAIL; + break; + } + + l2_blk = kmalloc(l1_rec.l2_len + 1, + GFP_KERNEL); + + if (l2_blk == NULL) { + res = FWLDR_FAIL; + break; + } + + memcpy(l2_blk, + fw_data + seek_off, + l1_rec.l2_len); + + l2_blk[l1_rec.l2_len] = '\0'; + + l2_buf = l2_blk + + (FWLDR_L2_CMD_SIZE + FWLDR_L2_LEN_SIZE); + + l2_len = l1_rec.l2_len - FWLDR_L2_BASIC_SIZE; + } + + switch (l1_rec.cmd_tag) { + case FWLDR_L1_CMD_LOAD_MEM: + if (!l2_buf) { + fwldr_dbg_err("Invalid params to Load Mem\n"); + res = FWLDR_FAIL; + quit = 1; + break; + } + + /* Load mem record */ + l1_rec.arg1 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG1_OFF]); + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: Addr: 0x%08X: Size: 0x%08X\n", + "LoadMem", l1_rec.arg1, l2_len); + + lm_v.dst_addr = l1_rec.arg1; + lm_v.len = l2_len; + lm_v.src_buf = l2_buf; + + fwldr_load_mem(lm_v.dst_addr, + lm_v.len, + lm_v.src_buf); + break; + + case FWLDR_L1_CMD_START_THRDS: + /* Start each thread with initial SP */ + if (!l2_buf) { + fwldr_dbg_err("%s : %d Invalid params\n", + __func__, __LINE__); + res = FWLDR_FAIL; + quit = 1; + break; + } + + cfg_buf[0] = '\0'; + cfg_buf_ptr = cfg_buf; + + while (l2_len > 0) { + struct fwldr_thrd_info tinfo_v; + + snprintf(cfg_buf_ptr, + sizeof(cfg_buf), + "\tThrd %d: SP: 0x%08X: PC: 0x%08X: Catch: 0x%08X\n", + fwldr_read_le4(l2_buf), + fwldr_read_le4(l2_buf + 4), + fwldr_read_le4(l2_buf + 8), + fwldr_read_le4(l2_buf + 12)); + + tinfo_v.thrd_num = fwldr_read_le4(l2_buf); + tinfo_v.stack_ptr = fwldr_read_le4(l2_buf + 4); + tinfo_v.prog_ctr = fwldr_read_le4(l2_buf + 8); + tinfo_v.catch_state_addr = + fwldr_read_le4(l2_buf + 12); + + fwldr_start_thrd(tinfo_v.thrd_num, + tinfo_v.stack_ptr, + tinfo_v.prog_ctr, + tinfo_v.catch_state_addr); + + l2_buf += (4 * sizeof(unsigned int)); + l2_len -= (4 * sizeof(unsigned int)); + cfg_buf_ptr += strlen(cfg_buf_ptr); + } + + snprintf(info_buf, + sizeof(info_buf), + "%-12s:\n%s", + "StartThrds", + cfg_buf); + + break; + + case FWLDR_L1_CMD_ZERO_MEM: + /* Zero memory */ + l1_rec.arg1 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG1_OFF]); + l1_rec.arg2 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG2_OFF]); + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: Addr: 0x%08X: Size: 0x%08X\n", + "ZeroMem", + l1_rec.arg1, + l1_rec.arg2); + + lm_v.dst_addr = l1_rec.arg1; + lm_v.len = l1_rec.arg2; + + fwldr_zero_mem(lm_v.dst_addr, lm_v.len); + + break; + + case FWLDR_L1_CMD_CONFIG: + /* Configuration commands */ + buf_len = (l1_rec.l2_len / 8) * 40; + + if (!l2_buf) { + fwldr_dbg_err("%s:%d:Invalid params\n", + __func__, __LINE__); + res = FWLDR_FAIL; + quit = 1; + break; + } + + cfg_str = kmalloc(buf_len, GFP_KERNEL); + + if (cfg_str) { + str_curr = cfg_str; + str_end = cfg_str + buf_len; + } + + do { + int rec_len = 0, len = 0; + unsigned int cmd = fwldr_read_le4(l2_buf); + + if ((str_curr && cfg_str) && + ((str_end - str_curr) < 256)) { + size_t pos = str_curr - cfg_str; + + /* Extend buffer */ + buf_len *= 2; + + str_new = krealloc(cfg_str, + buf_len, + GFP_KERNEL); + + if (str_new == NULL) { + fwldr_dbg_err("%s : %d %s\n", + __func__, + __LINE__, + "realloc failed"); + kfree(cfg_str); + cfg_str = NULL; + str_curr = NULL; + str_end = NULL; + } else { + cfg_str = str_new; + /* Relocate pointers */ + str_curr = cfg_str + + pos; + str_end = cfg_str + + buf_len; + } + } + + switch (cmd) { + case FWLDR_CONF_CMD_PAUSE: + rec_len = 8; + /* TODO: Calculate the exact delay */ + mdelay(2); + break; + case FWLDR_CONF_CMD_READ: + rec_len = 8; + + rw_v.addr = fwldr_read_le4(&l2_buf[4]); + rw_v.val = fwldr_config_read(rw_v.addr); + + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tRead : 0x%08X\n", + rw_v.addr); + } + + + /* Value read is in rw_v */ + break; + + case FWLDR_CONF_CMD_WRITE: + rec_len = 12; + + rw_v.addr = fwldr_read_le4(&l2_buf[4]); + rw_v.val = fwldr_read_le4(&l2_buf[8]); + + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tWrite: 0x%08X: 0x%08X\n", + rw_v.addr, + rw_v.val); + } + + + fwldr_config_write(rw_v.addr, rw_v.val); + + break; + + case FWLDR_CONF_CMD_USER: + if (str_curr) { + unsigned int v1 = 0; + unsigned int v2 = 0; + unsigned int v3 = 0; + unsigned int v4 = 0; + + v1 = + fwldr_read_le4(&l2_buf[4]), + v2 = + fwldr_read_le4(&l2_buf[8]), + v3 = + fwldr_read_le4(&l2_buf[12]), + v4 = + fwldr_read_le4(&l2_buf[16]), + + len = snprintf(str_curr, + buf_len, + "\tUser: 0x%08X: 0x%08X: 0x%08X: 0x%08X\n", + v1, + v2, + v3, + v4); + } + + rec_len = 20; + break; + + default: + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tUnknown: %08X (%d bytes remain)\n", + cmd, + l2_len); + } + break; + } + + if ((rec_len == 0) || (res == FWLDR_FAIL)) + break; + + if (str_curr) + str_curr += len; + + l2_buf += rec_len; + l2_len -= rec_len; + } while (l2_len > 0); + + snprintf(info_buf, sizeof(info_buf), + "%-12s: %d bytes %s\n", "Config", + (unsigned int)(l2_buf-l2_blk), + ((l2_len != 0) ? ": ERROR!!" : "")); + break; + + case FWLDR_L1_CMD_FILENAME: + if (!l2_blk) { + fwldr_dbg_err("Invalid params to Filename\n"); + res = FWLDR_FAIL; + quit = 1; + break; + } + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: %s\n", + "FileName", + l2_blk + 8); + break; + + default: + /* Not expected */ + snprintf(info_buf, + sizeof(info_buf), + "%-12s\n", + "Unknown"); + break; + } + + kfree(l2_blk); + + if (cfg_str) { + fwldr_dbg_info("0x%08X: %s%s", + file_offset, + info_buf, + cfg_str); + kfree(cfg_str); + cfg_str = NULL; + } else { + fwldr_dbg_info("0x%08X: %s ", + file_offset, + info_buf); + } + + if (l1_rec.nxt == FWLDR_L1_TERMINATE) { + unsigned int overlay_off = 0; + + /* There is the possibility of further overlays. + * Without additional information, the best guess is + * that they start immediately after the L2 data of the + * last record. + */ + l1_rec.l2_offset = fwldr_read_le4(&l1_buf[l1_rec.len - + FWLDR_L1_L2OFF_OFF]); + l1_rec.l2_len = fwldr_read_le2(&l1_buf[l1_rec.len - + FWLDR_L1_L2LEN_OFF]); + + overlay_off = fwldr_virt_to_linear_off(page_size, + l1_rec.l2_offset); + overlay_off += l1_rec.l2_len; + + /* Round to next 32-bit boundary */ + overlay_off += 3; + overlay_off &= ~3; + + fwldr_dbg_info("\n"); + fwldr_dbg_info("Possible next L1 Record 0x%08X\n", + overlay_off); + + quit = 1; + } else { + /* Move on to next L1 record */ + prev_offset = file_offset; + file_offset = fwldr_virt_to_linear_off(page_size, + l1_rec.nxt); + + if (file_offset <= prev_offset) { + /* Possibly incorrect page size specified. + * Stopping + */ + fwldr_dbg_err("Out of sequence record found\n"); + quit = 1; + } + } + } + + return res; +} + + +static int fwldr_wait_for_completion(void) +{ + struct fwldr_cfg_rw rw_v; + int result = 1; + unsigned int i = 0; + + rw_v.addr = UCCP_GRAM_BASE + UCCP_THRD_EXEC_SIG_OFFSET; + + do { + rw_v.val = fwldr_config_read(rw_v.addr); + /* Sleep for 10 ms */ + mdelay(10); + + i++; + + } while ((UCCP_THRD_EXEC_SIG != rw_v.val) && (i < 1000)); + + if (i == 1000) + result = 0; + + return result; +} diff --git a/drivers/net/wireless/uccp420wlan/src/hal_hostport.c b/drivers/net/wireless/uccp420wlan/src/hal_hostport.c new file mode 100644 index 00000000000000..7bbb0b063a5864 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/hal_hostport.c @@ -0,0 +1,2156 @@ +/* + * File Name : hal_hostport.c + * + * This file contains the source functions of HAL IF for hostport+shared + * memmory based communications + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "core.h" +#include "hal.h" +#include "hal_hostport.h" +#include "fwldr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMMAND_START_MAGIC 0xDEAD + +static int is_mem_dma(void *virt_addr, int len); +static int init_rx_buf(int pkt_desc, + unsigned int max_data_size, + dma_addr_t *dma_buf, + struct sk_buff *new_skb); + +static int is_mem_bounce(void *virt_addr, int len); + +static struct hal_priv *hpriv; +static const char *hal_name = "UCCP420_WIFI_HAL"; + +static unsigned long shm_offset = HAL_SHARED_MEM_OFFSET; +module_param(shm_offset, ulong, S_IRUSR|S_IWUSR); + +unsigned int hal_cmd_sent; +unsigned int hal_event_recv; +struct timer_list stats_timer; +unsigned int alloc_skb_failures; +unsigned int alloc_skb_dma_region; +unsigned int alloc_skb_priv_region; +unsigned int alloc_skb_priv_runtime; + +static unsigned int uccp_ddr_base; + +#ifdef PERF_PROFILING +/* The timing markers */ +unsigned long irq_timestamp[20] = {0}; +unsigned long rcv_hdlr_time[20] = {0}; +unsigned long rx_pkts_halint_event[20] = {0}; +unsigned long halint_event_handling_time[20] = {0}; +unsigned long pflags; + +/*Indexs for current sample*/ +unsigned int halint_handling_index; +unsigned int rx_pkt_index; +unsigned int rcv_hdlr_index; +unsigned int irq_ts_index; +spinlock_t timing_lock; +#endif + +#ifdef HAL_DEBUG +#define _HAL_DEBUG(fmt, args...) pr_debug(fmt, ##args) +#else /* CONFIG_HAL_DEBUG */ +#define _HAL_DEBUG(...) do { } while (0) +#endif /* CONFIG_HAL_DEBUG */ + +#define UCCP_DEBUG_HAL(fmt, ...) \ +do { \ + if ((uccp_debug & UCCP_DEBUG_HAL) && net_ratelimit()) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_DUMP_HAL(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_DUMP_HAL) \ + print_hex_dump(KERN_DEBUG, fmt, ##__VA_ARGS__); \ +} while (0) + +#define DUMP_HAL (uccp_debug & UCCP_DEBUG_DUMP_HAL) + +/* for send and receive count. */ +static unsigned long tx_cnt; +static unsigned long rx_cnt; +/*UCCP_DEBUG_HAL */ + +unsigned char vif_macs[2][ETH_ALEN]; + +static char *mac_addr; +module_param(mac_addr, charp, 0000); +MODULE_PARM_DESC(mac_addr, "Configure wifi base mac address"); + +#define RF_PARAMS_SIZE 369 +unsigned char rf_params[RF_PARAMS_SIZE]; +unsigned char *rf_params_vpd; +int num_streams_vpd = -1; + +/* Range check */ +#define CHECK_EVENT_ADDR_UCCP(x) ((x) >= HAL_UCCP_GRAM_BASE && (x) <=\ + (HAL_UCCP_GRAM_BASE + \ + hpriv->uccp_pkd_gram_len)) + +#define CHECK_EVENT_STATUS_ADDR_UCCP(x) ((x) >= HAL_UCCP_GRAM_BASE && (x) <=\ + (HAL_UCCP_GRAM_BASE + \ + hpriv->uccp_pkd_gram_len)) + +#define CHECK_EVENT_LEN(x) ((x) < 0x5000) +#define CHECK_RX_PKT_CNT(x) ((x) >= 1 && (x) <= 16) +/* #define CHECK_SRC_PTR(x, y) ((x) >= (y) && (x) <= (y) + + * HAL_HOST_BOUNCE_BUF_LEN) + */ +#define CHECK_PKT_DESC(x) ((x) < (hpriv->rx_bufs_2k + hpriv->rx_bufs_12k)) +/* MAX_RX_BUFS */ + +#define DEFAULT_MAC_ADDRESS "001122334455" + +static void __iomem *get_base_address_64mb(unsigned int bounce_addr) +{ + int boundary = 0x4000000 /*64MB*/; + unsigned int chunk_start_offset; + unsigned int chunk_start, next_chunk_start; + + /* divide the DDR in to 64MB chunks. + * and return the chuunk address base corresponding to the + * 4mb_addr. + */ + chunk_start_offset = (unsigned int) bounce_addr/boundary; + chunk_start = chunk_start_offset * boundary; + next_chunk_start = (chunk_start_offset + 1) * boundary; + + /* 4MB region spans across chunks, program + * bounce_addr-60MB as start of 64MB region. + */ + if (bounce_addr + HAL_HOST_BOUNCE_BUF_LEN > next_chunk_start) { + pr_info("%s: bounce_addr spans across chunks\n", __func__); + chunk_start = bounce_addr - HAL_HOST_NON_BOUNCE_BUF_LEN; + } + + pr_info("bounce_addr: 0x%x chunk_start: 0x%x\n", + (unsigned int) bounce_addr, + chunk_start_offset); + + return (void __iomem *) (chunk_start); +} + + +int hal_get_dump_len(unsigned long dump_type) +{ + unsigned int dump_len = 0; + + switch (dump_type) { + case HAL_RPU_TM_CMD_GRAM: + dump_len = hpriv->uccp_pkd_gram_len; + break; + case HAL_RPU_TM_CMD_COREA: + dump_len = UCCP_COREA_REGION_LEN; + break; + case HAL_RPU_TM_CMD_COREB: + dump_len = UCCP_COREB_REGION_LEN; + break; + case HAL_RPU_TM_CMD_PERIP: + dump_len = hpriv->uccp_perip_len; + break; + case HAL_RPU_TM_CMD_SYSBUS: + dump_len = hpriv->uccp_sysbus_len; + break; + default: + dump_len = 0; + } + return dump_len; +} + +int hal_get_dump_gram(long *dump_start) +{ + char *gram_dump; + + gram_dump = kzalloc(hpriv->uccp_pkd_gram_len, GFP_KERNEL); + + if (!dump_start) + return -ENOMEM; + + memcpy(gram_dump, + (char *)hpriv->gram_base_addr, + hpriv->uccp_pkd_gram_len); + + *dump_start = (long) gram_dump; + + return 0; +} + +int hal_get_dump_core(unsigned long *dump_start, unsigned char region_type) +{ + unsigned int *core_dump; + unsigned long len = 0; + unsigned long region_start; + + if (region_type == UCCP_REGION_TYPE_COREA) { + len = UCCP_COREA_REGION_LEN; + region_start = UCCP_COREA_REGION_START; + } else if (region_type == UCCP_REGION_TYPE_COREB) { + len = UCCP_COREB_REGION_LEN; + region_start = UCCP_COREB_REGION_START; + } + + core_dump = kzalloc(len, GFP_KERNEL); + + if (!core_dump) + return -ENOMEM; + + if (len % 4) + len = len/4 + 1; + else + len = len/4; + + rpudump_init(); + + core_mem_read(region_start, core_dump, len); + + *dump_start = (unsigned long) core_dump; + + return 0; + +} + +int hal_get_dump_perip(unsigned long *dump_start) +{ + unsigned int *perip_dump; + + perip_dump = kzalloc(hpriv->uccp_perip_len, GFP_KERNEL); + + if (!perip_dump) + return -ENOMEM; + + memcpy(perip_dump, + (char *)hpriv->uccp_perip_base_addr, + hpriv->uccp_perip_len); + + *dump_start = (unsigned long) perip_dump; + + return 0; +} + +int hal_get_dump_sysbus(unsigned long *dump_start) +{ + unsigned int *sysbus_dump; + + sysbus_dump = kzalloc(hpriv->uccp_sysbus_len, GFP_KERNEL); + + if (!sysbus_dump) + return -ENOMEM; + + memcpy(sysbus_dump, + (char *)hpriv->uccp_sysbus_base_addr, + hpriv->uccp_sysbus_len); + + *dump_start = (unsigned long) sysbus_dump; + return 0; + +} + +static int hal_reset_hal_params(void) +{ + hpriv->cmd_cnt = COMMAND_START_MAGIC; + hpriv->event_cnt = 0; + return 0; +} + + +static int hal_ready(struct hal_priv *priv) +{ + unsigned int value = 0; + + /* Check the ACK register bit */ + value = readl((void __iomem *)(HOST_TO_UCCP_CORE_CMD_ADDR)); + + if (value & BIT(UCCP_CORE_HOST_INT_SHIFT)) + return 0; + else + return 1; +} + + +static void tx_tasklet_fn(unsigned long data) +{ + struct hal_priv *priv = (struct hal_priv *)data; + struct sk_buff *skb; + unsigned int value = 0; + unsigned long start_addr; + unsigned long start = 0; + + while ((skb = skb_dequeue(&priv->txq))) { + tx_cnt++; + UCCP_DEBUG_HAL("%s: tx_cnt=%ld cmd_cnt=0x%X event_cnt=0x%X\n", + hal_name, + tx_cnt, + priv->cmd_cnt, + priv->event_cnt); + if (DUMP_HAL) { + UCCP_DEBUG_HAL("%s: xmit dump\n", hal_name); + UCCP_DEBUG_DUMP_HAL(" ", DUMP_PREFIX_NONE, 16, 1, + skb->data, skb->len, 1); + } + + start = jiffies; + + while (!hal_ready(priv) && + time_before(jiffies, start + msecs_to_jiffies(1000))) { + ; + } + + if (!hal_ready(priv)) { + pr_err("%s: Intf not ready for 1000ms, dropping cmd\n", + hal_name); + dev_kfree_skb_any(skb); + skb = NULL; + } + + if (!skb) + continue; + + if (priv->hal_disabled) + break; + + /* Write the command buffer in GRAM */ + start_addr = readl((void __iomem *)HAL_GRAM_CMD_START); + + UCCP_DEBUG_HAL("%s: Command address = 0x%08x\n", + hal_name, (unsigned int)start_addr); + + start_addr -= HAL_UCCP_GRAM_BASE; + start_addr += ((priv->gram_mem_addr)-(priv->shm_offset)); + + if ((start_addr < priv->gram_mem_addr) || + (start_addr > (priv->gram_mem_addr + HAL_WLAN_GRAM_LEN))) { + pr_err("%s: Invalid cmd addr 0x%08x, dropping cmd\n", + hal_name, (unsigned int)start_addr); + dev_kfree_skb_any(skb); + skb = NULL; + continue; + } + + memcpy((unsigned char *)start_addr, skb->data, skb->len); + + writel(skb->len, (void __iomem *)HAL_GRAM_CMD_LEN); + + value = (unsigned int) (priv->cmd_cnt); + value |= 0x7fff0000; + writel(value, (void __iomem *)(HOST_TO_UCCP_CORE_CMD_ADDR)); + priv->cmd_cnt++; + hal_cmd_sent++; + + dev_kfree_skb_any(skb); + } +} + + +static void hostport_send(struct hal_priv *priv, + struct sk_buff *skb) +{ + skb_queue_tail(&priv->txq, skb); + tasklet_schedule(&priv->tx_tasklet); +} + +static void hostport_send_head(struct hal_priv *priv, + struct sk_buff *skb) +{ + skb_queue_head(&priv->txq, skb); + tasklet_schedule(&priv->tx_tasklet); +} + + +static void hal_send(void *nwb, + unsigned char rcv_mod_id, + unsigned char send_mod_id, + void *dataptr) + { + struct sk_buff *cmd = (struct sk_buff *)nwb, *skb, *tmp; + struct sk_buff_head *skb_list; + struct hal_hdr *hdr; + unsigned long dcp_start_addr; + unsigned int pkt = 0, desc_id = 0, frame_id = 0; + struct hal_tx_data *hal_tx_data = NULL; + struct buf_info *tx_buf_info = NULL; + dma_addr_t dma_buf; + + if (dataptr) { + hdr = (struct hal_hdr *)cmd->data; + skb_list = (struct sk_buff_head *)dataptr; + + /* Struct of CMD's are hal_data + desc_id + payload_len*/ + desc_id = (*(unsigned int *)(cmd->data + HAL_PRIV_DATA_SIZE)) & + 0x0000FFFF; + + skb_queue_walk_safe(skb_list, skb, tmp) + { + frame_id = (desc_id * NUM_FRAMES_IN_TX_DESC) + pkt; + hal_tx_data = &hpriv->hal_tx_data[frame_id]; + tx_buf_info = &hpriv->tx_buf_info[frame_id]; + + hal_tx_data->data_len = tx_buf_info->dma_buf_len; + + dma_buf = tx_buf_info->dma_buf; + dma_buf -= uccp_ddr_base; + + hal_tx_data->address = dma_buf >> 2; + hal_tx_data->offset = dma_buf & 0x00000003; + pkt++; + } + + dcp_start_addr = HAL_GRAM_TX_DATA_START + + (desc_id * TX_DESC_HAL_SIZE); + + memcpy((void *)dcp_start_addr, + &hpriv->hal_tx_data[(desc_id * NUM_FRAMES_IN_TX_DESC)], + TX_DESC_HAL_SIZE); + } + + hostport_send(hpriv, nwb); + +} + +static void recv_tasklet_fn(unsigned long data) +{ + + struct hal_priv *priv = (struct hal_priv *)data; + struct sk_buff *skb; + + while ((skb = skb_dequeue(&priv->refillq))) { + /* As we refilled the buffers, now pass them UP */ + priv->rcv_handler(skb, LMAC_MOD_ID); + } +} + +static void rx_tasklet_fn(unsigned long data) +{ + struct hal_priv *priv = (struct hal_priv *)data; + struct sk_buff *skb; + unsigned char *buf; + unsigned long temp; + unsigned char *nbuff; + struct event_hal *evnt; + struct cmd_hal cmd_rx; + struct sk_buff *nbuf, *rx_skb; + unsigned char *cmd_data; + unsigned int payload_length, length, data_length; + void __iomem *src_ptr; + int count = 0; + unsigned int pkt_desc = 0, max_data_size = MAX_DATA_SIZE_2K; + dma_addr_t dma_buf = 0; + unsigned long event_addr, event_status_addr, event_len; +#ifdef PERF_PROFILING + struct timeval tv_start, tv_now, full_tv_start, full_tv_now; + long usec_diff = 0, full_usec_diff = 0; +#endif + struct buf_info *rx_buf_info = NULL; + struct buf_info temp_rx_buf_info; + struct sk_buff *new_skb; + + while ((skb = skb_dequeue(&priv->rxq))) { + event_addr = *((unsigned long *)(skb->cb)); + event_status_addr = *((unsigned long *)(skb->cb + 4)); + event_len = *(unsigned long *)(skb->cb + 8); + + /* Range check */ + if (skb->len > event_len) { + pr_err("%s: Err! skb->len=%d, event_len =%d\n", + __func__, skb->len, (int)event_len); + dev_kfree_skb_any(skb); + continue; + } + + temp = event_addr; + buf = skb_put(skb, event_len); + memcpy(buf, (unsigned char *)temp, skb->len); + + /* Mark the buffer free */ + temp = event_status_addr; + + UCCP_DEBUG_HAL("%s: Freeing event buffer at 0x%08x\n", + hal_name, (unsigned int)temp); + + *((unsigned long *)temp) = 0; + + rx_cnt++; + UCCP_DEBUG_HAL("%s:rx_cnt=%ld cmd_cnt=0x%X event_cnt=0x%X\n", + hal_name, rx_cnt, priv->cmd_cnt, priv->event_cnt); + if (DUMP_HAL) { + UCCP_DEBUG_HAL("%s: recv dump\n", hal_name); + UCCP_DEBUG_DUMP_HAL(" ", DUMP_PREFIX_NONE, 16, 1, + skb->data, skb->len, 1); + } + nbuff = skb->data; + evnt = (struct event_hal *)nbuff; + + /* Message from HAL after the DMA completion, + * Fetch the buffer addrs from UCCP HOST RAM + * Copy them to the skb + * Pass them up + * Refresh the RX descriptor in firmware + */ + if (evnt->hdr.id == 0xffffffff) { + /* HAL_INTERNAL CMD */ + memset(&cmd_rx, 0, sizeof(struct cmd_hal)); + + if (!CHECK_RX_PKT_CNT(evnt->rx_pkt_cnt)) { + /* Range check */ + pr_err("%s: Error!!! rx_pkt_cnt = %d\n", + __func__, evnt->rx_pkt_cnt); + dev_kfree_skb_any(skb); + continue; + } + +#ifdef PERF_PROFILING + do_gettimeofday(&full_tv_start); + + /* HAL Profile Stat: Rx Pkts per HAL internal Event */ + rx_pkts_halint_event[rx_pkt_index] = evnt->rx_pkt_cnt; + rx_pkt_index = (rx_pkt_index + 1) % 20; +#endif + for (count = 0; count < evnt->rx_pkt_cnt; count++) { + pkt_desc = evnt->rx_pkt_desc[count]; + + /* Range check */ + if (!CHECK_PKT_DESC(pkt_desc)) { + pr_err("%s: Error!!! pkt_desc = %d\n", + __func__, pkt_desc); + + /* Drop all the remaining buffers: As + * per Design They will not be reclaimed + * by FW. + */ + break; + } + + if (pkt_desc < hpriv->rx_bufs_12k) + max_data_size = MAX_DATA_SIZE_12K; + + if (hpriv->rx_buf_info == NULL) + break; + + rx_buf_info = hpriv->rx_buf_info + pkt_desc; + + memcpy(&temp_rx_buf_info, + rx_buf_info, + sizeof(struct buf_info)); + + dma_unmap_single(NULL, + rx_buf_info->dma_buf, + rx_buf_info->dma_buf_len, + DMA_FROM_DEVICE); + + dma_buf = rx_buf_info->dma_buf; + src_ptr = rx_buf_info->src_ptr; + + + UCCP_DEBUG_HAL("%s: dma_buf = 0x%08X\n", + hal_name, + (unsigned int)dma_buf); + + UCCP_DEBUG_HAL("%s: src_ptr = 0x%08X\n", + hal_name, + (unsigned int)src_ptr); + + if (DUMP_HAL) { + UCCP_DEBUG_HAL("DMA data dump:"); + UCCP_DEBUG_HAL(" size=200\n"); + UCCP_DEBUG_DUMP_HAL(" ", + DUMP_PREFIX_NONE, 16, + 1, src_ptr, 200, 1); + } + + /* Offset in UMAC_LMAC_MSG_HDR, points to + * payload_length + */ + + /* 802.11hdr + payload Len*/ + payload_length = *(((unsigned int *)src_ptr) + + 3); + length = *(((unsigned int *)src_ptr) + 5); + + /* Control Info Len*/ + data_length = payload_length + length; + + /* Complete data length to be copied */ + UCCP_DEBUG_HAL("%s: Payload Len =%d(0x%x), ", + hal_name, + payload_length, + payload_length); + + UCCP_DEBUG_HAL("Len=%d(0x%x), ", + length, + length); + + UCCP_DEBUG_HAL("Data Len = %d(0x%x)\n", + data_length, + data_length); + + if (data_length > max_data_size) { + pr_err("Max length exceeded:"); + pr_err(" payload_len: %d len:%d", + payload_length, + length); + pr_err(" data_len:%d desc:%d\n", + data_length, + pkt_desc); + + + pr_err("Event from LMAC:"); + print_hex_dump(KERN_DEBUG, + "", + DUMP_PREFIX_NONE, + 16, + 1, + skb->data, + skb->len, + 1); + + pr_err("DMA Data from LMAC:"); + print_hex_dump(KERN_DEBUG, + "", + DUMP_PREFIX_NONE, + 16, + 1, + src_ptr, + 200, + 1); + + /* Do not send the packet UP, + * just refill the buffer + * and give it to HW, for + * non-DMA case give the same + * buffer. + */ + dma_map_single(NULL, + rx_buf_info->src_ptr, + max_data_size, + DMA_FROM_DEVICE); + cmd_rx.rx_pkt_data.rx_pkt_cnt++; + cmd_rx.rx_pkt_data.rx_pkt[count].desc = + evnt->rx_pkt_desc[count]; + cmd_rx.rx_pkt_data.rx_pkt[count].ptr = + dma_buf - uccp_ddr_base; + continue; + } + + new_skb = alloc_skb(max_data_size, GFP_ATOMIC); + + if (!new_skb) { + /* If allocation fails, drop the packet, + * continue + */ + memcpy(rx_buf_info, + &temp_rx_buf_info, + sizeof(struct buf_info)); + + dma_map_single(NULL, + rx_buf_info->src_ptr, + max_data_size, + DMA_FROM_DEVICE); + + dma_buf = rx_buf_info->dma_buf; + } else { + rx_skb = temp_rx_buf_info.skb; + + if (temp_rx_buf_info.dma_buf_priv) { + memcpy(skb_put(rx_skb, + data_length), + src_ptr, + data_length); + + } else { + skb_put(rx_skb, data_length); + } + + init_rx_buf(pkt_desc, max_data_size, + &dma_buf, new_skb); + skb_queue_tail(&hpriv->refillq, rx_skb); + } + + cmd_rx.rx_pkt_data.rx_pkt_cnt++; + cmd_rx.rx_pkt_data.rx_pkt[count].desc = + evnt->rx_pkt_desc[count]; + cmd_rx.rx_pkt_data.rx_pkt[count].ptr = + dma_buf - uccp_ddr_base; + + } + + if (cmd_rx.rx_pkt_data.rx_pkt_cnt != 0) { + cmd_rx.hdr.id = 0xffffffff; + + /* Inform HAL about the newly allocated + * buffers + */ + nbuf = alloc_skb(sizeof(struct cmd_hal), + GFP_ATOMIC); + if (nbuf) { + cmd_data = skb_put(nbuf, + sizeof(struct cmd_hal)); + + memcpy(cmd_data, + (unsigned char *)&cmd_rx, + sizeof(struct cmd_hal)); + hal_cmd_sent--; + hostport_send_head(hpriv, nbuf); + + } + } + +#ifdef PERF_PROFILING + do_gettimeofday(&full_tv_now); + + if ((full_tv_now.tv_sec - full_tv_start.tv_sec) == 0) { + full_usec_diff = full_tv_now.tv_usec - + full_tv_start.tv_usec; + } else { + /* Exceeding the second */ + full_usec_diff = full_tv_now.tv_usec + + (((1000 * 1000) - + full_tv_start.tv_usec) + 1); + } + + spin_lock_irqsave(&timing_lock, pflags); + + halint_event_handling_time[halint_handling_index] = + full_usec_diff; + + halint_handling_index = (halint_handling_index + + 1) % 20; + + spin_unlock_irqrestore(&timing_lock, pflags); + + /* Start the Timer for RCV Handler Profiling */ + do_gettimeofday(&tv_start); +#endif + tasklet_schedule(&priv->recv_tasklet); +#ifdef PERF_PROFILING + do_gettimeofday(&tv_now); + + if ((tv_now.tv_sec - tv_start.tv_sec) == 0) { + usec_diff = tv_now.tv_usec - tv_start.tv_usec; + } else { + /* exceeding the second */ + usec_diff = tv_now.tv_usec + + (((1000 * 1000) - + tv_start.tv_usec) + 1); + } + + spin_lock_irqsave(&timing_lock, pflags); + + rcv_hdlr_time[rcv_hdlr_index] = usec_diff; + rcv_hdlr_index = (rcv_hdlr_index + 1)%20; + + spin_unlock_irqrestore(&timing_lock, pflags); +#endif + /* Internal CMD, Free it */ + dev_kfree_skb_any(skb); + + } else { + /* MSG from LMAC, non-data*/ + hal_event_recv++; + priv->rcv_handler(skb, LMAC_MOD_ID); + } + } +} + + +static void hal_register_callback(msg_handler handler, + unsigned char mod_id) +{ + hpriv->rcv_handler = handler; +} + + +static irqreturn_t hal_irq_handler(int irq, void *p) +{ + + unsigned int value; + unsigned long event_addr, event_status_addr, event_len; + unsigned char spurious; + struct sk_buff *skb; + struct hal_priv *priv = (struct hal_priv *)p; +#ifdef PERF_PROFILING + long usec_diff; + struct timeval tv_start, tv_now; +#endif + int is_err = 0; + + spurious = 0; + + value = readl((void __iomem *)(UCCP_CORE_TO_HOST_CMD_ADDR)) & + 0x7fffffff; + if (value == (0x7fff0000 | priv->event_cnt)) { +#ifdef PERF_PROFILING + do_gettimeofday(&tv_start); +#endif +#ifdef CONFIG_PM + rx_interrupt_status = 1; +#endif + event_addr = readl((void __iomem *)HAL_GRAM_EVENT_START); + event_status_addr = readl((void __iomem *)(HAL_GRAM_EVENT_START + + 4)); + event_len = readl((void __iomem *)(HAL_GRAM_EVENT_START + 8)); + + /* Range check */ + if (!(CHECK_EVENT_ADDR_UCCP(event_addr)) || + !(CHECK_EVENT_STATUS_ADDR_UCCP(event_status_addr)) || + !CHECK_EVENT_LEN(event_len)) { + pr_err("%s: Error!!! event_addr = 0x%08x\n", + __func__, + (unsigned int)event_addr); + + pr_err("%s: Error!!! event_len =%d\n", + __func__, + (int)event_len); + + pr_err("%s: Error!!! event_status_addr = 0x%08x\n", + __func__, + (unsigned int)event_status_addr); + + is_err = 1; + } + UCCP_DEBUG_HAL("%s: event address = 0x%08x\n", + hal_name, + (unsigned int)event_addr); + UCCP_DEBUG_HAL("%s: event status address = 0x%08x\n", + hal_name, + (unsigned int)event_status_addr); + UCCP_DEBUG_HAL("%s: event len = %d\n", + hal_name, + (int)event_len); + + if (unlikely(is_err)) { + /* If addr is valid try to clear */ + if (CHECK_EVENT_STATUS_ADDR_UCCP(event_status_addr)) { + event_status_addr -= HAL_UCCP_GRAM_BASE; + event_status_addr += ((priv->gram_mem_addr) - + (priv->shm_offset)); + *((unsigned long *)event_status_addr) = 0; + } else + pr_err("%s: UCCP status addr invalid, not clearing it\n", + hal_name); + + return IRQ_HANDLED; + } + + event_addr -= HAL_UCCP_GRAM_BASE; + event_status_addr -= HAL_UCCP_GRAM_BASE; + event_addr += ((priv->gram_mem_addr) - (priv->shm_offset)); + event_status_addr += ((priv->gram_mem_addr) - + (priv->shm_offset)); + + skb = dev_alloc_skb(event_len); + + if (!skb) { + *((unsigned long *)event_status_addr) = 0; + } else { + *(unsigned long *)(skb->cb) = event_addr; + + /* Address of event payload */ + *(unsigned long *)(skb->cb + 4) = event_status_addr; + + /* Address to mark free */ + *(unsigned long *)(skb->cb + 8) = event_len; + + /* Length of event payload */ + skb_queue_tail(&priv->rxq, skb); + tasklet_schedule(&priv->rx_tasklet); + } + + priv->event_cnt++; + } else { + spurious = 1; + } + + if (!spurious) { + /* Clear the uccp interrupt */ + value = 0; + value |= BIT(UCCP_CORE_INT_CLR_SHIFT); + writel(*((unsigned long *)&(value)), + (void __iomem *)(HOST_TO_UCCP_CORE_ACK_ADDR)); + } else { + pr_warn("%s: Spurious interrupt received\n", hal_name); + + } + +#ifdef PERF_PROFILING + do_gettimeofday(&tv_now); + + if ((tv_now.tv_sec - tv_start.tv_sec) == 0) { + usec_diff = tv_now.tv_usec - tv_start.tv_usec; + } else { + /* Exceeding the second */ + usec_diff = tv_now.tv_usec + + (((1000 * 1000) - tv_start.tv_usec) + 1); + } + + spin_lock_irqsave(&timing_lock, pflags); + + irq_timestamp[irq_ts_index] = usec_diff; + irq_ts_index = (irq_ts_index + 1)%20; + + spin_unlock_irqrestore(&timing_lock, pflags); +#endif + return IRQ_HANDLED; +} + + +static void hal_enable_int(void *p) +{ + unsigned int value = 0; + + /* Set external pin irq enable for host_irq and uccp_irq */ + value = readl((void __iomem *)UCCP_CORE_INT_ENAB_ADDR); + value |= BIT(UCCP_CORE_INT_IRQ_ENAB_SHIFT); + + writel(*((unsigned long *)&(value)), + (void __iomem *)(UCCP_CORE_INT_ENAB_ADDR)); + + /* Enable raising uccp_int when UCCP_INT = 1 */ + value = 0; + value |= BIT(UCCP_CORE_INT_EN_SHIFT); + writel(*((unsigned long *)&(value)), + (void __iomem *)(UCCP_CORE_INT_ENABLE_ADDR)); +} + + +static void hal_disable_int(void *p) +{ + unsigned int value = 0; + + /* Reset external pin irq enable for host_irq and uccp_irq */ + value = readl((void __iomem *)UCCP_CORE_INT_ENAB_ADDR); + value &= ~(BIT(UCCP_CORE_INT_IRQ_ENAB_SHIFT)); + writel(*((unsigned long *)&(value)), + (void __iomem *)(UCCP_CORE_INT_ENAB_ADDR)); + + /* Disable raising uccp_int when UCCP_INT = 1 */ + value = 0; + value &= ~(BIT(UCCP_CORE_INT_EN_SHIFT)); + writel(*((unsigned long *)&(value)), + (void __iomem *)(UCCP_CORE_INT_ENABLE_ADDR)); +} + + +#ifdef PERF_PROFILING +static int ulong_cmp(const void *a, const void *b) +{ + return *(unsigned long *)a - *(unsigned long *)b; +} + + +static int avg_array(unsigned long *arr, unsigned int max_index) +{ + unsigned int index; + unsigned int avg = 0; + + if (!max_index) + return 0; + + for (index = 0; index < max_index; index++) + avg += arr[index]; + + return avg/max_index; +} + + +static int max_array(unsigned long *arr, unsigned int max_index) +{ + sort(arr, max_index, sizeof(unsigned long), ulong_cmp, NULL); + return arr[max_index-1]; +} +#endif + + +static int proc_write_hal_stats(struct file *file, + const char __user *buffer, + size_t count, + loff_t *ppos) +{ + char buf[50]; + unsigned long val; + + if (count >= sizeof(buf)) + count = sizeof(buf)-1; + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + buf[count] = '\0'; + + if (param_get_val(buf, "get_gram_dump=", &val)) + hal_get_dump_gram(&val); + else if (param_get_val(buf, "get_core_dump=", &val)) + hal_get_dump_core(&val, 0); + else if (param_get_val(buf, "get_perip_dump=", &val)) + hal_get_dump_perip(&val); + else if (param_get_val(buf, "get_sysbus_dump=", &val)) + hal_get_dump_sysbus(&val); + return count; +} + +static int proc_read_hal_stats(struct seq_file *m, void *v) +{ +#ifdef PERF_PROFILING + int index, max_index = 20; + + seq_puts(m, "************* Host HAL Stats ***********\n"); + + seq_printf(m, "IRQ TIME: AVG: %d, MAX: %d\n", + avg_array(irq_timestamp, 20), + max_array(irq_timestamp, 20)); + + for (index = 0; index < max_index; index++) + seq_printf(m, "IRQ[%d] = %ld\n", + index, + irq_timestamp[index]); + + seq_printf(m, "RCV Handler TIME: AVG: %d, MAX: %d\n", + avg_array(rcv_hdlr_time, 20), + max_array(rcv_hdlr_time, 20)); + + for (index = 0; index < max_index; index++) + seq_printf(m, "RH[%d] = %ld\n", + index, + rcv_hdlr_time[index]); + + seq_printf(m, "Packetx Rx with HAL Internal: AVG: %d, MAX: %d\n", + avg_array(rx_pkts_halint_event, 20), + max_array(rx_pkts_halint_event, 20)); + + for (index = 0; index < max_index; index++) + seq_printf(m, "RXPKT[%d] = %ld\n", + index, + rx_pkts_halint_event[index]); + + seq_printf(m, "HAL Internal Event Handling Time: AVG: %d, MAX: %d\n", + avg_array(halint_event_handling_time, 20), + max_array(halint_event_handling_time, 20)); + + for (index = 0; index < max_index; index++) + seq_printf(m, "HALINT[%d] = %ld\n", + index, + halint_event_handling_time[index]); + +#endif + + seq_printf(m, "Alloc SKB Failures: %d\n", + alloc_skb_failures); + + seq_printf(m, "Alloc SKB in 60 MB DMA Region %d\n", + alloc_skb_dma_region); + + seq_printf(m, "Alloc SKB in Priv 4 MB Region: %d\n", + alloc_skb_priv_region); + + seq_printf(m, "Alloc SKB Run time: %d\n", alloc_skb_priv_runtime); + + seq_printf(m, "hal_cmd_sent_cnt: %d\n", + hal_cmd_sent); + + seq_printf(m, "hal_event_recv_cnt: %d\n", + hal_event_recv); + + return 0; +} + + +static int proc_open_hal_stats(struct inode *inode, struct file *file) +{ + return single_open(file, proc_read_hal_stats, NULL); +} + + +static const struct file_operations params_fops_hal_stats = { + .open = proc_open_hal_stats, + .read = seq_read, + .llseek = seq_lseek, + .write = proc_write_hal_stats, + .release = single_release +}; + + +static int hal_proc_init(struct proc_dir_entry *hal_proc_dir_entry) +{ + struct proc_dir_entry *entry; + int err = 0; + + entry = proc_create("hal_stats", + 0444, + hal_proc_dir_entry, + ¶ms_fops_hal_stats); + + if (!entry) { + pr_err("Failed to create HAL proc entry\n"); + err = -ENOMEM; + } + + return err; +} + + +#ifdef PERF_PROFILING +static void stats_timer_expiry(unsigned long data) +{ + if (alloc_skb_dma_region) { + pr_info("Alloc SKB in 60 MB DMA Region %d\n", + alloc_skb_dma_region); + + alloc_skb_dma_region = 0; + } + + if (alloc_skb_priv_region) { + pr_info("Alloc SKB in Priv 4 MB Region: %d\n", + alloc_skb_priv_region); + alloc_skb_priv_region = 0; + } + + if (alloc_skb_failures) { + pr_info("Alloc SKB Failures: %d\n", + alloc_skb_failures); + alloc_skb_failures = 0; + } + + if (alloc_skb_priv_runtime) { + pr_info("Alloc SKB Run time: %d\n", + alloc_skb_priv_runtime); + alloc_skb_priv_runtime = 0; + } + + mod_timer(&stats_timer, jiffies + msecs_to_jiffies(1000)); +} +#endif + + +int hal_start(void) +{ + +#ifdef PERF_PROFILING + init_timer(&stats_timer); + stats_timer.function = stats_timer_expiry; + stats_timer.data = (unsigned long) NULL; + mod_timer(&stats_timer, jiffies + msecs_to_jiffies(1000)); +#endif + hpriv->hal_disabled = 0; + + /* Enable host_int and uccp_int */ + hal_enable_int(NULL); + + return 0; +} + + +int hal_stop(void) +{ + /* Disable host_int and uccp_irq */ + hal_disable_int(NULL); + return 0; +} + + +static int chg_irq_register(int val) +{ + UCCP_DEBUG_HAL("%s: change irq regist state %s.\n", + hal_name, ((val == 1) ? "ON" : "OFF")); + + if (val == 0) { + /* Unregister irq handler */ + free_irq(hpriv->irq, hpriv); + + } else if (val == 1) { + /* Register irq handler */ + if (request_irq(hpriv->irq, + hal_irq_handler, + IRQF_NO_SUSPEND, + "wlan", + hpriv) != 0) { + return -1; + } + } + + return 0; +} + +static inline int conv_str_to_byte(unsigned char *byte, + unsigned char *str, + int len) +{ + int i, j = 0; + unsigned char ch, val = 0; + + for (i = 0; i < (len * 2); i++) { + /*convert to lower*/ + ch = ((str[i] >= 'A' && str[i] <= 'Z') ? str[i] + 32 : str[i]); + + if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f')) + return -1; + + if (ch >= '0' && ch <= '9') /*check is digit*/ + ch = ch - '0'; + else + ch = ch - 'a' + 10; + + val += ch; + + if (!(i%2)) + val <<= 4; + else { + byte[j] = val; + j++; + val = 0; + } + } + + return 0; +} + +/* Unmap and release all resoruces*/ +static int cleanup_all_resources(void) +{ + /* Unmap UCCP core memory */ + iounmap((void __iomem *)hpriv->uccp_sysbus_base_addr); + release_mem_region(hpriv->uccp_sysbus_base, hpriv->uccp_sysbus_len); + + /* Unmap UCCP perip memory */ + iounmap((void __iomem *)hpriv->uccp_perip_base_addr); + release_mem_region(hpriv->uccp_perip_base, hpriv->uccp_perip_len); + + /* Unmap GRAM */ + iounmap((void __iomem *)hpriv->gram_base_addr); + release_mem_region(hpriv->uccp_pkd_gram_base, + hpriv->uccp_pkd_gram_len); + + /* Unmap UCCP Host RAM */ + kfree(hpriv->base_addr_uccp_host_ram); + hpriv->base_addr_uccp_host_ram = NULL; + + kfree(hpriv); + return 0; +} + +static int uccp420_pltfr_probe(struct platform_device *pdev) +{ + struct resource *res; + int irq; + struct device_node *np = pdev->dev.of_node; + struct property *pp = NULL; + struct iio_channel *channels; + int ret; + int size; + + channels = iio_channel_get_all(&pdev->dev); + if (IS_ERR(channels)) + return PTR_ERR(channels); + + hpriv = kzalloc(sizeof(struct hal_priv), GFP_KERNEL); + if (!hpriv) + return -ENOMEM; + + irq = platform_get_irq_byname(pdev, "uccpirq"); + + hpriv->irq = irq; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uccp_sysbus_base"); + if (res == NULL) + return pr_err("No dts entry : uccp_sysbus_base"); + + hpriv->uccp_sysbus_base = res->start; + hpriv->uccp_sysbus_len = res->end - res->start + 1; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uccp_perip_base"); + if (res == NULL) + return pr_err("No dts entry : uccp_perip_base"); + + hpriv->uccp_perip_base = res->start; + hpriv->uccp_perip_len = res->end - res->start + 1; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uccp_pkd_gram_base"); + + if (res == NULL) + return pr_err("No dts entry : uccp_pkd_gram_base"); + + hpriv->uccp_pkd_gram_base = res->start; + hpriv->uccp_pkd_gram_len = res->end - res->start + 1; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uccp_gram_base"); + + if (res) { + hpriv->uccp_gram_base = res->start; + hpriv->uccp_gram_len = res->end - res->start + 1; + } + + pp = of_find_property(np, "mac-address0", NULL); + + if (pp && (pp->length == ETH_ALEN) && pp->value) + memcpy(&vif_macs[0], (void *)pp->value, ETH_ALEN); + else if (mac_addr == NULL) + mac_addr = DEFAULT_MAC_ADDRESS; + + pp = of_find_property(np, "mac-address1", NULL); + + if (pp && (pp->length == ETH_ALEN) && pp->value) + memcpy(&vif_macs[1], (void *)pp->value, ETH_ALEN); + + if (mac_addr != NULL) { + + conv_str_to_byte(vif_macs[0], mac_addr, ETH_ALEN); + + ether_addr_copy(vif_macs[1], vif_macs[0]); + + /* Set the Locally Administered bit*/ + vif_macs[1][0] |= 0x02; + + /* Increment the MSB by 1 (excluding 2 special bits)*/ + vif_macs[1][0] += (1 << 2); + } + + pp = of_find_property(np, "rf-params", &size); + + if (pp && pp->value) { + memcpy(rf_params, pp->value, size); + rf_params_vpd = rf_params; + } + + pp = of_find_property(np, "num_streams", &size); + + if (pp && pp->value) + num_streams_vpd = *((int *)pp->value); + + clk_prepare_enable(devm_clk_get(&pdev->dev, "rpu_core")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "rpu_l")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "rpu_v")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "rpu_sleep")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "wifi_adc")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "wifi_dac")); + + clk_prepare_enable(devm_clk_get(&pdev->dev, "event_timer")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "sys_event_timer")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "aux_adc")); + clk_prepare_enable(devm_clk_get(&pdev->dev, "aux_adc_internal")); + + /* To support suspend/resume (economy mode) + * during probe a wake up capable device will invoke + * the below routine with second parameter("can_wakeup" flag) + * set to 1. + */ + device_init_wakeup(&pdev->dev, 1); + + + ret = hal_ops.init(&pdev->dev); + + if (!ret) + UCCP_DEBUG_HAL("uccp420 wlan driver registration completed"); + + return ret; +} + +static int uccp420_pltfr_remove(struct platform_device *pdev) +{ + clk_disable_unprepare(devm_clk_get(&pdev->dev, "rpu_core")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "rpu_l")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "rpu_v")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "rpu_sleep")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "wifi_adc")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "wifi_dac")); + + clk_disable_unprepare(devm_clk_get(&pdev->dev, "event_timer")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "sys_event_timer")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "aux_adc")); + clk_disable_unprepare(devm_clk_get(&pdev->dev, "aux_adc_internal")); + + /* To support suspend/resume feature (economy mode) + * during remove a wake up capable device will invoke + * the below routine with second parameter("can_wakeup" flag) + * set to 0. + */ + device_init_wakeup(&pdev->dev, 0); + + return 0; +} + +static const struct of_device_id uccp420_dt_ids[] = { + { .compatible = "img,pistachio-uccp"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uccp420_dt_ids); + +struct platform_driver img_uccp_driver = { + .probe = uccp420_pltfr_probe, + .remove = uccp420_pltfr_remove, + .driver = { + .name = "uccp420", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(uccp420_dt_ids), + }, +}; + + + + +static int hal_deinit(void *dev) +{ + struct sk_buff *skb; + + (void)(dev); + + _uccp420wlan_80211if_exit(); + platform_driver_unregister(&img_uccp_driver); + + /* Free irq line */ + chg_irq_register(0); + + /* Kill the HAL tasklet */ + tasklet_kill(&hpriv->tx_tasklet); + tasklet_kill(&hpriv->rx_tasklet); + tasklet_kill(&hpriv->recv_tasklet); + while ((skb = skb_dequeue(&hpriv->rxq))) + dev_kfree_skb_any(skb); + + while ((skb = skb_dequeue(&hpriv->refillq))) + dev_kfree_skb_any(skb); + + while ((skb = skb_dequeue(&hpriv->txq))) + dev_kfree_skb_any(skb); + + cleanup_all_resources(); + + return 0; +} + + +static int hal_init(void *dev) +{ + struct proc_dir_entry *main_dir_entry; + int err = 0; + unsigned int value = 0; + unsigned char *rpusocwrap; + void __iomem *sixfour_mb_base; + unsigned int phys_64mb; + + (void) (dev); + + hpriv->shm_offset = shm_offset; + + if (hpriv->shm_offset != HAL_SHARED_MEM_OFFSET) + UCCP_DEBUG_HAL("%s: Using shared memory offset 0x%lx\n", + hal_name, hpriv->shm_offset); + + /* Map UCCP core memory */ + if (!(request_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len, + "uccp"))) { + pr_err("%s: request_mem_region failed for UCCP core region\n", + hal_name); + + kfree(hpriv); + return -ENOMEM; + } + + hpriv->uccp_sysbus_base_addr = (unsigned long)devm_ioremap(dev, + hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + + if (hpriv->uccp_sysbus_base_addr == 0) { + pr_err("%s: Ioremap failed for UCCP core mem region\n", + hal_name); + + release_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + kfree(hpriv); + + return -ENOMEM; + } + + hpriv->uccp_mem_addr = hpriv->uccp_sysbus_base_addr + + HAL_UCCP_CORE_REG_OFFSET; + + /* Map UCCP Perip memory */ + if (!(request_mem_region(hpriv->uccp_perip_base, + hpriv->uccp_perip_len, + "uccp"))) { + pr_err("%s: request_mem_region failed for UCCP perip region\n", + hal_name); + + kfree(hpriv); + return -ENOMEM; + } + + hpriv->uccp_perip_base_addr = + (unsigned long) devm_ioremap(dev, hpriv->uccp_perip_base, + hpriv->uccp_perip_len); + + if (hpriv->uccp_perip_base_addr == 0) { + pr_err("%s: Ioremap failed for UCCP perip mem region\n", + hal_name); + + iounmap((void __iomem *)hpriv->uccp_sysbus_base_addr); + release_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + release_mem_region(hpriv->uccp_perip_base, + hpriv->uccp_perip_len); + kfree(hpriv); + + return -ENOMEM; + } + + /* Map GRAM */ + if (!request_mem_region(hpriv->uccp_pkd_gram_base, + hpriv->uccp_pkd_gram_len, + "wlan_gram")) { + pr_err("%s: request_mem_region failed for GRAM\n", + hal_name); + + iounmap((void __iomem *)hpriv->uccp_sysbus_base_addr); + release_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + + kfree(hpriv); + + return -ENOMEM; + } + + hpriv->gram_base_addr = + (unsigned long)devm_ioremap(dev, hpriv->uccp_pkd_gram_base, + hpriv->uccp_pkd_gram_len); + if (hpriv->gram_base_addr == 0) { + pr_err("%s: Ioremap failed for g ram region.\n", + hal_name); + + iounmap((void __iomem *)hpriv->uccp_sysbus_base_addr); + release_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + release_mem_region(hpriv->uccp_pkd_gram_base, + hpriv->uccp_pkd_gram_len); + + kfree(hpriv); + + return -ENOMEM; + } + + hpriv->gram_mem_addr = hpriv->gram_base_addr + hpriv->shm_offset; + + hpriv->base_addr_uccp_host_ram = kmalloc(HAL_HOST_BOUNCE_BUF_LEN, + GFP_KERNEL); + + if (!hpriv->base_addr_uccp_host_ram) { + iounmap((void __iomem *)hpriv->uccp_sysbus_base_addr); + release_mem_region(hpriv->uccp_sysbus_base, + hpriv->uccp_sysbus_len); + + iounmap((void __iomem *)hpriv->gram_base_addr); + release_mem_region(hpriv->uccp_pkd_gram_base, + hpriv->uccp_pkd_gram_len); + + kfree(hpriv); + + return -ENOMEM; + } + + phys_64mb = virt_to_phys(hpriv->base_addr_uccp_host_ram); + + UCCP_DEBUG_HAL("%s: kmalloc success: %p an phy: 0x%x\n", + __func__, + hpriv->base_addr_uccp_host_ram, + phys_64mb); + + /* Program the 64MB base address to the RPU. + * RPU can access only 64MB starting from this + * address. + */ + sixfour_mb_base = get_base_address_64mb(phys_64mb); + + + rpusocwrap = (unsigned char *)(hpriv->uccp_sysbus_base_addr + 0x38000); + + value = ((unsigned int)sixfour_mb_base) / (4 * 1024); + uccp_ddr_base = value * (4 * 1024); + value = value << 10; + writel(value, rpusocwrap + 0x218); + + + if (hpriv->uccp_gram_base) { + + /* gram_b4_addr */ + if (!(request_mem_region(hpriv->uccp_gram_base, + hpriv->uccp_gram_len, + "uccp_gram_base"))) { + pr_err("%s:uccp_gram_base: request_mem_region failed\n", + hal_name); + + kfree(hpriv); + return -ENOMEM; + } + + hpriv->gram_b4_addr = + (unsigned long)devm_ioremap(dev, hpriv->uccp_gram_base, + hpriv->uccp_gram_len); + + if (hpriv->gram_b4_addr == 0) { + pr_err("%s: Ioremap failed for UCCP mem region\n", + hal_name); + + release_mem_region(hpriv->uccp_gram_base, + hpriv->uccp_gram_len); + kfree(hpriv); + + return -ENOMEM; + } + } + + /* Register irq handler */ + if (chg_irq_register(1)) { + pr_err("%s: Unable to register Interrupt handler with kernel\n", + hal_name); + + cleanup_all_resources(); + return -ENOMEM; + } + + /*Allocate space do update data pointers to DCP*/ + hpriv->hal_tx_data = kzalloc((NUM_TX_DESC * NUM_FRAMES_IN_TX_DESC * + sizeof(struct hal_tx_data)), GFP_KERNEL); + + if (!hpriv->hal_tx_data) + return -ENOMEM; + + /* Intialize HAL tasklets */ + tasklet_init(&hpriv->tx_tasklet, + tx_tasklet_fn, + (unsigned long)hpriv); + tasklet_init(&hpriv->rx_tasklet, + rx_tasklet_fn, + (unsigned long)hpriv); + tasklet_init(&hpriv->recv_tasklet, + recv_tasklet_fn, + (unsigned long)hpriv); + skb_queue_head_init(&hpriv->rxq); + skb_queue_head_init(&hpriv->txq); + skb_queue_head_init(&hpriv->refillq); +#ifdef PERF_PROFILING + spin_lock_init(&timing_lock); +#endif + + if (_uccp420wlan_80211if_init(&main_dir_entry) < 0) { + pr_err("%s: wlan_init failed\n", hal_name); + hal_deinit(NULL); + return -ENOMEM; + } + + err = hal_proc_init(main_dir_entry); + + if (err) + return err; + + hpriv->cmd_cnt = COMMAND_START_MAGIC; + hpriv->event_cnt = 0; + return 0; + +} + + +static void hal_deinit_bufs(void) +{ + int i = 0, j = 0; + struct buf_info *info = NULL; + + tasklet_disable(&hpriv->rx_tasklet); + tasklet_disable(&hpriv->recv_tasklet); + + if (hpriv->rx_buf_info) { + for (i = 0; i < hpriv->rx_bufs_2k + hpriv->rx_bufs_12k; i++) { + info = &hpriv->rx_buf_info[i]; + + if (info->dma_buf) { + dma_unmap_single(NULL, + info->dma_buf, + info->dma_buf_len, + DMA_FROM_DEVICE); + + info->dma_buf = 0; + info->dma_buf_len = 0; + } + + if (hpriv->rx_buf_info[i].skb) { + kfree_skb(hpriv->rx_buf_info[i].skb); + hpriv->rx_buf_info[i].skb = NULL; + } + } + + kfree(hpriv->rx_buf_info); + hpriv->rx_buf_info = NULL; + } + + if (hpriv->tx_buf_info) { + for (i = 0; i < hpriv->tx_bufs; i++) { + for (j = 0; i < NUM_FRAMES_IN_TX_DESC; i++) { + info = &hpriv->tx_buf_info[i + j]; + + if (info->dma_buf) { + dma_unmap_single(NULL, + info->dma_buf, + info->dma_buf_len, + DMA_TO_DEVICE); + + info->dma_buf = 0; + info->dma_buf_len = 0; + } + } + } + + kfree(hpriv->tx_buf_info); + hpriv->tx_buf_info = NULL; + } + + hpriv->hal_disabled = 1; + tasklet_enable(&hpriv->rx_tasklet); + tasklet_enable(&hpriv->recv_tasklet); +} + + +static int hal_init_bufs(unsigned int tx_bufs, + unsigned int rx_bufs_2k, + unsigned int rx_bufs_12k, + unsigned int tx_max_data_size) +{ + struct cmd_hal cmd_rx; + struct sk_buff *nbuf = NULL; + unsigned int count = 0, cmd_count = 0, pkt_desc = 0; + unsigned int rx_max_data_size; + dma_addr_t dma_buf = 0; + unsigned int cmd_buf_count = ((rx_bufs_2k + rx_bufs_12k) / + MAX_RX_BUF_PTR_PER_CMD); + int result = -1; + + hpriv->tx_bufs = tx_bufs; + hpriv->rx_bufs_2k = rx_bufs_2k; + hpriv->rx_bufs_12k = rx_bufs_12k; + hpriv->max_data_size = tx_max_data_size; + hpriv->tx_base_addr_uccp_host_ram = hpriv->base_addr_uccp_host_ram; + hpriv->rx_base_addr_uccp_host_ram = hpriv->base_addr_uccp_host_ram + + (tx_bufs * NUM_FRAMES_IN_TX_DESC * tx_max_data_size); + + if (((tx_bufs * NUM_FRAMES_IN_TX_DESC * tx_max_data_size) + + ((rx_bufs_2k * MAX_DATA_SIZE_2K + rx_bufs_12k * + MAX_DATA_SIZE_12K))) > HAL_HOST_BOUNCE_BUF_LEN) { + pr_err("%s Cannot accomodate tx_bufs: %d, frames/desc: %d and rx_bufs_2k: %d rx_bufs_12k: %d in %d UCCP Host RAM\n", + hal_name, tx_bufs, NUM_FRAMES_IN_TX_DESC, + rx_bufs_2k, rx_bufs_12k, HAL_HOST_BOUNCE_BUF_LEN); + + goto err; + } + + hpriv->rx_buf_info = kzalloc(((rx_bufs_2k + rx_bufs_12k) * + sizeof(struct buf_info)), GFP_KERNEL); + + if (!hpriv->rx_buf_info) { + pr_err("%s out of memory\n", hal_name); + goto err; + } + + hpriv->tx_buf_info = kzalloc((tx_bufs * NUM_FRAMES_IN_TX_DESC * + sizeof(struct buf_info)), + GFP_KERNEL); + + if (!hpriv->tx_buf_info) { + pr_err("%s out of memory\n", hal_name); + goto err; + } + + rx_max_data_size = MAX_DATA_SIZE_2K; + + for (cmd_count = 0; cmd_count < cmd_buf_count; cmd_count++) { + memset(&cmd_rx, 0, sizeof(struct cmd_hal)); + + UCCP_DEBUG_HAL("%s: Loop :%d: rx_max_data_size: %d\n", + hal_name, cmd_count, rx_max_data_size); + + for (count = 0; count < MAX_RX_BUF_PTR_PER_CMD; count++, + pkt_desc++) { + if (pkt_desc < hpriv->rx_bufs_12k) + rx_max_data_size = MAX_DATA_SIZE_12K; + + result = init_rx_buf(pkt_desc, + rx_max_data_size, + &dma_buf, + NULL); + + if (result) { + pr_err("%s Failed to initialize RX buf %d\n", + hal_name, pkt_desc); + goto err; + } + + cmd_rx.rx_pkt_data.rx_pkt_cnt++; + cmd_rx.rx_pkt_data.rx_pkt[count].desc = pkt_desc; + cmd_rx.rx_pkt_data.rx_pkt[count].ptr = dma_buf - + uccp_ddr_base; + } + + cmd_rx.hdr.id = 0xFFFFFFFF; + + nbuf = alloc_skb(sizeof(struct cmd_hal), GFP_ATOMIC); + + if (!nbuf) + goto err; + + memcpy(skb_put(nbuf, sizeof(struct cmd_hal)), + (unsigned char *)&cmd_rx, sizeof(struct cmd_hal)); + hal_cmd_sent--; + hostport_send_head(hpriv, nbuf); + } + + return 0; +err: + if (nbuf) { + kfree_skb(nbuf); + nbuf = NULL; + } + + hal_deinit_bufs(); + + return -1; +} + + +int hal_map_tx_buf(int pkt_desc, int frame_id, unsigned char *data, int len) +{ + unsigned int index = (pkt_desc * NUM_FRAMES_IN_TX_DESC) + frame_id; + void __iomem *tx_address = NULL; + int i, j; + dma_addr_t dma_buf = 0; + dma_addr_t curr_buf = 0; + + /* For QoS Null frames we dont try to map the frame since the data len + * will be 0 and there is nothing for the FW to process + */ + if (len == 0) + return 0; + + /* Sanity check */ + dma_buf = ((struct buf_info)(hpriv->tx_buf_info[index])).dma_buf; + + if (dma_buf) { + pr_err("%s: Already mapped pkt descriptor: %d and frame: %d dma_buf: 0x%x dma_buf: 0x%x index: %d\n", + __func__, + pkt_desc, + frame_id, + (unsigned int)hpriv->tx_buf_info[index].dma_buf, + (unsigned int)dma_buf, + index); + + for (i = 0; i < NUM_TX_DESC; i++) { + for (j = 0; j < NUM_FRAMES_IN_TX_DESC; j++) { + UCCP_DEBUG_HAL("%s: TX: descriptor: %d ", + __func__, i); + curr_buf = hpriv->tx_buf_info[i + j].dma_buf; + UCCP_DEBUG_HAL("and frame: %d dma_buf: 0x%x\n", + j, + curr_buf); + } + } + + for (i = 0; i < 80; i++) { + UCCP_DEBUG_HAL("%s: RX: descriptor: %d dma_buf: 0x%x\n", + __func__, + i, + hpriv->rx_buf_info[i].dma_buf); + } + + return -1; + } + + if (!is_mem_dma(data, len)) { + /* Copy SKB to the UCCP Private Area */ + tx_address = hpriv->tx_base_addr_uccp_host_ram + + (index * hpriv->max_data_size); + + memcpy(tx_address, data, len); + } else + tx_address = data; + + dma_buf = dma_map_single(NULL, + tx_address, + len, + DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(NULL, + dma_buf))) { + pr_err("%s Unable to map DMA on TX\n", hal_name); + return -1; + } + + hpriv->tx_buf_info[index].dma_buf = dma_buf; + + hpriv->tx_buf_info[index].dma_buf_len = len; + + return 0; +} + + +int hal_unmap_tx_buf(int pkt_desc, int frame_id) +{ + unsigned int index = (pkt_desc * NUM_FRAMES_IN_TX_DESC) + frame_id; + + /* For QoS Null frames we did not map the frame (since the data len + * will be 0 and there is nothing for the FW to process), hence no need + * to try and unmap + */ + if (!hpriv->tx_buf_info[index].dma_buf_len) + return 0; + + /* Sanity check */ + if (!hpriv->tx_buf_info[index].dma_buf) { + pr_err("%s called for unmapped pkt desc: %d , frame: %d\n", + __func__, pkt_desc, frame_id); + return -1; + } + + dma_unmap_single(NULL, + hpriv->tx_buf_info[index].dma_buf, + hpriv->tx_buf_info[index].dma_buf_len, + DMA_TO_DEVICE); + + memset(&hpriv->tx_buf_info[index], 0, sizeof(struct buf_info)); + + return 0; +} + + +static int is_mem_dma(void *virt_addr, int len) +{ + phys_addr_t phy_addr = 0; + + phy_addr = virt_to_phys(virt_addr); + + if (phy_addr >= uccp_ddr_base && + (phy_addr + len) < (uccp_ddr_base + + HAL_HOST_ZONE_DMA_LEN)) + return 1; + + return 0; +} + + +static int is_mem_bounce(void *virt_addr, int len) +{ + phys_addr_t phy_addr_start = 0; + phys_addr_t phy_addr = 0; + + phy_addr = virt_to_phys(virt_addr); + phy_addr_start = virt_to_phys(hpriv->base_addr_uccp_host_ram); + + if (phy_addr >= phy_addr_start && + (phy_addr + len) < (phy_addr_start + + HAL_HOST_BOUNCE_BUF_LEN)) + return 1; + + pr_warn("%s: Warning:Address is out of Bounce memory region\n", + hal_name); + + return 0; +} + + +static int init_rx_buf(int pkt_desc, + unsigned int max_data_size, + dma_addr_t *dma_buf, + struct sk_buff *new_skb) +{ + struct sk_buff *rx_skb = NULL; + void __iomem *src_ptr = NULL; + + memset(&hpriv->rx_buf_info[pkt_desc], 0, sizeof(struct buf_info)); + + if (new_skb == NULL) { + + rx_skb = alloc_skb(max_data_size, GFP_ATOMIC); + + if (!rx_skb) { + alloc_skb_failures++; + return -1; + } + } else + rx_skb = new_skb; + + if ((is_mem_dma(rx_skb->data, max_data_size))) { + src_ptr = rx_skb->data; + alloc_skb_dma_region++; + } else { + if (pkt_desc < hpriv->rx_bufs_12k) { + src_ptr = hpriv->rx_base_addr_uccp_host_ram + + (pkt_desc * MAX_DATA_SIZE_12K); + } else { + src_ptr = hpriv->rx_base_addr_uccp_host_ram + + (hpriv->rx_bufs_12k * MAX_DATA_SIZE_12K) + + ((pkt_desc - hpriv->rx_bufs_12k) * + MAX_DATA_SIZE_2K); + } + + if (!is_mem_bounce(src_ptr, max_data_size)) { + if (rx_skb) + dev_kfree_skb_any(rx_skb); + return -1; + } + + hpriv->rx_buf_info[pkt_desc].dma_buf_priv = 1; + alloc_skb_priv_region++; + } + + *dma_buf = dma_map_single(NULL, + src_ptr, + max_data_size, + DMA_FROM_DEVICE); + + if (unlikely(dma_mapping_error(NULL, + *dma_buf))) { + pr_err("%s Unable to map DMA on RX\n", hal_name); + + if (rx_skb) + dev_kfree_skb_any(rx_skb); + + return -1; + } + + hpriv->rx_buf_info[pkt_desc].skb = rx_skb; + hpriv->rx_buf_info[pkt_desc].src_ptr = src_ptr; + hpriv->rx_buf_info[pkt_desc].dma_buf = *dma_buf; + hpriv->rx_buf_info[pkt_desc].dma_buf_len = max_data_size; + + return 0; +} + +void hal_set_mem_region(unsigned int addr) +{ + +} + +void hal_request_mem_regions(unsigned char **gram_addr, + unsigned char **sysbus_addr, + unsigned char **gram_b4_addr) +{ + *gram_addr = (unsigned char *)hpriv->gram_base_addr; + *sysbus_addr = (unsigned char *)hpriv->uccp_sysbus_base_addr; + *gram_b4_addr = (unsigned char *)hpriv->gram_b4_addr; +} + +void hal_enable_irq_wake(void) +{ + enable_irq_wake(hpriv->irq); +} + +void hal_disable_irq_wake(void) +{ + disable_irq_wake(hpriv->irq); +} + + +struct hal_ops_tag hal_ops = { + .init = hal_init, + .deinit = hal_deinit, + .start = hal_start, + .stop = hal_stop, + .register_callback = hal_register_callback, + .send = hal_send, + .init_bufs = hal_init_bufs, + .deinit_bufs = hal_deinit_bufs, + .map_tx_buf = hal_map_tx_buf, + .unmap_tx_buf = hal_unmap_tx_buf, + .reset_hal_params = hal_reset_hal_params, + .set_mem_region = hal_set_mem_region, + .request_mem_regions = hal_request_mem_regions, + .enable_irq_wake = hal_enable_irq_wake, + .disable_irq_wake = hal_disable_irq_wake, + .get_dump_gram = hal_get_dump_gram, + .get_dump_core = hal_get_dump_core, + .get_dump_perip = hal_get_dump_perip, + .get_dump_sysbus = hal_get_dump_sysbus, + .get_dump_len = hal_get_dump_len, +}; + +#ifdef CONFIG_PM +static int host_suspend(void) +{ + if ((img_suspend_status == 1) && (rx_interrupt_status == 1)) { + pr_err("%s: Interrupt raised during Suspend, cancel suspend", + hal_name); + return -EBUSY; + } else { + return 0; + } +} +#else + #define host_suspend NULL +#endif + +static struct syscore_ops host_syscore_ops = { + .suspend = host_suspend, +}; + +static int __init hostport_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&img_uccp_driver); + register_syscore_ops(&host_syscore_ops); + + return ret; +} + +static void __exit hostport_exit(void) +{ + unregister_syscore_ops(&host_syscore_ops); + hal_ops.deinit(NULL); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Imagination Technologies"); +MODULE_DESCRIPTION("Driver for IMG UCCP420 WiFi solution"); + +module_init(hostport_init); +module_exit(hostport_exit); diff --git a/drivers/net/wireless/uccp420wlan/src/hal_hostport.h b/drivers/net/wireless/uccp420wlan/src/hal_hostport.h new file mode 100644 index 00000000000000..0f7419f960caab --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/hal_hostport.h @@ -0,0 +1,275 @@ +/* + * File Name : hal_hostport.h + * + * This file contains the definitions specific to HOSPORT comms + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _UCCP420WLAN_HAL_HOSTPORT_H_ +#define _UCCP420WLAN_HAL_HOSTPORT_H_ + +#include +#include + +#include + +#define _PACKED_ __attribute__((__packed__)) + +#define MAX_RX_BUF_PTR_PER_CMD (16) +#define MAX_DATA_SIZE_12K (12 * 1024) +#define MAX_DATA_SIZE_8K (8 * 1024) +#define MAX_DATA_SIZE_2K (2 * 1024) + +#define NUM_TX_DESC 12 +#define NUM_FRAMES_IN_TX_DESC 32 +#define NUM_BYTES_PER_FRAME 9 +#define TX_DESC_HAL_SIZE (NUM_FRAMES_IN_TX_DESC * NUM_BYTES_PER_FRAME) + +#if defined(__cplusplus) +extern "C" +{ +#endif /* __cplusplus */ + +struct buf_info { + dma_addr_t dma_buf; + void __iomem *src_ptr; + unsigned int dma_buf_len; + unsigned int dma_buf_priv; /* Is the DMA buffer in our private area */ + struct sk_buff *skb; +} _PACKED_; + +struct hal_tx_data { + unsigned int data_len:24; + unsigned long address:24; + unsigned long offset:24; +} _PACKED_; + +struct hal_priv { + /* UCCP Host RAM mappings*/ + void __iomem *base_addr_uccp_host_ram; + void __iomem *tx_base_addr_uccp_host_ram; + void __iomem *rx_base_addr_uccp_host_ram; + + /* UCCP and GRAM mappings */ + unsigned long uccp_mem_addr; + unsigned long gram_mem_addr; + unsigned long uccp_sysbus_base_addr; + unsigned long uccp_perip_base_addr; + unsigned long gram_base_addr; + unsigned long shm_offset; + unsigned long hal_disabled; + unsigned long gram_b4_addr; + + /* DTS entries */ + unsigned long uccp_sysbus_base; + unsigned long uccp_sysbus_len; /* HAL_HOST_UCCP_LEN */ + unsigned long uccp_perip_base; /* HAL_PERIP_BASE */ + unsigned long uccp_perip_len; /* HOST_PERIP_BASE_LEN */ + unsigned long uccp_pkd_gram_base; + unsigned long uccp_pkd_gram_len; + unsigned long uccp_gram_base; /* b4addr */ + unsigned long uccp_gram_len; /* b4addr length */ + + /* TX */ + struct sk_buff_head txq; + struct tasklet_struct tx_tasklet; + unsigned short cmd_cnt; + struct buf_info *tx_buf_info; + struct hal_tx_data *hal_tx_data; + + /* RX */ + struct sk_buff_head rxq; + struct tasklet_struct rx_tasklet; + struct tasklet_struct recv_tasklet; + unsigned short event_cnt; + msg_handler rcv_handler; + struct buf_info *rx_buf_info; + + /* Buffers info from IF layer*/ + unsigned int tx_bufs; + unsigned int rx_bufs_2k; + unsigned int rx_bufs_12k; + unsigned int max_data_size; + + /* Temp storage to refill first and process next*/ + struct sk_buff_head refillq; + int irq; + /* physical address mapping for address */ + unsigned long wifi_t0_addr; + unsigned long wifi_t1_addr; +}; + +struct hal_hdr { + /*! 0xffffffff - hal command or hal event + * 0x0 - lmac command or lmac event + */ + unsigned int id; + /*! Data pointer of commands with payload + * this field is valid only if descriptor id + * of command header is set to some value + * other. + */ + unsigned int data_ptr; +} _PACKED_; + +struct hal_rx_pkt_info { + /* Rx descriptor */ + unsigned int desc; + unsigned int ptr; +} _PACKED_; + +struct hal_rx_command { + unsigned int rx_pkt_cnt; + struct hal_rx_pkt_info rx_pkt[MAX_RX_BUF_PTR_PER_CMD]; +} _PACKED_; + +struct cmd_hal { + struct hal_hdr hdr; + struct hal_rx_command rx_pkt_data; +} _PACKED_; + +struct event_hal { + struct hal_hdr hdr; + unsigned int rx_pkt_cnt; + unsigned int rx_pkt_desc[16]; +} _PACKED_; + + +int _uccp420wlan_80211if_init(struct proc_dir_entry **); +void _uccp420wlan_80211if_exit(void); + +/*Porting information: + * HAL_UCCP_IRQ_LINE: This is the interrupt number assigned to UCCP host port + * interrupt. + * HAL_HOST_UCCP_RAM_START: This is the physical address of the start of + * Host RAM which is reserved for UCCP + * HAL_HOST_ZONE_DMA_START: This is the physical address of the start of 64MB + * ZONE_DMA area which is currently assigned a dummy + * value of 0xABABABAB. TSB needs to provide the actual + * value for this. + * + * These are the only values which need to be modified as per host memory + * map and interrupt configuration. + * The values for HAL_SHARED_MEM_OFFSET, HAL_WLAN_GRAM_LEN, HAL_COMMAND_OFFSET, + * and HAL_EVENT_OFFSET can be changed by IMG in future software releases. + */ + +#define HAL_HOST_UCCP_LEN 0x0003E800 +#define HAL_UCCP_GRAM_BASE 0xB7000000 + +#define HAL_UCCP_CORE_REG_OFFSET 0x400 + +/* Register HOST_TO_UCCP_CORE_CMD */ +#define HOST_TO_UCCP_CORE_CMD 0x0030 +#define HOST_TO_UCCP_CORE_CMD_ADDR ((hpriv->uccp_mem_addr) + \ + HOST_TO_UCCP_CORE_CMD) +#define UCCP_CORE_HOST_INT_SHIFT 31 + +/* Register UCCP_CORE_TO_HOST_CMD */ +#define UCCP_CORE_TO_HOST_CMD 0x0034 +#define UCCP_CORE_TO_HOST_CMD_ADDR ((hpriv->uccp_mem_addr) + \ + UCCP_CORE_TO_HOST_CMD) + +/* Register HOST_TO_UCCP_CORE_ACK */ +#define HOST_TO_UCCP_CORE_ACK 0x0038 +#define HOST_TO_UCCP_CORE_ACK_ADDR ((hpriv->uccp_mem_addr) + \ + HOST_TO_UCCP_CORE_ACK) +#define UCCP_CORE_INT_CLR_SHIFT 31 + +/* Register UCCP_CORE_TO_HOST_ACK */ +#define UCCP_CORE_TO_HOST_ACK 0x003C +#define UCCP_CORE_TO_HOST_ACK_ADDR ((hpriv->uccp_mem_addr) + \ + UCCP_CORE_TO_HOST_ACK) + +/* Register UCCP_CORE_INT_ENABLE */ +#define UCCP_CORE_INT_ENABLE 0x0044 +#define UCCP_CORE_INT_ENABLE_ADDR ((hpriv->uccp_mem_addr) + \ + UCCP_CORE_INT_ENABLE) +#define UCCP_CORE_INT_EN_SHIFT 31 + +#define UCCP_CORE_INT_ENAB 0x0000 +#define UCCP_CORE_INT_ENAB_ADDR ((hpriv->uccp_mem_addr) + UCCP_CORE_INT_ENAB) +#define UCCP_CORE_INT_IRQ_ENAB_SHIFT 15 + +/******************************************************************************/ +#define HAL_SHARED_MEM_OFFSET 0x45ffc +#define HAL_SHARED_MEM_MAX_MSG_SIZE 60 +#define HAL_WLAN_GRAM_LEN 0x1eac0 + +/* Command, Event, Tx Data and Buff mappping offsets */ +#define HAL_COMMAND_OFFSET (0) +#define HAL_EVENT_OFFSET (HAL_COMMAND_OFFSET + HAL_SHARED_MEM_MAX_MSG_SIZE) +#define HAL_TX_DATA_OFFSET (HAL_EVENT_OFFSET + HAL_SHARED_MEM_MAX_MSG_SIZE) + +#define HAL_GRAM_CMD_START ((hpriv->gram_mem_addr) + HAL_COMMAND_OFFSET) +#define HAL_GRAM_EVENT_START ((hpriv->gram_mem_addr) + HAL_EVENT_OFFSET) +#define HAL_GRAM_TX_DATA_START ((hpriv->gram_mem_addr) + HAL_TX_DATA_OFFSET) + +#define HAL_GRAM_CMD_LEN (HAL_GRAM_CMD_START + 8) +#define HAL_GRAM_TX_DATA_LEN (HAL_GRAM_TX_DATA_START + 0) +#define HAL_GRAM_TX_DATA_OFFSET (HAL_GRAM_TX_DATA_START + 3) +#define HAL_GRAM_TX_DATA_ADDR (HAL_GRAM_TX_DATA_START + 6) + +#define HAL_HOST_BOUNCE_BUF_LEN (4 * 1024 * 1024) +#define HAL_HOST_NON_BOUNCE_BUF_LEN (60 * 1024 * 1024) + +/* Shift the amount of movement of each the offset */ + +/* 32bit - valid offset 8bit - start pos 0bit*/ +#define WLN_CGN_HOST_SYS_ADDR_SHIFT (24) + +#define HAL_HOST_ZONE_DMA_START 0xABABABAB +#define HAL_HOST_ZONE_DMA_LEN (64 * 1024 * 1024) + +/*RPU DUMP Regions and Commands*/ +#define UCCP_REGION_TYPE_COREA 0 +#define UCCP_REGION_TYPE_COREB 1 + +/* fwldr.c converts these to HOST addresses + * so pass RPU addresses here. + * From: uccrunTime/Platform/configs + */ +#define UCCP_COREA_REGION_START 0x80880000 +#define UCCP_COREA_REGION_LEN 0x4C000 + +#define UCCP_COREB_REGION_START 0x82000000 +#define UCCP_COREB_REGION_LEN 0x4C000 + +/* Interrupt number assigned to UCCP host port interrupt */ +#define HAL_IRQ_LINE 74 + +enum hal_rpu_testmode_cmd { + HAL_RPU_TM_CMD_ALL = 0, + HAL_RPU_TM_CMD_GRAM = 1, + HAL_RPU_TM_CMD_COREA = 2, + HAL_RPU_TM_CMD_COREB = 3, + HAL_RPU_TM_CMD_PERIP = 4, + HAL_RPU_TM_CMD_SYSBUS = 5, +}; + +int reset_hal_params(void); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* _UCCP420WLAN_HAL_HOSTPORT_H_ */ + +/* EOF */ diff --git a/drivers/net/wireless/uccp420wlan/src/tx.c b/drivers/net/wireless/uccp420wlan/src/tx.c new file mode 100644 index 00000000000000..e81ffcc0dac1d2 --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/tx.c @@ -0,0 +1,2044 @@ +/* + * File Name : tx.c + * + * This file contains the source functions UMAC TX logic + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "core.h" + +#define TX_TO_MACDEV(x) ((struct mac80211_dev *) \ + (container_of(x, struct mac80211_dev, tx))) + +static void wait_for_tx_complete(struct tx_config *tx) +{ + int count = 0; + struct mac80211_dev *dev = NULL; + + /* Find_last_bit: Returns the bit number of the first set bit, + * or size. + */ + while (find_last_bit(tx->buf_pool_bmp, + NUM_TX_DESCS) != NUM_TX_DESCS) { + count++; + + if (count < TX_COMPLETE_TIMEOUT_TICKS) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + } else { + dev = TX_TO_MACDEV(tx); + + UCCP_DEBUG_TX("%s-UMACTX:WARNING: ", dev->name); + UCCP_DEBUG_TX("TX complete failed!!\n"); + UCCP_DEBUG_TX("%s-UMACTX:After ", dev->name); + UCCP_DEBUG_TX("%ld: bitmap is: 0x%lx\n", + TX_COMPLETE_TIMEOUT_TICKS, + tx->buf_pool_bmp[0]); + break; + } + } + + if (count && (count < TX_COMPLETE_TIMEOUT_TICKS)) { + UCCP_DEBUG_TX("%s-UMACTX:TX complete after %d timer ticks\n", + dev->name, count); + } +} + +int tx_queue_map(int queue) +{ + unsigned int ac[4] = {WLAN_AC_VO, WLAN_AC_VI, WLAN_AC_BE, WLAN_AC_BK}; + + if (queue < 4) + return ac[queue]; + + return WLAN_AC_VO; +} + +int tx_queue_unmap(int queue) +{ + unsigned int ac[4] = {3, 2, 1, 0}; + + return ac[queue]; +} + +static void update_aux_adc_voltage(struct mac80211_dev *dev, + unsigned char pdout) +{ + static unsigned int index; + + if (index > MAX_AUX_ADC_SAMPLES) + index = 0; + + dev->params->pdout_voltage[index++] = pdout; +} + +static int check_80211_aggregation(struct mac80211_dev *dev, + struct sk_buff *skb, + int ac, +#ifdef MULTI_CHAN_SUPPORT + int off_chanctx_idx, +#endif + int peer_id) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *mac_hdr = NULL, *mac_hdr_first = NULL; + struct sk_buff *skb_first; + struct sk_buff_head *pend_pkt_q = NULL; + struct tx_config *tx = &dev->tx; + bool ampdu = false, is_qos = false, addr = true; + + mac_hdr = (struct ieee80211_hdr *)skb->data; +#ifdef MULTI_CHAN_SUPPORT + pend_pkt_q = &tx->pending_pkt[off_chanctx_idx][peer_id][ac]; +#else + pend_pkt_q = &tx->pending_pkt[peer_id][ac]; +#endif + skb_first = skb_peek(pend_pkt_q); + if (skb_first) + mac_hdr_first = (struct ieee80211_hdr *)skb_first->data; + + ampdu = (tx_info->flags & IEEE80211_TX_CTL_AMPDU) ? true : false; + is_qos = ieee80211_is_data_qos(mac_hdr->frame_control); + + /* RPU has a limitation, it expects A1-A2-A3 to be same + * for all MPDU's within an AMPDU. This is a temporary + * solution, remove it when RPU has fix for this. + */ + if (skb_first && + ((!ether_addr_equal(mac_hdr->addr1, + mac_hdr_first->addr1)) || + (!ether_addr_equal(mac_hdr->addr2, + mac_hdr_first->addr2)) || + (!ether_addr_equal(mac_hdr->addr3, + mac_hdr_first->addr3)))) { + addr = false; + } + + /*stats and debug*/ + if (!is_qos) { + UCCP_DEBUG_TX("Not Qos\n"); + dev->stats->tx_noagg_not_qos++; + } else if (!ampdu) { + UCCP_DEBUG_TX("Not AMPDU\n"); + dev->stats->tx_noagg_not_ampdu++; + } else if (!addr) { + if (skb_first) { + UCCP_DEBUG_TX("first: A1: %pM-A2:%pM -A3%pM not same\n", + mac_hdr_first->addr1, + mac_hdr_first->addr2, + mac_hdr_first->addr3); + UCCP_DEBUG_TX("curr: A1: %pM-A2:%pM -A3%pM not same\n", + mac_hdr->addr1, + mac_hdr->addr2, + mac_hdr->addr3); + } + dev->stats->tx_noagg_not_addr++; + } + + return (ampdu && is_qos && addr); +} + +static void tx_status(struct sk_buff *skb, + struct umac_event_tx_done *tx_done, + unsigned int frame_idx, + struct mac80211_dev *dev, + struct ieee80211_tx_info tx_info_1st_mpdu) +{ + int index, i; + char idx = 0; + struct ieee80211_tx_rate *txrate; + struct ieee80211_tx_rate *tx_inf_rate = NULL; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + int tx_fixed_mcs_idx = 0; + int tx_fixed_rate = 0; + struct ieee80211_supported_band *band = NULL; + struct umac_vif *uvif = NULL; + + uvif = (struct umac_vif *)(tx_info->control.vif->drv_priv); + + /* Rate info will be retained, except the count*/ + ieee80211_tx_info_clear_status(tx_info); + + if (tx_done->frm_status[frame_idx] == TX_DONE_STAT_SUCCESS) + tx_info->flags |= IEEE80211_TX_STAT_ACK; + else if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) + tx_info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + + tx_info->flags &= ~IEEE80211_TX_STAT_AMPDU; + tx_info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + band = dev->hw->wiphy->bands[tx_info->band]; + + for (index = 0; index < 4; index++) { + tx_inf_rate = &tx_info->status.rates[index]; + + /* Populate tx_info based on 1st MPDU in an AMPDU */ + txrate = (&tx_info_1st_mpdu.control.rates[index]); + + if (txrate->idx < 0) + break; + + if ((dev->params->production_test == 1) && + ((dev->params->tx_fixed_mcs_indx != -1) || + (dev->params->tx_fixed_rate != -1))) { + tx_fixed_mcs_idx = dev->params->tx_fixed_mcs_indx; + tx_fixed_rate = dev->params->tx_fixed_rate; + + /* This index is always zero */ + /* TODO: See if we need to send channel bw information + * taken from proc, since in Production mode the bw + * advised by Minstrel can be overwritten by proc + * settings + */ + tx_inf_rate->flags = txrate->flags; + + if (tx_fixed_mcs_idx != -1) { + if (tx_fixed_mcs_idx <= 7) { + tx_inf_rate->flags |= + IEEE80211_TX_RC_MCS; + /* So that actual sent rate is seen in + * sniffer + */ + idx = tx_done->rate[frame_idx] & 0x7F; + tx_inf_rate->idx = idx; + } else if (tx_fixed_mcs_idx <= 9) { + tx_inf_rate->flags |= + IEEE80211_TX_RC_VHT_MCS; + /* So that actual sent rate is seen + * in sniffer + */ + idx = ((dev->params->num_spatial_streams + << 4) & 0xF0); + idx |= (tx_done->rate[frame_idx] & + 0x0F); + tx_inf_rate->idx = idx; + } + } else if (tx_fixed_rate != -1) { + for (i = 0; i < band->n_bitrates; i++) { + if ((band->bitrates[i]).hw_value == + tx_done->rate[frame_idx]) + tx_inf_rate->idx = i; + } + } + + tx_inf_rate->count = (tx_done->retries_num[frame_idx] + + 1); + break; + } + + if ((tx_done->rate[frame_idx] & + MARK_RATE_AS_MCS_INDEX) == 0x80) { + if ((txrate->flags & IEEE80211_TX_RC_VHT_MCS) && + ((tx_done->rate[frame_idx] & 0x0F) == + (txrate->idx & 0x0F))) { + tx_inf_rate->count = + (tx_done->retries_num[frame_idx] + 1); + } else if ((txrate->flags & IEEE80211_TX_RC_MCS) && + ((tx_done->rate[frame_idx] & 0x7F) == + (txrate->idx & 0x7F))) { + tx_inf_rate->count = + (tx_done->retries_num[frame_idx] + 1); + } + + break; + } else if (tx_done->rate[frame_idx] == + (band->bitrates[tx_inf_rate->idx]).hw_value) { + tx_inf_rate->count = + (tx_done->retries_num[frame_idx] + 1); + + break; + } + } + + /* Invalidate the remaining indices */ + while (((index + 1) < 4)) { + tx_info->status.rates[index + 1].idx = -1; + tx_info->status.rates[index + 1].count = 0; + index++; + } + + if (((tx_info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) +#ifdef MULTI_CHAN_SUPPORT + || (uvif->chanctx && + (uvif->chanctx->index == dev->roc_off_chanctx_idx)) +#endif + ) && + (atomic_dec_return(&dev->roc_params.roc_mgmt_tx_count) == 0)) { + UCCP_DEBUG_ROC("%s:%d TXDONE Frame: %d\n", + __func__, + __LINE__, + atomic_read(&dev->roc_params.roc_mgmt_tx_count)); + if (dev->roc_params.roc_in_progress && + dev->roc_params.roc_type == ROC_TYPE_OFFCHANNEL_TX) { + uccp420wlan_prog_roc(ROC_STOP, 0, 0, 0); + UCCP_DEBUG_ROC("%s:%d", __func__, __LINE__); + UCCP_DEBUG_ROC("all offchan pending frames cleared\n"); + } + } + + dev->stats->tx_dones_to_stack++; + + ieee80211_tx_status(dev->hw, skb); +} + + +static int get_token(struct mac80211_dev *dev, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int queue) +{ + int cnt = 0; + int curr_bit = 0; + int pool_id = 0; + int token_id = NUM_TX_DESCS; + struct tx_config *tx = &dev->tx; + + /* First search for a reserved token */ + for (cnt = 0; cnt < NUM_TX_DESCS_PER_AC; cnt++) { + curr_bit = ((queue + (NUM_ACS * cnt)) % TX_DESC_BUCKET_BOUND); + pool_id = ((queue + (NUM_ACS * cnt)) / TX_DESC_BUCKET_BOUND); + + if (!test_and_set_bit(curr_bit, &tx->buf_pool_bmp[pool_id])) { + token_id = queue + (NUM_ACS * cnt); + tx->outstanding_tokens[queue]++; + break; + } + } + + /* If reserved token is not found search for a spare token + * (only for non beacon queues) + */ + if ((cnt == NUM_TX_DESCS_PER_AC) && (queue != WLAN_AC_BCN)) { + for (token_id = NUM_TX_DESCS_PER_AC * NUM_ACS; + token_id < NUM_TX_DESCS; + token_id++) { + curr_bit = (token_id % TX_DESC_BUCKET_BOUND); + pool_id = (token_id / TX_DESC_BUCKET_BOUND); + if (!test_and_set_bit(curr_bit, + &tx->buf_pool_bmp[pool_id])) { + tx->outstanding_tokens[queue]++; + break; + } + } + } + + return token_id; +} + +void free_token(struct mac80211_dev *dev, + int token_id, + int queue) +{ + struct tx_config *tx = &dev->tx; + int bit = -1; + int pool_id = -1; + int test = 0; + unsigned int old_token = tx->outstanding_tokens[queue]; + bit = (token_id % TX_DESC_BUCKET_BOUND); + pool_id = (token_id / TX_DESC_BUCKET_BOUND); + + __clear_bit(bit, &tx->buf_pool_bmp[pool_id]); + + tx->outstanding_tokens[queue]--; + + test = tx->outstanding_tokens[queue]; + if (WARN_ON_ONCE(test < 0 || test > 4)) { + pr_warn("%s: invalid outstanding_tokens: %d, old:%d\n", + __func__, + test, + old_token); + } +} + + +struct curr_peer_info get_curr_peer_opp(struct mac80211_dev *dev, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int ac) +{ + unsigned int curr_peer_opp = 0; + unsigned int curr_vif_op_chan = UMAC_VIF_CHANCTX_TYPE_OPER; + unsigned int i = 0; + struct tx_config *tx = NULL; +#ifdef MULTI_CHAN_SUPPORT + struct ieee80211_sta *sta = NULL; + struct ieee80211_vif *vif = NULL; + struct umac_sta *usta = NULL; + struct umac_vif *uvif = NULL; + int vif_index = -1; +#endif + unsigned int init_peer_opp = 0; + struct curr_peer_info peer_info; + unsigned int pend_q_len; + struct sk_buff_head *pend_q = NULL; + + tx = &dev->tx; + +#ifdef MULTI_CHAN_SUPPORT + init_peer_opp = tx->curr_peer_opp[curr_chanctx_idx][ac]; +#else + init_peer_opp = tx->curr_peer_opp[ac]; +#endif + /*TODO: Optimize this loop for BCN_Q + */ + for (i = 0; i < MAX_PEND_Q_PER_AC; i++) { + curr_peer_opp = (init_peer_opp + i) % MAX_PEND_Q_PER_AC; + +#ifdef MULTI_CHAN_SUPPORT + rcu_read_lock(); + + /* RoC Frame do not have a "sta" entry. + * so we need not handle RoC stuff here + */ + if (curr_peer_opp < MAX_PEERS) { + sta = rcu_dereference(dev->peers[curr_peer_opp]); + + if (!sta) { + rcu_read_unlock(); + continue; + } + + usta = (struct umac_sta *)(sta->drv_priv); + + vif = rcu_dereference(dev->vifs[usta->vif_index]); + + if (!vif) { + rcu_read_unlock(); + continue; + } + + + uvif = (struct umac_vif *)(vif->drv_priv); + + if (!uvif->chanctx && !uvif->off_chanctx) { + rcu_read_unlock(); + continue; + } + + if ((uvif->chanctx && + (uvif->chanctx->index != curr_chanctx_idx)) || + !uvif->chanctx) { + if ((uvif->off_chanctx && + (uvif->off_chanctx->index != + curr_chanctx_idx)) || + !uvif->off_chanctx) { + rcu_read_unlock(); + continue; + } else { + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OFF; + } + } else { + if (dev->roc_params.roc_in_progress && + !dev->roc_params.need_offchan) + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OFF; + else + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OPER; + } + } else { + vif_index = (curr_peer_opp - MAX_PEERS); + + vif = rcu_dereference(dev->vifs[vif_index]); + + if (!vif) { + rcu_read_unlock(); + continue; + } + + uvif = (struct umac_vif *)(vif->drv_priv); + + if (!uvif->chanctx && !uvif->off_chanctx) { + rcu_read_unlock(); + continue; + } + + /* For a beacon queue we will process the frames + * irrespective of the current channel context. + * The FW will take care of transmitting them in the + * appropriate channel. + */ + + if (ac != WLAN_AC_BCN && + ((uvif->chanctx && + (uvif->chanctx->index != curr_chanctx_idx)) || + !uvif->chanctx)) { + if ((uvif->off_chanctx && + (uvif->off_chanctx->index != + curr_chanctx_idx)) || + !uvif->off_chanctx) { + rcu_read_unlock(); + continue; + } else { + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OFF; + } + } else { + if (dev->roc_params.roc_in_progress && + !dev->roc_params.need_offchan) + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OFF; + else + curr_vif_op_chan = + UMAC_VIF_CHANCTX_TYPE_OPER; + } + } + + rcu_read_unlock(); +#endif +#ifdef MULTI_CHAN_SUPPORT + pend_q = &tx->pending_pkt[curr_vif_op_chan][curr_peer_opp][ac]; +#else + pend_q = &tx->pending_pkt[curr_peer_opp][ac]; +#endif + pend_q_len = skb_queue_len(pend_q); + + if (pend_q_len) { +#ifdef MULTI_CHAN_SUPPORT + tx->curr_peer_opp[curr_chanctx_idx][ac] = + (curr_peer_opp + 1) % MAX_PEND_Q_PER_AC; +#else + tx->curr_peer_opp[ac] = + (curr_peer_opp + 1) % MAX_PEND_Q_PER_AC; +#endif + break; + } + } + + if (i == MAX_PEND_Q_PER_AC) { + peer_info.id = -1; + peer_info.op_chan_idx = -1; + } else { + peer_info.id = curr_peer_opp; + peer_info.op_chan_idx = curr_vif_op_chan; + UCCP_DEBUG_TX("%s: Queue: %d Peer: %d op_chan: %d ", + __func__, + ac, + curr_peer_opp, + curr_vif_op_chan); +#ifdef MULTI_CHAN_SUPPORT + UCCP_DEBUG_TX("chanctx: %d got opportunity, pending: %d\n", + curr_chanctx_idx, + pend_q_len); +#else + UCCP_DEBUG_TX("Pending: %d\n", + pend_q_len); +#endif + } + + return peer_info; +} + + +#ifdef MULTI_CHAN_SUPPORT +void uccp420wlan_tx_proc_send_pend_frms_all(struct mac80211_dev *dev, + int ch_id) +{ + int txq_len = 0; + int i = 0, cnt = 0; + int queue = 0; + int curr_bit = 0; + int pool_id = 0; + int ret = 0; + int start_ac, end_ac; + unsigned int pkts_pend = 0; + struct tx_config *tx = NULL; + struct sk_buff_head *txq = NULL; + + tx = &dev->tx; + + for (i = 0; i < NUM_TX_DESCS; i++) { + spin_lock_bh(&tx->lock); + + curr_bit = (i % TX_DESC_BUCKET_BOUND); + pool_id = (i / TX_DESC_BUCKET_BOUND); + + if (test_and_set_bit(curr_bit, &tx->buf_pool_bmp[pool_id])) { + spin_unlock_bh(&tx->lock); + continue; + } + + txq = &tx->pkt_info[ch_id][i].pkt; + txq_len = skb_queue_len(txq); + + /* Not valid when txq len is 0 */ + queue = tx->pkt_info[ch_id][i].queue; + + if (!txq_len) { + /* Reserved token */ + if (i < (NUM_TX_DESCS_PER_AC * NUM_ACS)) { + queue = (i % NUM_ACS); + start_ac = end_ac = queue; + } else { + /* Spare token: + * Loop through all AC's + */ + start_ac = WLAN_AC_VO; + end_ac = WLAN_AC_BK; + } + + for (cnt = start_ac; cnt >= end_ac; cnt--) { + pkts_pend = uccp420wlan_tx_proc_pend_frms(dev, + cnt, + ch_id, + i); + if (pkts_pend) { + queue = cnt; + break; + } + } + + if (pkts_pend == 0) { + __clear_bit(curr_bit, + &tx->buf_pool_bmp[pool_id]); + spin_unlock_bh(&tx->lock); + continue; + } + } + + tx->outstanding_tokens[queue]++; + spin_unlock_bh(&tx->lock); + + ret = __uccp420wlan_tx_frame(dev, + queue, + i, + ch_id, + 0, + 0); /* TODO: Currently sending 0 + since this param is not used + as expected in the orig + code for multiple frames etc + Need to set this + properly when the orig code + logic is corrected + */ + if (ret < 0) { + pr_err("%s: Queueing of TX frame to FW failed\n", + __func__); + } + } +} +#endif + + +int uccp420wlan_tx_proc_pend_frms(struct mac80211_dev *dev, + int ac, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int token_id) +{ + struct tx_config *tx = &dev->tx; + unsigned long ampdu_len = 0; + struct sk_buff *loop_skb = NULL; + struct sk_buff *tmp = NULL; + struct ieee80211_hdr *mac_hdr = NULL; + struct ieee80211_tx_info *tx_info = NULL; + struct umac_vif *uvif = NULL; + struct ieee80211_vif *ivif = NULL; + unsigned char *data = NULL; + unsigned int max_tx_cmds = dev->params->max_tx_cmds; + struct sk_buff_head *txq = NULL; + struct sk_buff_head *pend_pkt_q = NULL; + unsigned int total_pending_processed = 0; + int pend_pkt_q_len = 0; + struct curr_peer_info peer_info; + int loop_cnt = 0; + peer_info = get_curr_peer_opp(dev, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + ac); + + /* No pending frames for any peer in that AC. + */ + if (peer_info.id == -1) + return 0; + +#ifdef MULTI_CHAN_SUPPORT + pend_pkt_q = &tx->pending_pkt[peer_info.op_chan_idx][peer_info.id][ac]; +#else + pend_pkt_q = &tx->pending_pkt[peer_info.id][ac]; +#endif + +#ifdef MULTI_CHAN_SUPPORT + txq = &dev->tx.pkt_info[curr_chanctx_idx][token_id].pkt; +#else + txq = &dev->tx.pkt_info[token_id].pkt; +#endif + + + /* Aggregate Only MPDU's with same RA, same Rate, + * same Rate flags, same Tx Info flags + */ + skb_queue_walk_safe(pend_pkt_q, + loop_skb, + tmp) { + data = loop_skb->data; + mac_hdr = (struct ieee80211_hdr *)data; + + tx_info = IEEE80211_SKB_CB(loop_skb); + + ivif = tx_info->control.vif; + uvif = (struct umac_vif *)(ivif->drv_priv); + + ampdu_len += loop_skb->len; + + /* Temp Checks for Aggregation: Will be removed later*/ + if (vht_support && !loop_cnt) + if ((tx_info->control.rates[0].flags & + IEEE80211_TX_RC_MCS) && + max_tx_cmds > MAX_SUBFRAMES_IN_AMPDU_HT) + max_tx_cmds = MAX_SUBFRAMES_IN_AMPDU_HT; + if (!check_80211_aggregation(dev, + loop_skb, + ac, + curr_chanctx_idx, + peer_info.id) || + (skb_queue_len(txq) >= max_tx_cmds)) { + break; + } + loop_cnt++; + __skb_unlink(loop_skb, pend_pkt_q); + skb_queue_tail(txq, loop_skb); + } + + /* If our criterion rejects all pending frames, or + * pend_q is empty, send only 1 + */ + if (!skb_queue_len(txq)) + skb_queue_tail(txq, skb_dequeue(pend_pkt_q)); + + total_pending_processed = skb_queue_len(txq); + + pend_pkt_q_len = skb_queue_len(pend_pkt_q); + if ((ac != WLAN_AC_BCN) && + (tx->queue_stopped_bmp & (1 << ac)) && + pend_pkt_q_len < (MAX_TX_QUEUE_LEN / 2)) { + ieee80211_wake_queue(dev->hw, tx_queue_unmap(ac)); + tx->queue_stopped_bmp &= ~(1 << (ac)); + } + + UCCP_DEBUG_TX("%s-UMACTX: token_id: %d ", + dev->name, + token_id); + UCCP_DEBUG_TX("total_pending_packets_process: %d\n", + skb_queue_len(txq)); + + return total_pending_processed; +} + + +int uccp420wlan_tx_alloc_token(struct mac80211_dev *dev, + int ac, +#ifdef MULTI_CHAN_SUPPORT + int off_chanctx_idx, + int curr_chanctx_idx, +#endif + int peer_id, + struct sk_buff *skb) +{ + int token_id = NUM_TX_DESCS; + struct tx_config *tx = &dev->tx; + struct sk_buff_head *pend_pkt_q = NULL; + unsigned int pkts_pend = 0; + struct ieee80211_tx_info *tx_info; + unsigned int pend_q_len = 0; + spin_lock_bh(&tx->lock); + +#ifdef MULTI_CHAN_SUPPORT + pend_pkt_q = &tx->pending_pkt[off_chanctx_idx][peer_id][ac]; + +#else + pend_pkt_q = &tx->pending_pkt[peer_id][ac]; +#endif +#ifdef MULTI_CHAN_SUPPORT + UCCP_DEBUG_TX("%s-UMACTX:Alloc buf Req q = %d off_chan: %d\n", + dev->name, + ac, + off_chanctx_idx); +#else + UCCP_DEBUG_TX("%s-UMACTX:Alloc buf Req q = %d\n", + dev->name, + ac); +#endif + UCCP_DEBUG_TX("peerid: %d,\n", peer_id); + + /* Queue the frame to the pending frames queue */ + skb_queue_tail(pend_pkt_q, skb); + pend_q_len = skb_queue_len(pend_pkt_q); + + tx_info = IEEE80211_SKB_CB(skb); + + if (tx->outstanding_tokens[ac] >= NUM_TX_DESCS_PER_AC) { + bool agg_status = check_80211_aggregation(dev, + skb, + ac, + off_chanctx_idx, + peer_id); + + if (agg_status || !dev->params->enable_early_agg_checks) { + /* encourage aggregation to the max size + * supported (dev->params->max_tx_cmds) + */ + if (pend_q_len < dev->params->max_tx_cmds) { + UCCP_DEBUG_TX("tx:pend_q not full out_tok:%d\n", + tx->outstanding_tokens[ac]); + goto out; + } else { + UCCP_DEBUG_TX("tx:pend_q full out_tok:%d\n", + tx->outstanding_tokens[ac]); + } + } + } + + /* Take steps to stop the TX traffic if we have reached + * the queueing limit. + * We dont this for the ROC queue to avoid the case where we are in the + * OFF channel but there is lot of traffic for the operating channel on + * the shared ROC queue (which is VO right now), since this would block + * ROC traffic too. + */ + if (pend_q_len >= MAX_TX_QUEUE_LEN) { + if ((!dev->roc_params.roc_in_progress) || + (dev->roc_params.roc_in_progress && + (ac != UMAC_ROC_AC))) { + ieee80211_stop_queue(dev->hw, + skb->queue_mapping); + tx->queue_stopped_bmp |= (1 << ac); + } + } + + token_id = get_token(dev, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + ac); + + UCCP_DEBUG_TX("%s-UMACTX:Alloc buf Result *id= %d q = %d", + dev->name, + token_id, + ac); + UCCP_DEBUG_TX(", peerid: %d,\n", peer_id); + + if (token_id == NUM_TX_DESCS) + goto out; + + pkts_pend = uccp420wlan_tx_proc_pend_frms(dev, + ac, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + token_id); + + /* We have just added a frame to pending_q but channel context is + * mismatch. + */ + + if (!pkts_pend) { + free_token(dev, token_id, ac); + token_id = NUM_TX_DESCS; + } + +out: + spin_unlock_bh(&tx->lock); + + UCCP_DEBUG_TX("%s-UMACTX:Alloc buf Result *id= %d\n", + dev->name, + token_id); + /* If token is available, just return tokenid, list will be sent*/ + return token_id; +} + + +#ifdef MULTI_CHAN_SUPPORT +int get_band_chanctx(struct mac80211_dev *dev, struct umac_vif *uvif) +{ + struct ieee80211_chanctx_conf *chanctx = NULL; + int index = 0; + int band = 0; + + rcu_read_lock(); + index = uvif->chanctx->index; + chanctx = rcu_dereference(dev->chanctx[index]); + band = (chanctx->def.chan)->band; + rcu_read_unlock(); + + return band; +} +#endif + +int uccp420wlan_tx_free_buff_req(struct mac80211_dev *dev, + struct umac_event_tx_done *tx_done, + unsigned char *ac, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + int *vif_index_bitmap) +{ + int i = 0; + unsigned int pkts_pend = 0; + struct tx_config *tx = &dev->tx; + struct ieee80211_hdr *mac_hdr; + struct ieee80211_tx_info *tx_info_bcn; + struct ieee80211_tx_info tx_info_1st_mpdu; + struct sk_buff *skb, *tmp, *skb_first = NULL; + struct sk_buff_head *skb_list, tx_done_list; + int vif_index = -1; + unsigned int pkt = 0; + int cnt = 0; + unsigned int desc_id = tx_done->descriptor_id; + struct umac_vif *uvif = NULL; + struct ieee80211_vif *ivif = NULL; + unsigned long bcn_int = 0; +#ifdef MULTI_CHAN_SUPPORT + int chanctx_idx = 0; + struct tx_pkt_info *pkt_info = NULL; +#endif + int start_ac, end_ac; + + skb_queue_head_init(&tx_done_list); + + spin_lock_bh(&tx->lock); + +#ifdef MULTI_CHAN_SUPPORT + chanctx_idx = tx->desc_chan_map[desc_id]; + if (chanctx_idx == -1) { + spin_unlock_bh(&tx->lock); + pr_err("%s: Unexpected channel context:tok:%d q:%d\n", + __func__, + desc_id, + tx_done->queue); + free_token(dev, desc_id, tx_done->queue); + goto out; + } + pkt_info = &dev->tx.pkt_info[chanctx_idx][desc_id]; +#endif + UCCP_DEBUG_TX("%s-UMACTX:Free buf Req q = %d", + dev->name, + tx_done->queue); +#ifdef MULTI_CHAN_SUPPORT + UCCP_DEBUG_TX(", desc_id: %d chanctx: %d out_tok: %d\n", + desc_id, + chanctx_idx, + dev->tx.outstanding_tokens[tx_done->queue]); +#else + UCCP_DEBUG_TX(", desc_id: %d out_tok: %d\n", + desc_id, + dev->tx.outstanding_tokens[tx_done->queue]); +#endif + + + /* Defer Tx Done Processsing */ +#ifdef MULTI_CHAN_SUPPORT + skb_list = &dev->tx.pkt_info[chanctx_idx][desc_id].pkt; +#else + skb_list = &dev->tx.pkt_info[desc_id].pkt; +#endif + + if (skb_queue_len(skb_list)) { + /* Cut the list to new one, tx_pkt will be re-initialized */ + skb_queue_splice_tail_init(skb_list, &tx_done_list); + } else { + UCCP_DEBUG_TX("%s-UMACTX:Got Empty List: list_addr: %p\n", + dev->name, + skb_list); + } + + /* Reserved token */ + if (desc_id < (NUM_TX_DESCS_PER_AC * NUM_ACS)) { + start_ac = end_ac = tx_done->queue; + } else { + /* Spare token: + * Loop through all AC's + */ + start_ac = WLAN_AC_VO; + end_ac = WLAN_AC_BK; + } + for (cnt = start_ac; cnt >= end_ac; cnt--) { + pkts_pend = uccp420wlan_tx_proc_pend_frms(dev, + cnt, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + desc_id); + + if (pkts_pend) { + *ac = cnt; + break; + } + } + + /* Unmap here before release lock to avoid race */ + if (skb_queue_len(&tx_done_list)) { + skb_queue_walk_safe(&tx_done_list, skb, tmp) { + hal_ops.unmap_tx_buf(tx_done->descriptor_id, pkt); + UCCP_DEBUG_TX("%s-UMACTX:TXDONE: ID=%d", + dev->name, + tx_done->descriptor_id); + UCCP_DEBUG_TX("Stat=%d (%d, %d)\n", + tx_done->frm_status[pkt], + tx_done->rate[pkt], + tx_done->retries_num[pkt]); + + pkt++; + } + } + + if (!pkts_pend) { + /* Mark the token as available */ + free_token(dev, desc_id, tx_done->queue); +#ifdef MULTI_CHAN_SUPPORT + dev->tx.desc_chan_map[desc_id] = -1; +#endif + } + + /* Unlock: Give a chance for Tx to add to pending lists */ + spin_unlock_bh(&tx->lock); + + /* Protection from mac80211 _ops especially stop */ + if (dev->state != STARTED) + goto out; + + if (!skb_queue_len(&tx_done_list)) + goto out; + + skb_first = skb_peek(&tx_done_list); + + memcpy(&tx_info_1st_mpdu, + (struct ieee80211_tx_info *)IEEE80211_SKB_CB(skb_first), + sizeof(struct ieee80211_tx_info)); + + pkt = 0; + + skb_queue_walk_safe(&tx_done_list, skb, tmp) { + __skb_unlink(skb, &tx_done_list); + + if (!skb) + continue; + /* In the Tx path we move the .11hdr from skb to CMD_TX + * Hence pushing it here, not required for loopback case + */ +#ifdef MULTI_CHAN_SUPPORT + skb_push(skb, + dev->tx.pkt_info[chanctx_idx][desc_id].hdr_len); +#else + skb_push(skb, + dev->tx.pkt_info[tx_done->descriptor_id].hdr_len); +#endif + mac_hdr = (struct ieee80211_hdr *)(skb->data); + + if (!ieee80211_is_beacon(mac_hdr->frame_control)) { + vif_index = vif_addr_to_index(mac_hdr->addr2, + dev); + if (vif_index > -1) + *vif_index_bitmap |= (1 << vif_index); + + /* Same Rate info for all packets */ + tx_status(skb, + tx_done, + pkt, + dev, + tx_info_1st_mpdu); + } else { + + if (tx_done->frm_status[pkt] == + TX_DONE_STAT_DISCARD_BCN) { + /* We did not send beacon */ + dev->tx_last_beacon = 0; + } else if (tx_done->frm_status[pkt] == + TX_DONE_STAT_SUCCESS) { + /* We did send beacon */ + dev->tx_last_beacon = 1; + } + + tx_info_bcn = IEEE80211_SKB_CB(skb); + ivif = tx_info_bcn->control.vif; + uvif = (struct umac_vif *)(ivif->drv_priv); + bcn_int = uvif->vif->bss_conf.beacon_int - 10; + bcn_int = msecs_to_jiffies(bcn_int); + + /* Beacon Time Stamp */ + if (tx_done->frm_status[pkt] == TX_DONE_STAT_SUCCESS) { + unsigned int ts2; + unsigned int ldelta = 0; + int ets_band; + int bts_vif = uvif->vif_index; + +#ifdef MULTI_CHAN_SUPPORT + ets_band = get_band_chanctx(dev, uvif); +#endif + spin_lock(&tsf_lock); + dev->params->sync[bts_vif].status = 1; + memcpy(dev->params->sync[bts_vif].bssid, + ivif->bss_conf.bssid, ETH_ALEN); + memcpy(dev->params->sync[bts_vif].ts1, + tx_done->reserved, 8); + memcpy(&dev->params->sync[bts_vif].ts2, + (tx_done->reserved + 8), 4); + ts2 = dev->params->sync[bts_vif].ts2; + dev->params->sync[bts_vif].atu = 0; + + if (IEEE80211_BAND_2GHZ == ets_band) + ldelta = BTS_AP_24GHZ_ETS; + else if (IEEE80211_BAND_5GHZ == ets_band) + ldelta = BTS_AP_5GHZ_ETS; + + if (frc_to_atu) { + frc_to_atu(ts2, + &dev->params->sync[bts_vif].atu, 0); + dev->params->sync[bts_vif].atu += ldelta * 1000; + } + spin_unlock(&tsf_lock); + } + + for (i = 0; i < MAX_VIFS; i++) { + if (dev->active_vifs & (1 << i)) { + if (dev->vifs[i] == ivif) + mod_timer(&uvif->bcn_timer, + jiffies + + bcn_int); + } + } + + dev_kfree_skb_any(skb); + } + + pkt++; + } +out: + return pkts_pend; +} + + +#ifdef MULTI_CHAN_SUPPORT +void uccp420wlan_proc_ch_sw_event(struct umac_event_ch_switch *ch_sw_info, + void *context) +{ + struct mac80211_dev *dev = NULL; + int chan = 0; + int curr_freq = 0; + int chan_id = 0; + struct ieee80211_chanctx_conf *curr_chanctx = NULL; + int i = 0; + + if (!ch_sw_info || !context) { + pr_err("%s: Invalid Parameters:\n", __func__); + return; + } + + dev = (struct mac80211_dev *)context; + chan = ch_sw_info->chan; + + rcu_read_lock(); + + for (i = 0; i < MAX_CHANCTX; i++) { + curr_chanctx = rcu_dereference(dev->chanctx[i]); + + if (curr_chanctx) { + curr_freq = curr_chanctx->def.chan->center_freq; + + if (ieee80211_frequency_to_channel(curr_freq) == chan) { + chan_id = i; + break; + } + } + } + + rcu_read_unlock(); + + if (i == MAX_CHANCTX) { + pr_err("%s: Invalid Channel Context: chan: %d\n", + __func__, + chan); + return; + } + + /* Switch to the new channel context */ + spin_lock(&dev->chanctx_lock); + dev->curr_chanctx_idx = chan_id; + spin_unlock(&dev->chanctx_lock); + + /* We now try to xmit any frames whose xmission got cancelled due to a + * previous channel switch + */ + uccp420wlan_tx_proc_send_pend_frms_all(dev, chan_id); +} + + +unsigned int uccp420wlan_proc_tx_dscrd_chsw(struct mac80211_dev *dev, + int curr_chanctx_idx, + struct umac_event_tx_done *tx_done) +{ + struct tx_config *tx = &dev->tx; + struct sk_buff_head *txq = NULL, tx_done_list; + int chanctx_idx = -1; + int pkt = 0; + int txq_len = 0; + struct sk_buff *skb = NULL; + struct sk_buff *skb_first = NULL; + struct sk_buff *tmp = NULL; + int queue = 0; + int ret = 0, cnt = 0; + unsigned int desc_id = 0; + unsigned int *curr_retries = NULL; + unsigned int max_retries = 0; + struct ieee80211_tx_info tx_info_1st_mpdu; + struct ieee80211_hdr *mac_hdr = NULL; + bool retries_exceeded = false; + unsigned int *rate = NULL; + unsigned int *retries = NULL; + int start_ac, end_ac; + unsigned int pkts_pend = 0; + + skb_queue_head_init(&tx_done_list); + + spin_lock_bh(&tx->lock); + + desc_id = tx_done->descriptor_id; + + /* We keep the frames which were not consumed by the FW in the + * tx_pkt queue. These frames will then be requeued to the FW when this + * channel context is scheduled again + */ + chanctx_idx = tx->desc_chan_map[desc_id]; + + if ((chanctx_idx == -1) || + (chanctx_idx > (MAX_CHANCTX + MAX_OFF_CHANCTX))) { + pr_err("%s: Unexpected channel context: %d\n", + __func__, + chanctx_idx); + goto out; + } + + txq = &tx->pkt_info[chanctx_idx][desc_id].pkt; + txq_len = skb_queue_len(txq); + + if (!txq_len) { + pr_err("%s: TX_DONE received for empty queue: chan: %d desc_id: %d\n", + __func__, + chanctx_idx, + desc_id); + goto out; + } + + UCCP_DEBUG_TX("%s: %d retries: %d rate: %d\n", + __func__, + __LINE__, + tx_done->retries_num[0], + tx_done->rate[0]); + pkt = 0; + + skb_first = skb_peek(txq); + + if (!skb_first) { + pr_err("%s: Empty txq: chan: %d desc_id: %d\n", + __func__, + chanctx_idx, + desc_id); + goto out; + } + + curr_retries = &tx->pkt_info[chanctx_idx][desc_id].curr_retries; + max_retries = tx->pkt_info[chanctx_idx][desc_id].max_retries; + retries = tx->pkt_info[chanctx_idx][desc_id].retries; + rate = tx->pkt_info[chanctx_idx][desc_id].rate; + tx->pkt_info[chanctx_idx][desc_id].adjusted_rates = true; + + if ((tx_done->retries_num[0] + *curr_retries) > max_retries) + retries_exceeded = true; + else + *curr_retries += tx_done->retries_num[0]; + + memcpy(&tx_info_1st_mpdu, + (struct ieee80211_tx_info *)IEEE80211_SKB_CB(skb_first), + sizeof(struct ieee80211_tx_info)); + + skb_queue_walk_safe(txq, skb, tmp) { + if (!skb) + continue; + + hal_ops.unmap_tx_buf(desc_id, pkt); + + /* In the Tx path we move the .11hdr from skb to CMD_TX + * Hence pushing it here + */ + skb_push(skb, + tx->pkt_info[chanctx_idx][desc_id].hdr_len); + + mac_hdr = (struct ieee80211_hdr *)skb->data; + + if (retries_exceeded) { + __skb_unlink(skb, txq); + + if (!skb) + continue; + skb_queue_tail(&tx_done_list, skb); + UCCP_DEBUG_TX("%s: %d ", __func__, __LINE__); + UCCP_DEBUG_TX("Freeing the skb MAX retries reached.\n"); + } else { + UCCP_DEBUG_TX("%s: %d ", __func__, __LINE__); + UCCP_DEBUG_TX("Re-programming the skb when "); + UCCP_DEBUG_TX("CTX is right with retry bit set.\n"); + mac_hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_RETRY); + } + pkt++; + } + + /* First check if there is a packet in the txq of the current + * chanctx that needs to be transmitted + */ + txq = &tx->pkt_info[curr_chanctx_idx][desc_id].pkt; + txq_len = skb_queue_len(txq); + queue = tx->pkt_info[curr_chanctx_idx][desc_id].queue; + pkts_pend = txq_len; + + if (txq_len) { + spin_unlock_bh(&tx->lock); + + /* TODO: Currently sending 0 since this param is not + * used as expected in the orig code for multiple + * frames etc Need to set this properly when the orig + * code logic is corrected + */ + ret = __uccp420wlan_tx_frame(dev, + queue, + desc_id, + curr_chanctx_idx, + 0, + 1); + if (ret < 0) { + /* TODO: Check if we need to clear the TX bitmap + * and desc_chan_map here + */ + pr_err("%s: Queueing of TX frame to FW failed\n", + __func__); + } + + /* This is needed to avoid freeing up the token + */ + pkts_pend = 1; + + goto tx_done; + } else { + /* Check pending queue */ + /* Reserved token */ + if (desc_id < (NUM_TX_DESCS_PER_AC * NUM_ACS)) { + queue = (desc_id % NUM_ACS); + start_ac = end_ac = queue; + } else { + /* Spare token: + * Loop through all AC's + */ + start_ac = WLAN_AC_VO; + end_ac = WLAN_AC_BK; + } + + for (cnt = start_ac; cnt >= end_ac; cnt--) { + pkts_pend = uccp420wlan_tx_proc_pend_frms(dev, + cnt, + curr_chanctx_idx, + desc_id); + if (pkts_pend) { + queue = cnt; + break; + } + } + + spin_unlock_bh(&tx->lock); + + if (pkts_pend > 0) { + /* TODO: Currently sending 0 since this param is not + * used as expected in the orig code for multiple + * frames etc. Need to set this properly when the orig + * code logic is corrected + */ + ret = __uccp420wlan_tx_frame(dev, + queue, + desc_id, + curr_chanctx_idx, + 0, + 0); + + if (ret < 0) { + pr_err("%s: Queueing of TX frame to FW failed\n", + __func__); + } + } + goto tx_done; + } + + if (txq_len == 1) + dev->stats->tx_cmd_send_count_single--; + else + dev->stats->tx_cmd_send_count_multi--; + +out: + spin_unlock_bh(&tx->lock); + + return pkts_pend; + +tx_done: + skb_queue_walk_safe(&tx_done_list, skb, tmp) { + tx_status(skb, + tx_done, + pkt, + dev, + tx_info_1st_mpdu); + } + + return pkts_pend; +} +#endif + + +#ifdef PERF_PROFILING +static void print_persec_stats(unsigned long data) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)data; + struct tx_config *tx = &dev->tx; + + if (dev->stats->tx_cmds_from_stack != 0) { + pr_info("%s: %d The persec stats from stack: %d outstanding_tokens: [%d = %d = %d = %d = %d]\n", + __func__, + __LINE__, + dev->stats->tx_cmds_from_stack, + tx->outstanding_tokens[0], + tx->outstanding_tokens[1], + tx->outstanding_tokens[2], + tx->outstanding_tokens[3], + tx->outstanding_tokens[4]); + + dev->stats->tx_cmds_from_stack = 0; + } + + mod_timer(&tx->persec_timer, jiffies + msecs_to_jiffies(1000)); +} +#endif + + +void uccp420wlan_tx_init(struct mac80211_dev *dev) +{ + int i = 0; + int j = 0; +#ifdef MULTI_CHAN_SUPPORT + int k = 0; +#endif + struct tx_config *tx = &dev->tx; + + memset(&tx->buf_pool_bmp, + 0, + sizeof(long) * ((NUM_TX_DESCS/TX_DESC_BUCKET_BOUND) + 1)); + + tx->queue_stopped_bmp = 0; + tx->next_spare_token_ac = WLAN_AC_BE; + + for (i = 0; i < NUM_ACS; i++) { + for (j = 0; j < MAX_PEND_Q_PER_AC; j++) { +#ifdef MULTI_CHAN_SUPPORT + for (k = 0; k < MAX_UMAC_VIF_CHANCTX_TYPES; k++) + skb_queue_head_init(&tx->pending_pkt[k][j][i]); +#else + skb_queue_head_init(&tx->pending_pkt[j][i]); +#endif + } + + tx->outstanding_tokens[i] = 0; + } + + for (i = 0; i < NUM_TX_DESCS; i++) { +#ifdef MULTI_CHAN_SUPPORT + tx->desc_chan_map[i] = -1; + + for (j = 0; j < MAX_CHANCTX + MAX_OFF_CHANCTX ; j++) + skb_queue_head_init(&tx->pkt_info[j][i].pkt); +#else + skb_queue_head_init(&tx->pkt_info[i].pkt); +#endif + } + + for (j = 0; j < NUM_ACS; j++) +#ifdef MULTI_CHAN_SUPPORT + for (i = 0; i < MAX_CHANCTX; i++) + tx->curr_peer_opp[i][j] = 0; +#else + tx->curr_peer_opp[j] = 0; +#endif + +#ifdef PERF_PROFILING + init_timer(&tx->persec_timer); + tx->persec_timer.data = (unsigned long)dev; + tx->persec_timer.function = print_persec_stats; + mod_timer(&tx->persec_timer, jiffies + msecs_to_jiffies(1000)); +#endif +#ifdef MULTI_CHAN_SUPPORT + dev->curr_chanctx_idx = -1; +#endif + spin_lock_init(&tx->lock); + ieee80211_wake_queues(dev->hw); + + UCCP_DEBUG_TX("%s-UMACTX: initialization successful\n", + TX_TO_MACDEV(tx)->name); +} + + +void uccp420wlan_tx_deinit(struct mac80211_dev *dev) +{ + int i = 0; + int j = 0; +#ifdef MULTI_CHAN_SUPPORT + int k = 0; +#endif + struct tx_config *tx = &dev->tx; + struct sk_buff *skb = NULL; +#ifdef MULTI_CHAN_SUPPORT + unsigned int qlen = 0; +#endif + struct sk_buff_head *pend_q = NULL; + + ieee80211_stop_queues(dev->hw); + + wait_for_tx_complete(tx); + + spin_lock_bh(&tx->lock); + + for (i = 0; i < NUM_TX_DESCS; i++) { +#ifdef MULTI_CHAN_SUPPORT + for (j = 0; j < MAX_CHANCTX + MAX_OFF_CHANCTX; j++) { + qlen = skb_queue_len(&tx->pkt_info[j][i].pkt); + + if (qlen) { + while ((skb = + skb_dequeue(&tx->pkt_info[j][i].pkt)) != + NULL) { + dev_kfree_skb_any(skb); + } + } + } +#else + while ((skb = skb_dequeue(&tx->pkt_info[i].pkt)) != NULL) + dev_kfree_skb_any(skb); +#endif + } + + for (i = 0; i < NUM_ACS; i++) { + for (j = 0; j < MAX_PEND_Q_PER_AC; j++) { +#ifdef MULTI_CHAN_SUPPORT + for (k = 0; k < MAX_UMAC_VIF_CHANCTX_TYPES; k++) + pend_q = &tx->pending_pkt[k][j][i]; +#else + pend_q = &tx->pending_pkt[j][i]; +#endif + + while ((skb = skb_dequeue(pend_q)) != NULL) + dev_kfree_skb_any(skb); + } + } + + spin_unlock_bh(&tx->lock); + + UCCP_DEBUG_TX("%s-UMACTX: deinitialization successful\n", + TX_TO_MACDEV(tx)->name); +} + + +int __uccp420wlan_tx_frame(struct mac80211_dev *dev, + unsigned int queue, + unsigned int token_id, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + unsigned int more_frames, + bool retry) +{ + struct umac_event_tx_done tx_done; + struct sk_buff_head *txq = NULL; + int ret = 0; + int pkt = 0; + + ret = uccp420wlan_prog_tx(queue, + more_frames, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + token_id, + retry); + + if (ret < 0) { + pr_err("%s-UMACTX: Unable to send frame, dropping ..%d\n", + dev->name, ret); + + tx_done.descriptor_id = token_id; + tx_done.queue = queue; +#ifdef MULTI_CHAN_SUPPORT + dev->tx.desc_chan_map[token_id] = curr_chanctx_idx; +#endif + +#ifdef MULTI_CHAN_SUPPORT + txq = &dev->tx.pkt_info[curr_chanctx_idx][token_id].pkt; +#else + txq = &dev->tx.pkt_info[token_id].pkt; +#endif + + for (pkt = 0; pkt < skb_queue_len(txq); pkt++) { + tx_done.frm_status[pkt] = TX_DONE_STAT_ERR_RETRY_LIM; + tx_done.rate[pkt] = 0; + } + + uccp420wlan_tx_complete(&tx_done, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + dev); + } + + return ret; +} + + +int uccp420wlan_tx_frame(struct sk_buff *skb, + struct ieee80211_sta *sta, + struct mac80211_dev *dev, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + bool bcast) +{ + unsigned int queue = 0; + unsigned int token_id = 0; + unsigned int more_frames = 0; + int ret = 0; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *mac_hdr = NULL; + struct umac_vif *uvif = NULL; + struct umac_sta *usta = NULL; + int peer_id = -1; +#ifdef MULTI_CHAN_SUPPORT + int off_chanctx_idx; +#endif + + uvif = (struct umac_vif *)(tx_info->control.vif->drv_priv); + + if (sta) { + usta = (struct umac_sta *)sta->drv_priv; + peer_id = usta->index; + } else { + peer_id = MAX_PEERS + uvif->vif_index; + } + + if (bcast == false) { + queue = tx_queue_map(skb->queue_mapping); + more_frames = 0; + dev->stats->tx_cmds_from_stack++; + } else { + queue = WLAN_AC_BCN; + /* Hack: skb->priority is used to indicate more frames */ + more_frames = skb->priority; + } + + + if (dev->params->production_test == 1) + tx_info->flags |= IEEE80211_TX_CTL_AMPDU; + +#ifdef MULTI_CHAN_SUPPORT + if ((tx_info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) || + (uvif->chanctx && + uvif->chanctx->index == dev->roc_off_chanctx_idx)) { + atomic_inc(&dev->roc_params.roc_mgmt_tx_count); + off_chanctx_idx = UMAC_VIF_CHANCTX_TYPE_OFF; + UCCP_DEBUG_ROC("%s:%d Sending OFFCHAN Frame: %d\n", + __func__, __LINE__, + atomic_read(&dev->roc_params.roc_mgmt_tx_count)); + } else { + off_chanctx_idx = UMAC_VIF_CHANCTX_TYPE_OPER; + } +#endif + + mac_hdr = (struct ieee80211_hdr *)(skb->data); + + UCCP_DEBUG_TX("%s-UMACTX:%s:%d ", + dev->name, + __func__, + __LINE__); + UCCP_DEBUG_TX("Waiting for Allocation:queue: %d qmap: %d is_bcn: %d\n", + queue, + skb->queue_mapping, + ieee80211_is_beacon(mac_hdr->frame_control)); + + token_id = uccp420wlan_tx_alloc_token(dev, + queue, +#ifdef MULTI_CHAN_SUPPORT + off_chanctx_idx, + curr_chanctx_idx, +#endif + peer_id, + skb); + + /* The frame was unable to find a reserved token */ + if (token_id == NUM_TX_DESCS) { + UCCP_DEBUG_TX("%s-UMACTX:%s:%d Token Busy Queued:\n", + dev->name, __func__, __LINE__); + return NETDEV_TX_OK; + } + + ret = __uccp420wlan_tx_frame(dev, + queue, + token_id, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + more_frames, + 0); + + + return NETDEV_TX_OK; +} + + +void uccp420wlan_proc_tx_complete(struct umac_event_tx_done *tx_done, + void *context) +{ + + struct mac80211_dev *dev = (struct mac80211_dev *)context; + struct sk_buff *skb, *tmp; + struct sk_buff_head *tx_done_list; + unsigned int pkt = 0; + + tx_done_list = &dev->tx.proc_tx_list[tx_done->descriptor_id]; + dev->stats->tx_done_recv_count++; + update_aux_adc_voltage(dev, tx_done->pdout_voltage); + skb_queue_walk_safe(tx_done_list, skb, tmp) { + __skb_unlink(skb, tx_done_list); + if (!skb) + continue; + hal_ops.unmap_tx_buf(tx_done->descriptor_id, pkt); + dev_kfree_skb_any(skb); + pkt++; + } + + /*send NEXT packet list*/ + if ((dev->params->pkt_gen_val == -1) || + (--dev->params->pkt_gen_val > 0)) + tasklet_schedule(&dev->proc_tx_tasklet); +} + +void uccp420wlan_tx_complete(struct umac_event_tx_done *tx_done, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + void *context) +{ + struct mac80211_dev *dev = (struct mac80211_dev *)context; + unsigned int more_frames = 0; + int vif_index = 0, vif_index_bitmap = 0, ret = 0; + unsigned int pkts_pending = 0; + unsigned char queue = 0; + struct umac_event_noa noa_event; + int token_id = 0; + int qlen = 0; + + token_id = tx_done->descriptor_id; + +#ifdef MULTI_CHAN_SUPPORT + qlen = skb_queue_len(&dev->tx.pkt_info[curr_chanctx_idx][token_id].pkt); +#else + qlen = skb_queue_len(&dev->tx.pkt_info[token_id].pkt); +#endif + + UCCP_DEBUG_TX("%s-UMACTX:TX Done Rx for desc_id: %d", + dev->name, + tx_done->descriptor_id); +#ifdef MULTI_CHAN_SUPPORT + UCCP_DEBUG_TX("Q: %d qlen: %d status: %d chactx: %d out_tok: %d\n", + tx_done->queue, + qlen, + tx_done->frm_status[0], + curr_chanctx_idx, + dev->tx.outstanding_tokens[tx_done->queue]); +#else + UCCP_DEBUG_TX("Q: %d qlen: %d status: %d out_tok: %d\n", + tx_done->queue, + qlen, + tx_done->frm_status[0], + dev->tx.outstanding_tokens[tx_done->queue]); +#endif + + update_aux_adc_voltage(dev, tx_done->pdout_voltage); + +#ifdef MULTI_CHAN_SUPPORT + if (tx_done->frm_status[0] == TX_DONE_STAT_DISCARD_CHSW) { + pkts_pending = uccp420wlan_proc_tx_dscrd_chsw(dev, + curr_chanctx_idx, + tx_done); + goto out; + } +#endif + pkts_pending = uccp420wlan_tx_free_buff_req(dev, + tx_done, + &queue, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + &vif_index_bitmap); + + if (pkts_pending) { + /*TODO..Do we need to check each skb for more_frames??*/ + more_frames = 0; + + UCCP_DEBUG_TX("%s-UMACTX:%s:%d Transfer Pending Frames:\n", + dev->name, + __func__, + __LINE__); + + ret = __uccp420wlan_tx_frame(dev, + queue, + token_id, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + more_frames, + 0); + + } else { + DEBUG_LOG("%s-UMACTX:No Pending Packets\n", dev->name); + } + +#ifdef MULTI_CHAN_SUPPORT +out: +#endif + + for (vif_index = 0; vif_index < MAX_VIFS; vif_index++) { + if (vif_index_bitmap & (1 << vif_index)) { + memset(&noa_event, 0, sizeof(noa_event)); + noa_event.if_index = vif_index; + uccp420wlan_noa_event(FROM_TX_DONE, + &noa_event, + (void *)dev, + NULL); + } + } +} + + +#ifdef MULTI_CHAN_SUPPORT +static int uccp420_flush_vif_all_pend_q(struct mac80211_dev *dev, + struct umac_vif *uvif, + unsigned int hw_queue_map, + enum UMAC_VIF_CHANCTX_TYPE chanctx_type) +{ + unsigned int pending = 0; + int count = 0; + int peer_id = -1; + unsigned int queue = 0; + int pend_q = 0; + struct sk_buff_head *pend_pkt_q = NULL; + struct tx_config *tx = NULL; + struct ieee80211_sta *sta = NULL; + struct umac_sta *usta = NULL; + bool warned = false; + + tx = &dev->tx; + +#ifdef MULTI_CHAN_SUPPORT + if (!uvif->chanctx) { + UCCP_DEBUG_TSMC("%s: Chanctx NULL, returning\n", + __func__); + return -1; + } +#endif + + for (queue = 0; queue < NUM_ACS; queue++) { + if (!(BIT(queue) & hw_queue_map)) + continue; + + for (pend_q = 0; pend_q < MAX_PEND_Q_PER_AC; pend_q++) { + if (pend_q < MAX_PEERS) { + rcu_read_lock(); + sta = rcu_dereference(dev->peers[pend_q]); + + if (!sta) { + rcu_read_unlock(); + continue; + } + + usta = (struct umac_sta *)(sta->drv_priv); + + if (usta->vif_index == uvif->vif_index) + peer_id = pend_q; + else { + rcu_read_unlock(); + continue; + } + + rcu_read_unlock(); + } else if (pend_q == uvif->vif_index) + peer_id = uvif->vif_index; + else + continue; + + while (1) { + spin_lock_bh(&tx->lock); + + pend_pkt_q = + &tx->pending_pkt[chanctx_type] + [peer_id] + [queue]; + + /* Assuming all packets for the peer have same + * channel context + */ + pending = skb_queue_len(pend_pkt_q); + + spin_unlock_bh(&tx->lock); + + if (!pending) + break; + + if (!warned && + count >= QUEUE_FLUSH_TIMEOUT_TICKS) { + pr_err("%s: Timeout: VIF: %d Queue: %d pending: %d: LMAC probably STUCK\n", + dev->name, + uvif->vif_index, + queue, + pending); + WARN_ON(1); + warned = true; + } + + current->state = TASK_INTERRUPTIBLE; + + if (0 == schedule_timeout(1)) + count++; + + } + + if (pending) { + pr_err("%s-UMACTX: Failed for VIF: %d ", + dev->name, + uvif->vif_index); + pr_err(" and Queue: %d, pending: %d\n", + queue, + pending); + + return -1; + } + } + } + + UCCP_DEBUG_TSMC("%s: Success for VIF: %d and Queue: %d\n", + __func__, + uvif->vif_index, + queue); + return 0; +} +#endif + + +#ifdef MULTI_CHAN_SUPPORT +static int uccp420_flush_vif_tx_queues(struct mac80211_dev *dev, + struct umac_vif *uvif, + int chanctx_idx, + unsigned int hw_queue_map) +{ + unsigned int tokens = 0; + unsigned int i = 0; + unsigned long buf_pool_bmp = 0; + struct tx_pkt_info *pkt_info = NULL; + struct tx_config *tx = NULL; + int count = 0; + bool warned = false; + + tx = &dev->tx; + + spin_lock_bh(&tx->lock); + + for (i = 0; i < NUM_TX_DESCS; i++) { + pkt_info = &tx->pkt_info[chanctx_idx][i]; + + if ((pkt_info->vif_index == uvif->vif_index) && + (BIT(pkt_info->queue) & hw_queue_map)) + tokens |= BIT(i); + } + + spin_unlock_bh(&tx->lock); + + if (!tokens) + return 0; + + while (1) { + spin_lock_bh(&tx->lock); + buf_pool_bmp = tx->buf_pool_bmp[0]; + spin_unlock_bh(&tx->lock); + + if (!(buf_pool_bmp & tokens)) + break; + + if (!warned && + count >= QUEUE_FLUSH_TIMEOUT_TICKS) { + pr_err("%s-UMACTX: Failed for VIF: %d, buf_pool_bmp : 0x%lx: LMAC probably STUCK\n", + dev->name, + uvif->vif_index, + buf_pool_bmp); + WARN_ON(1); + warned = true; + } + + current->state = TASK_INTERRUPTIBLE; + + if (0 == schedule_timeout(1)) + count++; + } + + UCCP_DEBUG_TSMC("%s: Success for VIF: %d, buf_pool_bmp : 0x%lx\n", + __func__, + uvif->vif_index, + buf_pool_bmp); + return 0; +} +#endif + + +#ifdef MULTI_CHAN_SUPPORT +int uccp420_flush_vif_queues(struct mac80211_dev *dev, + struct umac_vif *uvif, + int chanctx_idx, + unsigned int hw_queue_map, + enum UMAC_VIF_CHANCTX_TYPE vif_chanctx_type) +{ + int result = -1; + + result = uccp420_flush_vif_all_pend_q(dev, + uvif, + hw_queue_map, + vif_chanctx_type); + + if (result == 0) { + result = uccp420_flush_vif_tx_queues(dev, + uvif, + chanctx_idx, + hw_queue_map); + } + + return result; +} +#endif diff --git a/drivers/net/wireless/uccp420wlan/src/umac_if.c b/drivers/net/wireless/uccp420wlan/src/umac_if.c new file mode 100644 index 00000000000000..f56159559e674d --- /dev/null +++ b/drivers/net/wireless/uccp420wlan/src/umac_if.c @@ -0,0 +1,2483 @@ +/* + * File Name : umac_if.c + * + * This file contains the defintions of helper functions for UMAC comms + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include + +#include "umac_if.h" +#include "core.h" + +#define UCCP_DEBUG_IF(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_IF) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +#define UCCP_DEBUG_FAIL_SAFE(fmt, ...) \ +do { \ + if (uccp_debug & UCCP_DEBUG_FAIL_SAFE) \ + pr_debug(fmt, ##__VA_ARGS__); \ +} while (0) + +unsigned char wildcard_ssid[7] = "DIRECT-"; +#ifdef CONFIG_PM +unsigned char rx_interrupt_status; +#endif + +struct cmd_send_recv_cnt cmd_info; + +struct lmac_if_data { + char *name; + void *context; +}; + +static struct lmac_if_data __rcu *lmac_if; + +static void update_mcs_packet_stat(int mcs_rate_num, + int rate_flags, + struct mac80211_dev *dev) +{ + if (rate_flags & ENABLE_11N_FORMAT) { + switch (mcs_rate_num) { + case 0: + dev->stats->ht_tx_mcs0_packet_count++; + break; + case 1: + dev->stats->ht_tx_mcs1_packet_count++; + break; + case 2: + dev->stats->ht_tx_mcs2_packet_count++; + break; + case 3: + dev->stats->ht_tx_mcs3_packet_count++; + break; + case 4: + dev->stats->ht_tx_mcs4_packet_count++; + break; + case 5: + dev->stats->ht_tx_mcs5_packet_count++; + break; + case 6: + dev->stats->ht_tx_mcs6_packet_count++; + break; + case 7: + dev->stats->ht_tx_mcs7_packet_count++; + break; + case 8: + dev->stats->ht_tx_mcs8_packet_count++; + break; + case 9: + dev->stats->ht_tx_mcs9_packet_count++; + break; + case 10: + dev->stats->ht_tx_mcs10_packet_count++; + break; + case 11: + dev->stats->ht_tx_mcs11_packet_count++; + break; + case 12: + dev->stats->ht_tx_mcs12_packet_count++; + break; + case 13: + dev->stats->ht_tx_mcs13_packet_count++; + break; + case 14: + dev->stats->ht_tx_mcs14_packet_count++; + break; + case 15: + dev->stats->ht_tx_mcs15_packet_count++; + break; + default: + break; + } + } else if (rate_flags & ENABLE_VHT_FORMAT) { + switch (mcs_rate_num) { + case 0: + dev->stats->vht_tx_mcs0_packet_count++; + break; + case 1: + dev->stats->vht_tx_mcs1_packet_count++; + break; + case 2: + dev->stats->vht_tx_mcs2_packet_count++; + break; + case 3: + dev->stats->vht_tx_mcs3_packet_count++; + break; + case 4: + dev->stats->vht_tx_mcs4_packet_count++; + break; + case 5: + dev->stats->vht_tx_mcs5_packet_count++; + break; + case 6: + dev->stats->vht_tx_mcs6_packet_count++; + break; + case 7: + dev->stats->vht_tx_mcs7_packet_count++; + break; + case 8: + dev->stats->vht_tx_mcs8_packet_count++; + break; + case 9: + dev->stats->vht_tx_mcs9_packet_count++; + break; + default: + break; + } + } +} + + +static void get_rate(struct sk_buff *skb, + struct cmd_tx_ctrl *txcmd, + struct tx_pkt_info *pkt_info, + bool retry, + struct mac80211_dev *dev) +{ + struct ieee80211_rate *rate; + struct ieee80211_tx_info *c; + unsigned int index; + bool is_mcs = false, is_mgd = false; + struct ieee80211_tx_rate *txrate; + unsigned char mcs_rate_num = 0; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + int mcs_indx; + int mgd_rate; + int prot_type; + + /* Normal Mode*/ + rate = ieee80211_get_tx_rate(dev->hw, IEEE80211_SKB_CB(skb)); + + if (rate == NULL) { + rate = &dev->hw->wiphy->bands[ + dev->hw->conf.chandef.chan->band]->bitrates[0]; + txcmd->num_rates = 1; + txcmd->rate[0] = rate->hw_value; + txcmd->rate_retries[0] = 5; + txcmd->rate_protection_type[0] = USE_PROTECTION_NONE; + txcmd->rate_preamble_type[0] = DONT_USE_SHORT_PREAMBLE; + + return; + } + + c = IEEE80211_SKB_CB(skb); + /* Some defaults*/ + txcmd->num_rates = 0; + txcmd->stbc_enabled = 0; + + /* BCC (or) LDPC */ + if (c->flags & IEEE80211_TX_CTL_LDPC) + txcmd->bcc_or_ldpc = 1; + else + txcmd->bcc_or_ldpc = 0; + + if (ieee80211_is_data(hdr->frame_control) && + c->flags & IEEE80211_TX_CTL_AMPDU) { + txcmd->aggregate_mpdu = AMPDU_AGGR_ENABLED; + } + + for (index = 0; index < 4; index++) { + txrate = (&c->control.rates[index]); + txcmd->rate_flags[index] = 0; + + if (txrate->idx < 0) + continue; + + txcmd->num_spatial_streams[index] = 1; + + /* production test*/ + if (dev->params->production_test == 1 && + dev->params->tx_fixed_mcs_indx != -1) { + txcmd->rate_preamble_type[index] = + dev->params->prod_mode_rate_preamble_type; + txcmd->rate_flags[index] = + dev->params->prod_mode_rate_flag; + txcmd->rate[index] = 0x80; + txcmd->rate[index] |= + (dev->params->tx_fixed_mcs_indx); + txcmd->num_spatial_streams[index] = + dev->params->num_spatial_streams; + txcmd->bcc_or_ldpc = + dev->params->prod_mode_bcc_or_ldpc; + txcmd->stbc_enabled = + dev->params->prod_mode_stbc_enabled; + update_mcs_packet_stat( + dev->params->tx_fixed_mcs_indx, + txcmd->rate_flags[index], dev); + txcmd->num_rates++; + break; + } else if (dev->params->production_test == 1 && + dev->params->tx_fixed_rate != -1) { + txcmd->rate_preamble_type[index] = + dev->params->prod_mode_rate_preamble_type; + txcmd->rate_flags[index] = + dev->params->prod_mode_rate_flag; + + txcmd->rate[index] = 0x00; + if (dev->params->tx_fixed_rate == 55) + txcmd->rate[index] |= + ((dev->params->tx_fixed_rate) / + 5); + else + txcmd->rate[index] |= + ((dev->params->tx_fixed_rate * + 10) / 5); + txcmd->num_spatial_streams[index] = 1; + txcmd->bcc_or_ldpc = 0; + txcmd->stbc_enabled = 0; + txcmd->num_rates++; + break; + } + /* No input from production_test proc, continue and use + * info from mac80211 RC + */ + + /* It is an VHT MCS rate */ + if (((txrate->flags & IEEE80211_TX_RC_MCS) || + (txrate->flags & IEEE80211_TX_RC_VHT_MCS)) && + txrate->flags & IEEE80211_TX_RC_VHT_MCS) { + /*idx field is split + * into a higher 4 bits (Nss), starts + * with 0 and lower 4 bits (MCS number) + */ + is_mcs = true; + mcs_rate_num = (txrate->idx & 0x0F); + txcmd->num_spatial_streams[index] = + ((txrate->idx & 0xF0) >> 4) + 1; + /* STBC Enabled/Disabled: valid Nss = 1 */ + if (txcmd->num_spatial_streams[index] == 1 && + (c->flags & IEEE80211_TX_CTL_STBC)) + txcmd->stbc_enabled = 1; + + } else if (((txrate->flags & IEEE80211_TX_RC_MCS) || + (txrate->flags & IEEE80211_TX_RC_VHT_MCS)) && + txrate->flags & IEEE80211_TX_RC_MCS) { /*HT rate */ + is_mcs = true; + mcs_rate_num = txrate->idx; + + /* Update No of Spatial streams*/ + if (mcs_rate_num < 8) { + txcmd->num_spatial_streams[index] = 1; + } else if (mcs_rate_num > 7 && + mcs_rate_num < 16) { + txcmd->num_spatial_streams[index] = 2; + } else { + pr_err("UCCP420_WIFI: Invalid MCS index: %d, Supports only 2 spatial streams\n", + mcs_rate_num); + } + + /* Ensures good throughput */ + if (mcs_rate_num > 15 && + dev->params->uccp_num_spatial_streams == 1) { + mcs_rate_num = 7; + txcmd->num_spatial_streams[index] = 1; + } else if (mcs_rate_num > 15 && + dev->params->uccp_num_spatial_streams == 2) { + mcs_rate_num = 15; + txcmd->num_spatial_streams[index] = 2; + } + + /* STBC Enabled/Disabled: valid for Nss=1 */ + if (mcs_rate_num < 8 && + (c->flags & IEEE80211_TX_CTL_STBC)) + txcmd->stbc_enabled = 1; + + } else if (((txrate->flags & IEEE80211_TX_RC_MCS) || + (txrate->flags & IEEE80211_TX_RC_VHT_MCS))) { + is_mcs = true; + WARN_ON(1); + } + + /* Rate FORMAT*/ + if (txrate->flags & IEEE80211_TX_RC_VHT_MCS) + txcmd->rate_flags[index] |= ENABLE_VHT_FORMAT; + else if (txrate->flags & IEEE80211_TX_RC_MCS) + txcmd->rate_flags[index] |= ENABLE_11N_FORMAT; + + mcs_indx = dev->params->mgd_mode_tx_fixed_mcs_indx; + mgd_rate = dev->params->mgd_mode_tx_fixed_rate; + + /* Rate Index: + * From proc (only for data packets) + * From RC in mac80211 + * Can be MCS(HT/VHT) or Rate (11abg) + */ + if (ieee80211_is_data(hdr->frame_control) && mcs_indx != -1) { + is_mgd = true; + + txcmd->rate[index] = 0x80; + txcmd->rate[index] |= (mcs_indx); + txcmd->rate_flags[index] = + dev->params->prod_mode_rate_flag; + txcmd->num_spatial_streams[index] = + dev->params->num_spatial_streams; + txcmd->bcc_or_ldpc = + dev->params->prod_mode_bcc_or_ldpc; + txcmd->stbc_enabled = + dev->params->prod_mode_stbc_enabled; + + update_mcs_packet_stat(mcs_indx, + txcmd->rate_flags[index], + dev); + } else if (ieee80211_is_data(hdr->frame_control) && + mgd_rate != -1) { + is_mgd = true; + txcmd->rate[index] = 0x80; + txcmd->rate[index] = 0x00; + + if (mgd_rate == 55) + txcmd->rate[index] |= ((mgd_rate) / 5); + else + txcmd->rate[index] |= ((mgd_rate * 10) / 5); + + txcmd->rate_flags[index] = 0; + txcmd->num_spatial_streams[index] = 1; + txcmd->bcc_or_ldpc = 0; + txcmd->stbc_enabled = 0; + } else if (is_mcs) { /* idx is MCS */ + /* Now mark MSB to tell LMAC that it is a MCS Index */ + txcmd->rate[index] = 0x80; + txcmd->rate[index] |= mcs_rate_num; + update_mcs_packet_stat(mcs_rate_num, + txcmd->rate_flags[index], + dev); + } else if (!is_mcs) { /* idx is RATE...*/ + rate = &dev->hw->wiphy->bands[ + c->band]->bitrates[ + c->control.rates[index].idx]; + /* Now mark MSB to tell LMAC that it is a rate*/ + txcmd->rate[index] = 0x00; + txcmd->rate[index] |= rate->hw_value; + /* using rate so 11g/11b/11a */ + txcmd->num_spatial_streams[index] = 1; + } + + if (txcmd->rate_flags[index] & ENABLE_VHT_FORMAT) { + /*Enabled for all ucast/bcast/mcast frames*/ + txcmd->aggregate_mpdu = AMPDU_AGGR_ENABLED; + } + + txcmd->rate_retries[index] = + c->control.rates[index].count; + if (c->control.rates[index].flags & + IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + txcmd->rate_preamble_type[index] = + USE_SHORT_PREAMBLE; + else + txcmd->rate_preamble_type[index] = + DONT_USE_SHORT_PREAMBLE; + + prot_type = USE_PROTECTION_NONE; + + if (dev->params->rate_protection_type == 1) { + /* Protection*/ + if (c->control.rates[index].flags & + IEEE80211_TX_RC_USE_CTS_PROTECT) + prot_type = USE_PROTECTION_CTS2SELF; + else if (c->control.rates[index].flags & + IEEE80211_TX_RC_USE_RTS_CTS) + prot_type = USE_PROTECTION_RTS; + else + prot_type = USE_PROTECTION_NONE; + + if (txcmd->aggregate_mpdu == AMPDU_AGGR_ENABLED) + prot_type = USE_PROTECTION_RTS; + + if (c->control.rates[index].flags & + IEEE80211_TX_RC_40_MHZ_WIDTH) + prot_type = USE_PROTECTION_RTS; + + /*RTS threshold: Check for PSDU length + * Need to add all HW added lenghts to skb, + * sw added lengths are already part of skb->len + * IV ==> Always SW + * MIC for CCMP ==> HW (MMIC for TKIP ==> SW) + * ICV ==> HW + * FCS ==> HW + */ + if (ieee80211_is_data(hdr->frame_control) && + !is_multicast_ether_addr(hdr->addr1) && + ieee80211_has_protected(hdr->frame_control)) { + if (skb->len + + c->control.hw_key->icv_len + + FCS_LEN > dev->rts_threshold) + prot_type = USE_PROTECTION_RTS; + } + + if (ieee80211_is_data(hdr->frame_control) && + !is_multicast_ether_addr(hdr->addr1) && + !ieee80211_has_protected(hdr->frame_control) && + (skb->len + FCS_LEN > dev->rts_threshold)) + prot_type = USE_PROTECTION_RTS; + + } + + /*No 3rd party device is using this, so diable for now*/ + if (txcmd->rate_flags[index] & ENABLE_VHT_FORMAT) + prot_type = USE_PROTECTION_NONE; + + txcmd->rate_protection_type[index] = prot_type; + + + /* Do not set the flags for Managed Mode, they will come + * from proc + */ + if (!is_mgd) { + if (c->control.rates[index].flags & + IEEE80211_TX_RC_GREEN_FIELD) + txcmd->rate_flags[index] |= + ENABLE_GREEN_FIELD; + if (c->control.rates[index].flags & + IEEE80211_TX_RC_40_MHZ_WIDTH) + txcmd->rate_flags[index] |= + ENABLE_CHNL_WIDTH_40MHZ; + if (c->control.rates[index].flags & + IEEE80211_TX_RC_80_MHZ_WIDTH) + txcmd->rate_flags[index] |= + ENABLE_CHNL_WIDTH_80MHZ; + if (c->control.rates[index].flags & + IEEE80211_TX_RC_SHORT_GI) + txcmd->rate_flags[index] |= ENABLE_SGI; + } + + /*Some Sanity Checks*/ + /* Nss-1/2 */ + if (txcmd->num_spatial_streams[index] <= 0 || + txcmd->num_spatial_streams[index] > 2) + txcmd->num_spatial_streams[index] = 1; + + /* VHT 20MHz MCS9 is not valid*/ + if (txrate->flags & IEEE80211_TX_RC_VHT_MCS && + ((txcmd->rate[index] & 0x7F) == 9) && + !(txcmd->rate_flags[index] & + ENABLE_CHNL_WIDTH_40MHZ) && + !(txcmd->rate_flags[index] & + ENABLE_CHNL_WIDTH_80MHZ)) + /* Downgrade to VHT-MCS8-Nss-1 */ + txcmd->rate[index] = 0x88; + + /*First Time*/ +#ifdef notyet + if (!retry) { +#endif + if (!index) + pkt_info->max_retries = 0; + pkt_info->max_retries += + txcmd->rate_retries[index]; +#ifdef notyet + pkt_info->retries[index] = + txcmd->rate_retries[index]; + UCCP_DEBUG_IF("%s-UMACTX : Using MINSTREL rates\n", + dev->name); + } else { + txcmd->rate_retries[index] = + pkt_info->retries[index]; + UCCP_DEBUG_IF("%s-UMACTX : Using Adjusted rates\n", + dev->name); + } +#endif + + txcmd->num_rates++; + } +} + + +static int uccp420wlan_send_cmd(unsigned char *buf, + unsigned int len, + unsigned char id) +{ + struct host_mac_msg_hdr *hdr = (struct host_mac_msg_hdr *)buf; + struct sk_buff *nbuf; + struct lmac_if_data *p; + struct mac80211_dev *dev; + + rcu_read_lock(); + + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + pr_err("%s: Unable to retrieve lmac_if\n", __func__); +#ifdef DRIVER_DEBUG + WARN_ON(1); +#endif + rcu_read_unlock(); + return -1; + } + dev = p->context; + nbuf = alloc_skb(len, GFP_ATOMIC); + + if (!nbuf) { + rcu_read_unlock(); + return -1; + } + hdr->id = id; + hdr->length = len; + UCCP_DEBUG_IF("%s-UMACIF: Sending command:%d, outstanding_cmds: %d\n", + p->name, hdr->id, cmd_info.outstanding_ctrl_req); + hdr->descriptor_id = 0; + hdr->descriptor_id |= 0x0000ffff; + memcpy(skb_put(nbuf, len), buf, len); + + dev->stats->outstanding_cmd_cnt = cmd_info.outstanding_ctrl_req; + + /* Take lock to make the control commands sequential in case of SMP*/ + spin_lock_bh(&cmd_info.control_path_lock); + + if (cmd_info.outstanding_ctrl_req < MAX_OUTSTANDING_CTRL_REQ) { + UCCP_DEBUG_IF("Sending the CMD, got Access\n"); + hal_ops.send((void *)nbuf, HOST_MOD_ID, UMAC_MOD_ID, 0); + dev->stats->gen_cmd_send_count++; + } else { + UCCP_DEBUG_IF("Sending the CMD, Waiting in Queue: %d\n", + cmd_info.outstanding_ctrl_req); + skb_queue_tail(&cmd_info.outstanding_cmd, nbuf); + } + + /* sent but still no proc_done / unsent due to pending requests */ + cmd_info.outstanding_ctrl_req++; + spin_unlock_bh(&cmd_info.control_path_lock); + rcu_read_unlock(); + + return 0; +} + + +int uccp420wlan_prog_reset(unsigned int reset_type, unsigned int lmac_mode) +{ + struct cmd_reset reset; + struct mac80211_dev *dev; + struct lmac_if_data *p; + unsigned int i; + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + rcu_read_unlock(); + dev = p->context; + + memset(&reset, 0, sizeof(struct cmd_reset)); + + reset.type = reset_type; + + if (reset_type == LMAC_ENABLE) { + UCCP_DEBUG_IF("ed = %d auto = %d\n", + dev->params->ed_sensitivity, + dev->params->auto_sensitivity); + reset.ed_sensitivity = dev->params->ed_sensitivity; + reset.auto_sensitivity = dev->params->auto_sensitivity; + reset.include_rxmac_hdr = 0; + reset.num_spatial_streams = + dev->params->uccp_num_spatial_streams; + reset.lmac_mode = lmac_mode; + reset.antenna_sel = dev->params->antenna_sel; + + if (dev->params->production_test == 0 && + dev->params->bypass_vpd == 0) { + memcpy(reset.rf_params, dev->params->rf_params_vpd, + RF_PARAMS_SIZE); + } else { + memcpy(reset.rf_params, dev->params->rf_params, + RF_PARAMS_SIZE); + } + + reset.system_rev = dev->stats->system_rev; + reset.bg_scan.enabled = dev->params->bg_scan_enable; + + if (reset.bg_scan.enabled) { + for (i = 0; i < dev->params->bg_scan_num_channels; + i++) { + reset.bg_scan.channel_list[i] = + dev->params->bg_scan_channel_list[i]; + reset.bg_scan.channel_flags[i] = + dev->params->bg_scan_channel_flags[i]; + } + reset.bg_scan.num_channels = + dev->params->bg_scan_num_channels; + reset.bg_scan.scan_intval = + dev->params->bg_scan_intval; + reset.bg_scan.channel_dur = + /* Channel spending time */ + dev->params->bg_scan_chan_dur; + + reset.bg_scan.serv_channel_dur = + /* operating channel spending time */ + dev->params->bg_scan_serv_chan_dur; + } + } + + return uccp420wlan_send_cmd((unsigned char *) &reset, + sizeof(struct cmd_reset), UMAC_CMD_RESET); +} + +int uccp420wlan_proc_tx(void) +{ + struct cmd_tx_ctrl tx_cmd; + struct sk_buff *nbuf, *nbuf_start, *tmp, *skb; + unsigned char *data; + struct lmac_if_data *p; + struct mac80211_dev *dev; + struct sk_buff_head *skb_list; + struct ieee80211_hdr *mac_hdr; + unsigned int index = 0, descriptor_id = 0, queue = WLAN_AC_BE, pkt = 0; + u16 hdrlen = 26; + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + memset(&tx_cmd, 0, sizeof(struct cmd_tx_ctrl)); + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + dev = p->context; + skb_list = &dev->tx.proc_tx_list[descriptor_id]; + tx_cmd.hdr.id = UMAC_CMD_TX; + /* Keep the queue num and pool id in descriptor id */ + tx_cmd.hdr.descriptor_id = 0; + tx_cmd.hdr.descriptor_id |= ((queue & 0x0000FFFF) << 16); + tx_cmd.hdr.descriptor_id |= (descriptor_id & 0x0000FFFF); + /* Not used anywhere currently */ + tx_cmd.hdr.length = sizeof(struct cmd_tx_ctrl); + + /* UMAC_CMD_TX*/ + tx_cmd.if_index = 0; + tx_cmd.queue_num = queue; + tx_cmd.more_frms = 0; + tx_cmd.descriptor_id = descriptor_id; + tx_cmd.num_frames_per_desc = skb_queue_len(skb_list); + tx_cmd.pkt_gram_payload_len = hdrlen; + tx_cmd.aggregate_mpdu = AMPDU_AGGR_DISABLED; + + /* production test*/ + tx_cmd.num_rates = 1; + if (dev->params->tx_fixed_mcs_indx != -1) { + tx_cmd.rate_preamble_type[index] = + dev->params->prod_mode_rate_preamble_type; + tx_cmd.rate_flags[index] = + dev->params->prod_mode_rate_flag; + tx_cmd.rate[index] = 0x80; + tx_cmd.rate[index] |= + (dev->params->tx_fixed_mcs_indx); + tx_cmd.num_spatial_streams[index] = + dev->params->num_spatial_streams; + tx_cmd.bcc_or_ldpc = + dev->params->prod_mode_bcc_or_ldpc; + tx_cmd.stbc_enabled = + dev->params->prod_mode_stbc_enabled; + update_mcs_packet_stat( + dev->params->tx_fixed_mcs_indx, + tx_cmd.rate_flags[index], dev); + tx_cmd.num_rates++; + } else if (dev->params->tx_fixed_rate != -1) { + tx_cmd.rate_preamble_type[index] = + dev->params->prod_mode_rate_preamble_type; + tx_cmd.rate_flags[index] = + dev->params->prod_mode_rate_flag; + + tx_cmd.rate[index] = 0x00; + if (dev->params->tx_fixed_rate == 55) + tx_cmd.rate[index] |= + ((dev->params->tx_fixed_rate) / + 5); + else + tx_cmd.rate[index] |= + ((dev->params->tx_fixed_rate * + 10) / 5); + tx_cmd.num_spatial_streams[index] = 1; + tx_cmd.bcc_or_ldpc = 0; + tx_cmd.stbc_enabled = 0; + tx_cmd.num_rates++; + } else { + WARN_ON(1); + rcu_read_unlock(); + return -90; + } + + nbuf = alloc_skb(sizeof(struct cmd_tx_ctrl) + + tx_cmd.num_frames_per_desc * + MAX_GRAM_PAYLOAD_LEN, GFP_ATOMIC); + + data = skb_put(nbuf, sizeof(struct cmd_tx_ctrl)); + memset(data, 0, sizeof(struct cmd_tx_ctrl)); + /*store the start for later use*/ + nbuf_start = (struct sk_buff *)data; + memcpy(data, &tx_cmd, sizeof(struct cmd_tx_ctrl)); + pkt = 0; + skb_queue_walk_safe(skb_list, skb, tmp) { + if (!skb || (pkt > tx_cmd.num_frames_per_desc)) + break; + + mac_hdr = (struct ieee80211_hdr *)skb->data; + /* Complete packet length*/ + ((struct cmd_tx_ctrl *)nbuf_start)->pkt_length[pkt] = skb->len; + skb_put(nbuf, MAX_GRAM_PAYLOAD_LEN); + memcpy((unsigned char *)nbuf_start + + sizeof(struct cmd_tx_ctrl)+ + (pkt * MAX_GRAM_PAYLOAD_LEN), + mac_hdr, hdrlen); + + skb_pull(skb, hdrlen); + if (hal_ops.map_tx_buf(descriptor_id, pkt, + skb->data, skb->len)) { + rcu_read_unlock(); + dev_kfree_skb_any(nbuf); + return -30; + } + pkt++; + } + hal_ops.send((void *)nbuf, HOST_MOD_ID, UMAC_MOD_ID, + (void *) skb_list); + /* increment tx_cmd_send_count to keep track of number of + * tx_cmd send + */ + if (skb_queue_len(skb_list) == 1) + dev->stats->tx_cmd_send_count_single++; + else if (skb_queue_len(skb_list) > 1) + dev->stats->tx_cmd_send_count_multi++; + + rcu_read_unlock(); + + return 0; +} + +int uccp420wlan_prog_txpower(unsigned int txpower) +{ + struct cmd_tx_pwr power; + + memset(&power, 0, sizeof(struct cmd_tx_pwr)); + power.tx_pwr = txpower; + power.if_index = 0; + + return uccp420wlan_send_cmd((unsigned char *) &power, + sizeof(struct cmd_tx_pwr), + UMAC_CMD_TX_POWER); +} + + +int uccp420wlan_prog_btinfo(unsigned int bt_state) +{ + struct cmd_bt_info bt_info; + + memset(&bt_info, 0, sizeof(struct cmd_bt_info)); + bt_info.bt_state = bt_state; + + return uccp420wlan_send_cmd((unsigned char *) &bt_info, + sizeof(struct cmd_bt_info), + UMAC_CMD_BT_INFO); +} + + +int uccp420wlan_prog_vif_ctrl(int index, + unsigned char *mac_addr, + unsigned int vif_type, + unsigned int op) +{ + struct cmd_vifctrl vif_ctrl; + + memset(&vif_ctrl, 0, sizeof(struct cmd_vifctrl)); + vif_ctrl.mode = vif_type; + memcpy(vif_ctrl.mac_addr, mac_addr, 6); + vif_ctrl.if_index = index; + vif_ctrl.if_ctrl = op; + + return uccp420wlan_send_cmd((unsigned char *) &vif_ctrl, + sizeof(struct cmd_vifctrl), + UMAC_CMD_VIF_CTRL); +} + + +int uccp420wlan_prog_mcast_addr_cfg(unsigned char *mcast_addr, + unsigned int op) +{ + struct cmd_mcst_addr_cfg mcast_config; + + memset(&mcast_config, 0, sizeof(struct cmd_mcst_addr_cfg)); + + mcast_config.op = op; + memcpy(mcast_config.mac_addr, mcast_addr, 6); + + return uccp420wlan_send_cmd((unsigned char *) &mcast_config, + sizeof(struct cmd_mcst_addr_cfg), + UMAC_CMD_MCST_ADDR_CFG); +} + + +int uccp420wlan_prog_mcast_filter_control(unsigned int mcast_filter_enable) +{ + struct cmd_mcst_filter_ctrl mcast_ctrl; + + memset(&mcast_ctrl, 0, sizeof(struct cmd_mcst_filter_ctrl)); + mcast_ctrl.ctrl = mcast_filter_enable; + + return uccp420wlan_send_cmd((unsigned char *) &mcast_ctrl, + sizeof(struct cmd_mcst_filter_ctrl), + UMAC_CMD_MCST_FLTR_CTRL); +} + + +int uccp420wlan_prog_vht_bform(unsigned int vht_beamform_status, + unsigned int vht_beamform_period) +{ + struct cmd_vht_beamform vht_beamform; + + memset(&vht_beamform, 0, sizeof(struct cmd_vht_beamform)); + + vht_beamform.vht_beamform_status = vht_beamform_status; + vht_beamform.vht_beamform_period = vht_beamform_period; + + return uccp420wlan_send_cmd((unsigned char *) &vht_beamform, + sizeof(struct cmd_vht_beamform), + UMAC_CMD_VHT_BEAMFORM_CTRL); +} + + +int uccp420wlan_prog_roc(unsigned int roc_ctrl, + unsigned int roc_channel, + unsigned int roc_duration, + unsigned int roc_type) +{ + struct cmd_roc cmd_roc; + + memset(&cmd_roc, 0, sizeof(struct cmd_roc)); + + cmd_roc.roc_ctrl = roc_ctrl; + cmd_roc.roc_channel = roc_channel; + cmd_roc.roc_duration = roc_duration; + cmd_roc.roc_type = roc_type; + + return uccp420wlan_send_cmd((unsigned char *) &cmd_roc, + sizeof(struct cmd_roc), UMAC_CMD_ROC_CTRL); +} + + +int uccp420wlan_prog_nw_selection(unsigned int nw_select_enabled, + unsigned char *mac_addr) +{ + struct cmd_nw_selection nw_select; + unsigned char req_ie[] = {0xdd, 0x7d, 0x00, 0x50, 0xf2, 0x04, 0x10, + 0x4a, 0x00, 0x01, 0x10, 0x10, 0x3a, 0x00, + 0x01, 0x01, 0x10, 0x08, 0x00, 0x02, 0x23, + 0x88, 0x10, 0x47, 0x00, 0x10, 0x09, 0x0d, + 0xf9, 0x4b, 0xf7, 0xab, 0x54, 0x75, 0x8b, + 0x4b, 0x91, 0x94, 0x5a, 0x3c, 0xb0, 0xda, + 0x10, 0x54, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x50, 0xf2, 0x04, 0x00, 0x01, 0x10, 0x3c, + 0x00, 0x01, 0x01, 0x10, 0x02, 0x00, 0x02, + 0x00, 0x00, 0x10, 0x09, 0x00, 0x02, 0x00, + 0x00, 0x10, 0x12, 0x00, 0x02, 0x00, 0x00, + 0x10, 0x21, 0x00, 0x09, 0x48, 0x65, 0x6c, + 0x6c, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x10, + 0x23, 0x00, 0x06, 0x57, 0x50, 0x53, 0x32, + 0x2e, 0x30, 0x10, 0x24, 0x00, 0x04, 0x30, + 0x2e, 0x38, 0x78, 0x10, 0x11, 0x00, 0x02, + 0x4d, 0x4d, 0x10, 0x49, 0x00, 0x09, 0x00, + 0x37, 0x2a, 0x00, 0x01, 0x20, 0x03, 0x01, + 0x01, 0xdd, 0x11, 0x50, 0x6f, 0x9a, 0x09, + 0x02, 0x02, 0x00, 0x23, 0x00, 0x06, 0x05, + 0x00, 0x58, 0x58, 0x04, 0x51, 0x0b}; + + unsigned char resp_ie[] = {0xdd, 0x70, 0x00, 0x50, 0xf2, 0x04, 0x10, + 0x4a, 0x00, 0x01, 0x10, 0x10, 0x44, 0x00, + 0x01, 0x01, 0x10, 0x3b, 0x00, 0x01, 0x00, + 0x10, 0x47, 0x00, 0x10, 0x09, 0x0d, 0xf9, + 0x4b, 0xf7, 0xab, 0x54, 0x75, 0x8b, 0x4b, + 0x91, 0x94, 0x5a, 0x3c, 0xb0, 0xda, 0x10, + 0x21, 0x00, 0x09, 0x48, 0x65, 0x6c, 0x6c, + 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x10, 0x23, + 0x00, 0x06, 0x57, 0x50, 0x53, 0x32, 0x2e, + 0x30, 0x10, 0x24, 0x00, 0x04, 0x30, 0x2e, + 0x38, 0x78, 0x10, 0x42, 0x00, 0x04, 0x30, + 0x30, 0x31, 0x34, 0x10, 0x54, 0x00, 0x08, + 0x00, 0x01, 0x00, 0x50, 0xf2, 0x04, 0x00, + 0x01, 0x10, 0x11, 0x00, 0x02, 0x4d, 0x4d, + 0x10, 0x08, 0x00, 0x02, 0x23, 0x88, 0x10, + 0x49, 0x00, 0x06, 0x00, 0x37, 0x2a, 0x00, + 0x01, 0x20, 0xdd, 0x23, 0x50, 0x6f, 0x9a, + 0x09, 0x02, 0x02, 0x00, 0x23, 0x00, 0x0d, + 0x17, 0x00, mac_addr[0], mac_addr[1], + mac_addr[2], mac_addr[3], mac_addr[4], + mac_addr[5], 0x01, 0x88, 0x00, 0x01, 0x00, + 0x50, 0xf2, 0x04, 0x00, 0x01, 0x00, 0x10, + 0x11, 0x00, 0x02, 0x4d, 0x4d}; + + memset(&nw_select, 0, sizeof(struct cmd_nw_selection)); + nw_select.p2p_selection = nw_select_enabled; + memcpy(nw_select.ssid.ssid, wildcard_ssid, 7); + nw_select.ssid.len = 7; + nw_select.scan_req_ie_len = sizeof(req_ie); + nw_select.scan_resp_ie_len = sizeof(resp_ie); + + pr_err("req_len = %d, resp_len = %d\n", + nw_select.scan_req_ie_len, nw_select.scan_resp_ie_len); + + memcpy(nw_select.scan_req_ie, req_ie, nw_select.scan_req_ie_len); + memcpy(nw_select.scan_resp_ie, resp_ie, nw_select.scan_resp_ie_len); + + return uccp420wlan_send_cmd((unsigned char *) &nw_select, + sizeof(struct cmd_nw_selection), + UMAC_CMD_NW_SELECTION); + +} + + +int uccp420wlan_prog_peer_key(int vif_index, + unsigned char *vif_addr, + unsigned int op, + unsigned int key_id, + unsigned int key_type, + unsigned int cipher_type, + struct umac_key *key) +{ + struct cmd_setkey peer_key; + + memset(&peer_key, 0, sizeof(struct cmd_setkey)); + + peer_key.if_index = vif_index; + /* memcpy(peer_key.vif_addr, vif_addr, ETH_ALEN); */ + peer_key.ctrl = op; + peer_key.key_id = key_id; + ether_addr_copy(peer_key.mac_addr, key->peer_mac); + + peer_key.key_type = key_type; + peer_key.cipher_type = cipher_type; + memcpy(peer_key.key, key->key, MAX_KEY_LEN); + peer_key.key_len = MAX_KEY_LEN; + + if (key->tx_mic) { + memcpy(peer_key.key + MAX_KEY_LEN, key->tx_mic, TKIP_MIC_LEN); + peer_key.key_len += TKIP_MIC_LEN; + } + if (key->rx_mic) { + memcpy(peer_key.key + MAX_KEY_LEN + TKIP_MIC_LEN, key->rx_mic, + TKIP_MIC_LEN); + peer_key.key_len += TKIP_MIC_LEN; + } + peer_key.rsc_len = 6; + memset(peer_key.rsc, 0, 6); + + return uccp420wlan_send_cmd((unsigned char *) &peer_key, + sizeof(struct cmd_setkey), UMAC_CMD_SETKEY); +} + + +int uccp420wlan_prog_if_key(int vif_index, + unsigned char *vif_addr, + unsigned int op, + unsigned int key_id, + unsigned int cipher_type, + struct umac_key *key) + { + struct cmd_setkey if_key; + + memset(&if_key, 0, sizeof(struct cmd_setkey)); + + if_key.if_index = vif_index; + /* memcpy(if_key.vif_addr, vif_addr, 6); */ + if_key.key_id = key_id; + if_key.ctrl = op; + + if (op == KEY_CTRL_ADD) { + if_key.cipher_type = cipher_type; + + if (cipher_type == CIPHER_TYPE_TKIP || cipher_type == + CIPHER_TYPE_CCMP) { + memcpy(if_key.key, key->key, MAX_KEY_LEN); + if_key.key_len = MAX_KEY_LEN; + + if (key->tx_mic) { + memcpy(if_key.key + MAX_KEY_LEN, key->tx_mic, + TKIP_MIC_LEN); + if_key.key_len += TKIP_MIC_LEN; + } + } else { + if_key.key_len = + (cipher_type == CIPHER_TYPE_WEP40) ? 5 : 13; + memcpy(if_key.key, key->key, if_key.key_len); + } + } + + if_key.rsc_len = 6; + if_key.key_type = KEY_TYPE_BCAST; + memset(if_key.rsc, 0, 6); + memset(if_key.mac_addr, 0xff, 6); + + return uccp420wlan_send_cmd((unsigned char *) &if_key, + sizeof(struct cmd_setkey), UMAC_CMD_SETKEY); +} + +int uccp420wlan_prog_ba_session_data(unsigned int op, + unsigned short tid, + unsigned short *ssn, + unsigned short ba_policy, + unsigned char *vif_addr, + unsigned char *peer_addr) +{ + struct cmd_ht_ba ba_cmd; + int index; + struct mac80211_dev *dev; + struct lmac_if_data *p; + struct ieee80211_vif *vif = NULL; + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + + dev = p->context; + + memset(&ba_cmd, 0, sizeof(struct cmd_ht_ba)); + + for (index = 0; index < dev->params->num_vifs; index++) { + if (!(dev->active_vifs & (1 << index))) + continue; + + vif = rcu_dereference(dev->vifs[index]); + + if (ether_addr_equal(vif->addr, vif_addr)) + break; + } + + if (index == dev->params->num_vifs) { + UCCP_DEBUG_IF("no VIF found\n"); + return -1; + } + + ba_cmd.if_index = index; + ba_cmd.op = op; + ba_cmd.policy = ba_policy; + ba_cmd.tid = tid; + ba_cmd.ssn = *ssn; + ether_addr_copy(ba_cmd.vif_addr, vif_addr); + ether_addr_copy(ba_cmd.peer_addr, peer_addr); + + rcu_read_unlock(); + + return uccp420wlan_send_cmd((unsigned char *) &ba_cmd, + sizeof(struct cmd_ht_ba), + UMAC_CMD_BA_SESSION_INFO); +} + + +int uccp420wlan_scan(int index, + struct scan_req *req) +{ + struct cmd_scan *scan; + unsigned char i; + struct mac80211_dev *dev; + struct lmac_if_data *p; + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + + rcu_read_unlock(); + dev = p->context; + + scan = kmalloc(sizeof(struct cmd_scan) + + req->ie_len, GFP_KERNEL); + + if (scan == NULL) { + UCCP_DEBUG_IF("%s: Failed to allocate memory\n", __func__); + return -ENOMEM; + } + + memset(scan, 0, sizeof(struct cmd_scan)); + + scan->if_index = index; + + /* We support 4 SSIDs */ + scan->n_ssids = req->n_ssids; + scan->n_channel = req->n_channels; + scan->type = dev->params->scan_type; + + for (i = 0; i < scan->n_channel; i++) { + scan->channel_list[i] = + (ieee80211_frequency_to_channel(req->center_freq[i])); + scan->chan_max_power[i] = req->freq_max_power[i]; + + /* scan->chan_max_antenna_gain[i] = + * req->freq_max_antenna_gain[i]; + */ + + /* In mac80211 the flags are u32 but for scanning we need + * only first PASSIVE_SCAN flag, remaining flags may be used + * in future. + */ + if ((req->chan_flags[i] & IEEE80211_CHAN_NO_IR) || + (req->chan_flags[i] & IEEE80211_CHAN_RADAR)) { + scan->chan_flags[i] = PASSIVE; + } else { + scan->chan_flags[i] = ACTIVE; + } + } + + scan->p2p_probe = req->p2p_probe; + + scan->extra_ies_len = req->ie_len; + + if (req->ie_len) + memcpy(scan->extra_ies, req->ie, req->ie_len); + + if (req->n_ssids > 0) { + for (i = 0; i < scan->n_ssids; i++) { + scan->ssids[i].len = req->ssids[i].ssid_len; + if (scan->ssids[i].len > 0) + memcpy(scan->ssids[i].ssid, req->ssids[i].ssid, + req->ssids[i].ssid_len); + } + } + UCCP_DEBUG_SCAN("Scan request ie len = %d n_channel = %d,", + req->ie_len, + scan->n_channel); + UCCP_DEBUG_SCAN(" n_ssids = %d, if_index = %d type = %d p2p = %d\n", + scan->n_ssids, + scan->if_index, + scan->type, + scan->p2p_probe); + + for (i = 0; i < scan->n_ssids; i++) { + if (scan->ssids[i].len != 0) + UCCP_DEBUG_SCAN("SSID: %s\n", scan->ssids[i].ssid); + else + UCCP_DEBUG_SCAN("SSID: EMPTY\n"); + } + + UCCP_DEBUG_SCAN("CHANNEL_LIST: Channel ==> Channel Flags\n"); + + for (i = 0; i < scan->n_channel; i++) + UCCP_DEBUG_SCAN("Index %d: %d ==> %d\n", i, + scan->channel_list[i], scan->chan_flags[i]); + + dev->stats->umac_scan_req++; + + uccp420wlan_send_cmd((unsigned char *)scan, sizeof(struct cmd_scan) + + req->ie_len, UMAC_CMD_SCAN); + kfree(scan); + + return 0; +} + + +int uccp420wlan_scan_abort(int index) +{ + struct cmd_scan_abort *scan_abort = NULL; + + scan_abort = (struct cmd_scan_abort *) + kmalloc(sizeof(struct cmd_scan_abort), GFP_KERNEL); + + if (scan_abort == NULL) { + UCCP_DEBUG_IF("%s: Failed to allocate memory\n", __func__); + return -ENOMEM; + } + + memset(scan_abort, 0, sizeof(struct cmd_scan_abort)); + + scan_abort->if_index = index; + + uccp420wlan_send_cmd((unsigned char *)scan_abort, + sizeof(struct cmd_scan_abort), + UMAC_CMD_SCAN_ABORT); + + kfree(scan_abort); + scan_abort = NULL; + + return 0; +} + + +int uccp420wlan_prog_channel(unsigned int prim_ch, + unsigned int center_freq1, + unsigned int center_freq2, + unsigned int ch_width, +#ifdef MULTI_CHAN_SUPPORT + unsigned int vif_index, +#endif + unsigned int freq_band) +{ + struct cmd_channel channel; + struct lmac_if_data *p; + struct mac80211_dev *dev; + int is_vht_bw80_sec_40minus; + int is_vht_bw80_sec_40plus; + int is_vht_bw80; + int ch_no1, ch_no2; + int err = 0; + unsigned int cf_offset = center_freq1; + + memset(&channel, 0, sizeof(struct cmd_channel)); + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + dev = p->context; + if (dev->params->production_test == 1) { + if ((dev->params->prod_mode_chnl_bw_40_mhz == 1) && + (dev->params->sec_ch_offset_40_minus == 1)) { + /* NL80211_CHAN_HT40MINUS */ + ch_width = 2; + cf_offset -= 10; + } else if (dev->params->prod_mode_chnl_bw_40_mhz == 1) { + /* NL80211_CHAN_HT40PLUS */ + ch_width = 2; + cf_offset += 10; + } + + is_vht_bw80 = vht_support && + (dev->params->prod_mode_chnl_bw_80_mhz == 1); + + is_vht_bw80_sec_40minus = is_vht_bw80 && + (dev->params->sec_ch_offset_40_minus == 1); + + is_vht_bw80_sec_40plus = is_vht_bw80 && + (dev->params->sec_ch_offset_40_plus == 1); + + if (is_vht_bw80) + ch_width = 3; + + if (is_vht_bw80_sec_40minus && + (dev->params->sec_40_ch_offset_80_minus == 1)) + cf_offset -= 30; + else if (is_vht_bw80_sec_40minus && + (dev->params->sec_40_ch_offset_80_plus == 1)) + cf_offset += 10; + else if (is_vht_bw80_sec_40minus)/* default */ + cf_offset -= 30; + + if (is_vht_bw80_sec_40plus && + (dev->params->sec_40_ch_offset_80_minus == 1)) + cf_offset -= 10; + else if (is_vht_bw80_sec_40plus && + (dev->params->sec_40_ch_offset_80_plus == 1)) + cf_offset += 30; + else if (is_vht_bw80_sec_40plus)/* default */ + cf_offset -= 10; + + + } + ch_no1 = ieee80211_frequency_to_channel(cf_offset); + ch_no2 = 0; + + channel.primary_ch_number = prim_ch; + channel.channel_number1 = ch_no1; + channel.channel_number2 = ch_no2; + + switch (ch_width) { + case 0: + case 1: + channel.channel_bw = 0; + break; + case 2: + channel.channel_bw = 1; + break; + case 3: + channel.channel_bw = 2; + break; + case 4: + case 5: + channel.channel_bw = 3; + break; + default: + break; + } + + channel.freq_band = freq_band; +#ifdef MULTI_CHAN_SUPPORT + channel.vif_index = vif_index; +#endif + dev->cur_chan.center_freq1 = cf_offset; + dev->cur_chan.center_freq2 = ch_no2; + dev->cur_chan.pri_chnl_num = prim_ch; + dev->cur_chan.ch_width = ch_width; + dev->cur_chan.freq_band = freq_band; + dev->chan_prog_done = 0; + + rcu_read_unlock(); + + dev->chan_prog_done = 0; + + err = uccp420wlan_send_cmd((unsigned char *) &channel, + sizeof(struct cmd_channel), + UMAC_CMD_CHANNEL); + + + if (err) + return err; + + if (wait_for_channel_prog_complete(dev)) + return -1; + + return 0; +} + + +#ifdef MULTI_CHAN_SUPPORT +int uccp420wlan_prog_chanctx_time_info(void) +{ + struct cmd_chanctx_time_config time_cfg; + int i = 0; + int j = 0; + struct mac80211_dev *dev = NULL; + struct lmac_if_data *p = NULL; + struct ieee80211_chanctx_conf *curr_conf = NULL; + struct umac_chanctx *curr_ctx = NULL; + int freq = 0; + + rcu_read_lock(); + + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + + rcu_read_unlock(); + + dev = p->context; + + memset(&time_cfg, 0, sizeof(struct cmd_chanctx_time_config)); + + rcu_read_lock(); + + for (i = 0; i < MAX_CHANCTX; i++) { + curr_conf = rcu_dereference(dev->chanctx[i]); + + if (curr_conf) { + curr_ctx = (struct umac_chanctx *)curr_conf->drv_priv; + + if (curr_ctx->nvifs) { + freq = curr_conf->def.chan->center_freq; + + time_cfg.info[j].chan = + ieee80211_frequency_to_channel(freq); + time_cfg.info[j].percentage = + (100 / dev->num_active_chanctx); + j++; + } + } + } + + rcu_read_unlock(); + + return uccp420wlan_send_cmd((unsigned char *)&time_cfg, + sizeof(struct cmd_chanctx_time_config), + UMAC_CMD_CHANCTX_TIME_INFO); +} +#endif + + +int uccp420wlan_prog_ps_state(int index, + unsigned char *vif_addr, + unsigned int powersave_state) +{ + struct cmd_ps ps_cfg; + + memset(&ps_cfg, 0, sizeof(struct cmd_ps)); + ps_cfg.mode = powersave_state; + ps_cfg.if_index = index; + + return uccp420wlan_send_cmd((unsigned char *)&ps_cfg, + sizeof(struct cmd_ps), UMAC_CMD_PS); +} + + +int uccp420wlan_prog_tx(unsigned int queue, + unsigned int more_frms, +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx, +#endif + unsigned int descriptor_id, + bool retry) +{ + struct cmd_tx_ctrl tx_cmd; + struct sk_buff *nbuf, *nbuf_start; + unsigned char *data; + struct lmac_if_data *p; + struct mac80211_dev *dev; + struct umac_vif *uvif; + struct sk_buff *skb, *skb_first, *tmp; + struct sk_buff_head *txq = NULL; + struct ieee80211_hdr *mac_hdr; + struct ieee80211_tx_info *tx_info_first; + unsigned int hdrlen, pkt = 0; + int vif_index; + __u16 fc; +#ifdef MULTI_CHAN_SUPPORT + struct tx_config *tx; +#endif + struct tx_pkt_info *pkt_info = NULL; + + memset(&tx_cmd, 0, sizeof(struct cmd_tx_ctrl)); + + rcu_read_lock(); + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + rcu_read_unlock(); + return -1; + } + + dev = p->context; + spin_lock_bh(&dev->tx.lock); +#ifdef MULTI_CHAN_SUPPORT + tx = &dev->tx; + txq = &dev->tx.pkt_info[curr_chanctx_idx][descriptor_id].pkt; + pkt_info = &dev->tx.pkt_info[curr_chanctx_idx][descriptor_id]; +#else + txq = &dev->tx.pkt_info[descriptor_id].pkt; + pkt_info = &dev->tx.pkt_info[descriptor_id]; +#endif + skb_first = skb_peek(txq); + + if (!skb_first) { + spin_unlock_bh(&dev->tx.lock); + rcu_read_unlock(); + return -10; + } + + tx_info_first = IEEE80211_SKB_CB(skb_first); + + mac_hdr = (struct ieee80211_hdr *)skb_first->data; + fc = mac_hdr->frame_control; + hdrlen = ieee80211_hdrlen(fc); + vif_index = vif_addr_to_index(mac_hdr->addr2, dev); + + /* GET The security Header Length only for data/qos-data/unicast PMF + * for 11W case. + */ + if ((ieee80211_is_data(fc) || + ieee80211_is_data_qos(fc)) + && ieee80211_has_protected(fc)) { + /* hw_key == NULL: Encrypted in SW (injected frames) + * iv_len = 0: treat as SW encryption. + */ + if (tx_info_first->control.hw_key == NULL || + !tx_info_first->control.hw_key->iv_len) { + UCCP_DEBUG_IF("%s: hw_key is %s and iv_len: 0\n", + __func__, + tx_info_first->control.hw_key?"valid":"NULL"); + tx_cmd.encrypt = ENCRYPT_DISABLE; + } else { + UCCP_DEBUG_IF("%s: cipher: %d, icv: %d", + __func__, + tx_info_first->control.hw_key->cipher, + tx_info_first->control.hw_key->icv_len); + UCCP_DEBUG_IF("iv: %d, key: %d\n", + tx_info_first->control.hw_key->iv_len, + tx_info_first->control.hw_key->keylen); + /* iv_len is always the header and icv_len is always + * the trailer include only iv_len + */ + hdrlen += tx_info_first->control.hw_key->iv_len; + tx_cmd.encrypt = ENCRYPT_ENABLE; + } + } + +#ifdef MULTI_CHAN_SUPPORT + if (tx_info_first->flags & IEEE80211_TX_CTL_TX_OFFCHAN) + tx_cmd.tx_flags |= (1 << UMAC_TX_FLAG_OFFCHAN_FRM); +#endif + + /* For injected frames (wlantest) hw_key is not set,as PMF uses + * CCMP always so hardcode this to CCMP IV LEN 8. + * For Auth3: It is completely handled in SW (mac80211). + */ + if (ieee80211_is_unicast_robust_mgmt_frame(skb_first) && + ieee80211_has_protected(fc)) { + hdrlen += 8; + tx_cmd.encrypt = ENCRYPT_ENABLE; + } + + /* separate in to up to TSF and From TSF*/ + if (ieee80211_is_beacon(fc) || ieee80211_is_probe_resp(fc)) + hdrlen += 8; /* Timestamp*/ + + /* HAL UMAC-LMAC HDR*/ + tx_cmd.hdr.id = UMAC_CMD_TX; + /* Keep the queue num and pool id in descriptor id */ + tx_cmd.hdr.descriptor_id = 0; + tx_cmd.hdr.descriptor_id |= ((queue & 0x0000FFFF) << 16); + tx_cmd.hdr.descriptor_id |= (descriptor_id & 0x0000FFFF); + /* Not used anywhere currently */ + tx_cmd.hdr.length = sizeof(struct cmd_tx_ctrl); + + /* UMAC_CMD_TX*/ + tx_cmd.if_index = vif_index; + tx_cmd.queue_num = queue; + tx_cmd.more_frms = more_frms; + tx_cmd.descriptor_id = descriptor_id; + tx_cmd.num_frames_per_desc = skb_queue_len(txq); + tx_cmd.pkt_gram_payload_len = hdrlen; + tx_cmd.aggregate_mpdu = AMPDU_AGGR_DISABLED; + +#ifdef MULTI_CHAN_SUPPORT + dev->tx.pkt_info[curr_chanctx_idx][descriptor_id].vif_index = vif_index; + dev->tx.pkt_info[curr_chanctx_idx][descriptor_id].queue = queue; +#else + dev->tx.pkt_info[descriptor_id].vif_index = vif_index; + dev->tx.pkt_info[descriptor_id].queue = queue; +#endif + + uvif = (struct umac_vif *) (tx_info_first->control.vif->drv_priv); + + nbuf = alloc_skb(sizeof(struct cmd_tx_ctrl) + + tx_cmd.num_frames_per_desc * + MAX_GRAM_PAYLOAD_LEN, GFP_ATOMIC); + + if (!nbuf) { + spin_unlock_bh(&dev->tx.lock); + rcu_read_unlock(); + return -20; + } + + /* Get the rate for first packet as all packets have same rate */ + get_rate(skb_first, + &tx_cmd, + pkt_info, + retry, + dev); + + data = skb_put(nbuf, sizeof(struct cmd_tx_ctrl)); + memset(data, 0, sizeof(struct cmd_tx_ctrl)); + /*store the start for later use*/ + nbuf_start = (struct sk_buff *)data; + memcpy(data, &tx_cmd, sizeof(struct cmd_tx_ctrl)); + + UCCP_DEBUG_TX("%s-UMACTX: TX Frame, Queue = %d, descriptord_id = %d\n", + dev->name, + tx_cmd.queue_num, tx_cmd.descriptor_id); + UCCP_DEBUG_TX(" num_frames= %d qlen: %d len = %d\n", + tx_cmd.num_frames_per_desc, skb_queue_len(txq), + nbuf->len); + + UCCP_DEBUG_TX("%s-UMACTX: Num rates = %d, %x, %x, %x, %x\n", + dev->name, + tx_cmd.num_rates, + tx_cmd.rate[0], + tx_cmd.rate[1], + tx_cmd.rate[2], + tx_cmd.rate[3]); + + UCCP_DEBUG_TX("%s-UMACTX: Retries = %d, %d, %d, %d, %d\n", + dev->name, + pkt_info->max_retries, + tx_cmd.rate_retries[0], + tx_cmd.rate_retries[1], + tx_cmd.rate_retries[2], + tx_cmd.rate_retries[3]); + +#ifdef MULTI_CHAN_SUPPORT + tx->desc_chan_map[descriptor_id] = curr_chanctx_idx; +#endif + + skb_queue_walk_safe(txq, skb, tmp) { + if (!skb || (pkt > tx_cmd.num_frames_per_desc)) + break; + + mac_hdr = (struct ieee80211_hdr *)skb->data; + + /* Only for Non-Qos and MGMT frames, for Qos-Data + * mac80211 handles the sequence no generation + */ + if (!retry && + tx_info_first->flags & + IEEE80211_TX_CTL_ASSIGN_SEQ) { + if (tx_info_first->flags & + IEEE80211_TX_CTL_FIRST_FRAGMENT) { + uvif->seq_no += 0x10; + } + + mac_hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + mac_hdr->seq_ctrl |= cpu_to_le16(uvif->seq_no); + } + + /* Need it for tx_status later */ +#ifdef MULTI_CHAN_SUPPORT + dev->tx.pkt_info[curr_chanctx_idx][descriptor_id].hdr_len = + hdrlen; +#else + dev->tx.pkt_info[descriptor_id].hdr_len = hdrlen; +#endif + + /* Complete packet length */ + ((struct cmd_tx_ctrl *)nbuf_start)->pkt_length[pkt] = skb->len; + + /* We move the 11hdr from skb to UMAC_CMD_TX, this is part of + * online DMA changes, HW expects only data portion + * While DMA. Not requried for loopback + */ + skb_put(nbuf, MAX_GRAM_PAYLOAD_LEN); + + memcpy((unsigned char *)nbuf_start + + sizeof(struct cmd_tx_ctrl)+ + (pkt * MAX_GRAM_PAYLOAD_LEN), + mac_hdr, hdrlen); + + skb_pull(skb, hdrlen); + if (hal_ops.map_tx_buf(descriptor_id, pkt, + skb->data, skb->len)) { + spin_unlock_bh(&dev->tx.lock); + rcu_read_unlock(); + dev_kfree_skb_any(nbuf); + return -30; + } + + pkt++; + } + +#ifdef PERF_PROFILING + if (dev->params->driver_tput == 0) { +#endif + + /* SDK: Check if we can use the same txq initialized before in + * the function here + */ +#ifdef MULTI_CHAN_SUPPORT + txq = &dev->tx.pkt_info[curr_chanctx_idx][descriptor_id].pkt; +#else + txq = &dev->tx.pkt_info[descriptor_id].pkt; +#endif + + spin_lock_bh(&cmd_info.control_path_lock); + + hal_ops.send((void *)nbuf, + HOST_MOD_ID, + UMAC_MOD_ID, + (void *)txq); + + spin_unlock_bh(&cmd_info.control_path_lock); + + /* increment tx_cmd_send_count to keep track of number of + * tx_cmd send + */ + if (queue != WLAN_AC_BCN) { + if (skb_queue_len(txq) == 1) + dev->stats->tx_cmd_send_count_single++; + else if (skb_queue_len(txq) > 1) + dev->stats->tx_cmd_send_count_multi++; + } else { + dev->stats->tx_cmd_send_count_beaconq++; + } +#ifdef PERF_PROFILING + } +#endif + + spin_unlock_bh(&dev->tx.lock); + rcu_read_unlock(); + + return 0; +} + + +int uccp420wlan_prog_vif_short_slot(int index, + unsigned char *vif_addr, + unsigned int use_short_slot) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = SHORTSLOT_CHANGED; + vif_cfg.use_short_slot = use_short_slot; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_atim_window(int index, + unsigned char *vif_addr, + unsigned int atim_window) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = ATIMWINDOW_CHANGED; + vif_cfg.atim_window = atim_window; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_long_retry(int index, + unsigned char *vif_addr, + unsigned int long_retry) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = LONGRETRY_CHANGED; + vif_cfg.long_retry = long_retry; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); + +} + + +int uccp420wlan_prog_short_retry(int index, + unsigned char *vif_addr, + unsigned int short_retry) +{ + + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = SHORTRETRY_CHANGED; + vif_cfg.short_retry = short_retry; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); + + +} + + +int uccp420wlan_prog_vif_basic_rates(int index, + unsigned char *vif_addr, + unsigned int basic_rate_set) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = BASICRATES_CHANGED; + vif_cfg.basic_rate_set = basic_rate_set; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); + + +} + + +int uccp420wlan_prog_vif_aid(int index, + unsigned char *vif_addr, + unsigned int aid) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = AID_CHANGED; + vif_cfg.aid = aid; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_op_channel(int index, + unsigned char *vif_addr, + unsigned char op_channel) +{ + + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = OP_CHAN_CHANGED; + vif_cfg.op_channel = op_channel; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_conn_state(int index, + unsigned char *vif_addr, + unsigned int connect_state) +{ + + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = CONNECT_STATE_CHANGED; + vif_cfg.connect_state = connect_state; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_assoc_cap(int index, + unsigned char *vif_addr, + unsigned int caps) +{ + struct cmd_vif_cfg vif_cfg; + + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = CAPABILITY_CHANGED; + vif_cfg.capability = caps; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); + +} + + +int uccp420wlan_prog_vif_beacon_int(int index, + unsigned char *vif_addr, + unsigned int bcn_int) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + + vif_cfg.changed_bitmap = BCN_INT_CHANGED; + vif_cfg.beacon_interval = bcn_int; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_dtim_period(int index, + unsigned char *vif_addr, + unsigned int dtim_period) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + + vif_cfg.changed_bitmap = DTIM_PERIOD_CHANGED; + vif_cfg.beacon_interval = dtim_period; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_bssid(int index, + unsigned char *vif_addr, + unsigned char *bssid) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = BSSID_CHANGED; + ether_addr_copy(vif_cfg.bssid, bssid); + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + vif_cfg.if_index = index; + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_prog_vif_smps(int index, + unsigned char *vif_addr, + unsigned char smps_mode) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = SMPS_CHANGED; + vif_cfg.if_index = index; + ether_addr_copy(vif_cfg.vif_addr, vif_addr); + + switch (smps_mode) { + case IEEE80211_SMPS_STATIC: + vif_cfg.smps_info |= SMPS_ENABLED; + break; + case IEEE80211_SMPS_DYNAMIC: + vif_cfg.smps_info |= SMPS_ENABLED; + vif_cfg.smps_info |= SMPS_MODE; + break; + case IEEE80211_SMPS_AUTOMATIC:/* will be one of the above*/ + case IEEE80211_SMPS_OFF: + break; + default: + WARN(1, "Invalid SMPS Mode: %d\n", smps_mode); + } + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); +} + + +int uccp420wlan_sta_add(int index, struct peer_sta_info *st) +{ + struct cmd_sta sta; + int i; + + memset(&sta, 0, (sizeof(struct cmd_sta))); + sta.op = ADD; + + for (i = 0; i < STA_NUM_BANDS; i++) + sta.supp_rates[i] = st->supp_rates[i]; + + /* HT info */ + sta.if_index = index; + sta.ht_cap = st->ht_cap; + sta.ht_supported = st->ht_supported; + sta.vht_supported = st->vht_supported; + sta.vht_cap = st->vht_cap; + sta.ampdu_factor = st->ampdu_factor; + sta.ampdu_density = st->ampdu_density; + sta.rx_highest = st->rx_highest; + sta.tx_params = st->tx_params; + + /* Enable it when FW supports it */ + /* sta.uapsd_queues = st->uapsd_queues; */ + for (i = 0; i < HT_MCS_MASK_LEN; i++) + sta.rx_mask[i] = st->rx_mask[i]; + + for (i = 0; i < ETH_ALEN; i++) + sta.addr[i] = st->addr[i]; + + return uccp420wlan_send_cmd((unsigned char *)&sta, + sizeof(struct cmd_sta), UMAC_CMD_STA); +} + + +int uccp420wlan_sta_remove(int index, struct peer_sta_info *st) +{ + struct cmd_sta sta; + int i; + + memset(&sta, 0, (sizeof(struct cmd_sta))); + sta.op = REM; + + for (i = 0; i < ETH_ALEN; i++) + sta.addr[i] = st->addr[i]; + + return uccp420wlan_send_cmd((unsigned char *)&sta, + sizeof(struct cmd_sta), UMAC_CMD_STA); + +} + + +int uccp420wlan_prog_txq_params(int index, + unsigned char *addr, + unsigned int queue, + unsigned int aifs, + unsigned int txop, + unsigned int cwmin, + unsigned int cwmax, + unsigned int uapsd) +{ + struct cmd_txq_params params; + + memset(¶ms, 0, (sizeof(struct cmd_txq_params))); + + params.if_index = index; + ether_addr_copy(params.vif_addr, addr); + params.queue_num = queue; + params.aifsn = aifs; + params.txop = txop; + params.cwmin = cwmin; + params.cwmax = cwmax; + params.uapsd = uapsd; + + return uccp420wlan_send_cmd((unsigned char *) ¶ms, + sizeof(struct cmd_txq_params), + UMAC_CMD_TXQ_PARAMS); +} + + +int uccp420wlan_set_rate(int rate, int mcs) +{ + struct cmd_rate cmd_rate; + + memset(&cmd_rate, 0, (sizeof(struct cmd_rate))); + UCCP_DEBUG_IF("mcs = %d rate = %d\n", mcs, rate); + cmd_rate.is_mcs = mcs; + cmd_rate.rate = rate; + return uccp420wlan_send_cmd((unsigned char *) &cmd_rate, + sizeof(struct cmd_rate), + UMAC_CMD_RATE); +} + + +int uccp420wlan_prog_rcv_bcn_mode(unsigned int bcn_rcv_mode) +{ + struct cmd_vif_cfg vif_cfg; + + memset(&vif_cfg, 0, sizeof(struct cmd_vif_cfg)); + vif_cfg.changed_bitmap = RCV_BCN_MODE_CHANGED; + vif_cfg.bcn_mode = bcn_rcv_mode; + + return uccp420wlan_send_cmd((unsigned char *)&vif_cfg, + sizeof(struct cmd_vif_cfg), + UMAC_CMD_VIF_CFG); + +} + +int uccp420wlan_prog_aux_adc_chain(unsigned int chain_id) +{ + struct cmd_aux_adc_chain_sel aadc_chain_sel; + + memset(&aadc_chain_sel, 0, sizeof(struct cmd_aux_adc_chain_sel)); + aadc_chain_sel.chain_id = chain_id; + + return uccp420wlan_send_cmd((unsigned char *)&aadc_chain_sel, + sizeof(struct cmd_aux_adc_chain_sel), + UMAC_CMD_AUX_ADC_CHAIN_SEL); +} + +int uccp420wlan_prog_cont_tx(int val) +{ + struct cmd_cont_tx status; + + memset(&status, 0, sizeof(struct cmd_cont_tx)); + status.op = val; + + return uccp420wlan_send_cmd((unsigned char *)&status, + sizeof(struct cmd_cont_tx), + UMAC_CMD_CONT_TX); +} + + + +int uccp420wlan_prog_mib_stats(void) +{ + struct host_mac_msg_hdr mib_stats_cmd; + + UCCP_DEBUG_IF("cmd mib stats\n"); + memset(&mib_stats_cmd, 0, sizeof(struct host_mac_msg_hdr)); + + return uccp420wlan_send_cmd((unsigned char *)&mib_stats_cmd, + sizeof(struct host_mac_msg_hdr), + UMAC_CMD_MIB_STATS); +} + + +int uccp420wlan_prog_clear_stats(void) +{ + struct host_mac_msg_hdr clear_stats_cmd; + + UCCP_DEBUG_IF("cmd clear stats\n"); + memset(&clear_stats_cmd, 0, sizeof(struct host_mac_msg_hdr)); + + return uccp420wlan_send_cmd((unsigned char *)&clear_stats_cmd, + sizeof(struct host_mac_msg_hdr), + UMAC_CMD_CLEAR_STATS); +} + + +int uccp420wlan_prog_phy_stats(void) +{ + struct host_mac_msg_hdr phy_stats_cmd; + + UCCP_DEBUG_IF("cmd phy stats\n"); + memset(&phy_stats_cmd, 0, sizeof(struct host_mac_msg_hdr)); + + return uccp420wlan_send_cmd((unsigned char *)&phy_stats_cmd, + sizeof(struct host_mac_msg_hdr), + UMAC_CMD_PHY_STATS); +} + + +int uccp420wlan_prog_radar_detect(unsigned int op_code) +{ + struct cmd_detect_radar dfs_op; + + UCCP_DEBUG_IF("cmd radar detect\n"); + dfs_op.radar_detect_op = op_code; + + return uccp420wlan_send_cmd((unsigned char *) &dfs_op, + sizeof(struct cmd_detect_radar), + UMAC_CMD_DETECT_RADAR); +} + + +int uccp420wlan_prog_global_cfg(unsigned int rx_msdu_lifetime, + unsigned int tx_msdu_lifetime, + unsigned int sensitivity, + unsigned int dyn_ed_enable, + unsigned char *rf_params) +{ + /*DUMMY*/ + return 0; +} + + +#ifdef CONFIG_PM +int uccp420wlan_prog_econ_ps_state(int if_index, + unsigned int ps_state) +{ + struct cmd_ps ps_cfg; + + memset(&ps_cfg, 0, sizeof(struct cmd_ps)); + ps_cfg.mode = ps_state; + ps_cfg.if_index = if_index; + + return uccp420wlan_send_cmd((unsigned char *)&ps_cfg, + sizeof(struct cmd_ps), + UMAC_CMD_PS_ECON_CFG); +} +#endif + + +int uccp420wlan_msg_handler(void *nbuff, + unsigned char sender_id) +{ + unsigned int event; + unsigned char *buff; + struct host_mac_msg_hdr *hdr; + struct lmac_if_data *p; + struct sk_buff *skb = (struct sk_buff *)nbuff; + struct sk_buff *pending_cmd; + struct mac80211_dev *dev; +#ifdef MULTI_CHAN_SUPPORT + int curr_chanctx_idx = -1; +#endif + + rcu_read_lock(); + + p = (struct lmac_if_data *)(rcu_dereference(lmac_if)); + + if (!p) { + WARN_ON(1); + dev_kfree_skb_any(skb); + rcu_read_unlock(); + return 0; + } + + buff = skb->data; + hdr = (struct host_mac_msg_hdr *)buff; + + event = hdr->id & 0xffff; + + dev = (struct mac80211_dev *)p->context; + + /* UCCP_DEBUG_IF("%s-UMACIF: event %d received\n", p->name, event); */ + if (event == UMAC_EVENT_RESET_COMPLETE) { + struct host_event_reset_complete *r = + (struct host_event_reset_complete *)buff; + + uccp420wlan_reset_complete(r->version, p->context); + spin_lock_bh(&cmd_info.control_path_lock); + + if (cmd_info.outstanding_ctrl_req == 0) { + pr_err("%s-UMACIF: Unexpected: Spurious proc_done received. Ignoring and continuing.\n", + p->name); + } else { + cmd_info.outstanding_ctrl_req--; + + UCCP_DEBUG_IF("After DEC: outstanding cmd: %d\n", + cmd_info.outstanding_ctrl_req); + pending_cmd = skb_dequeue(&cmd_info.outstanding_cmd); + + if (unlikely(pending_cmd != NULL)) { + UCCP_DEBUG_IF("Send 1 outstanding cmd\n"); + hal_ops.send((void *)pending_cmd, HOST_MOD_ID, + UMAC_MOD_ID, 0); + dev->stats->gen_cmd_send_count++; + } + } + + spin_unlock_bh(&cmd_info.control_path_lock); + } else if (event == UMAC_EVENT_SCAN_ABORT_COMPLETE) { + dev->scan_abort_done = 1; +#ifdef CONFIG_PM + } else if (event == UMAC_EVENT_PS_ECON_CFG_DONE) { + struct umac_event_ps_econ_cfg_complete *econ_cfg_complete_data = + (struct umac_event_ps_econ_cfg_complete *)buff; + dev->econ_ps_cfg_stats.completed = 1; + dev->econ_ps_cfg_stats.result = econ_cfg_complete_data->status; + rx_interrupt_status = 0; + } else if (event == UMAC_EVENT_PS_ECON_WAKE) { + struct umac_event_ps_econ_wake *econ_wake_data = + (struct umac_event_ps_econ_wake *)buff; + dev->econ_ps_cfg_stats.wake_trig = econ_wake_data->trigger; +#endif + } else if (event == UMAC_EVENT_SCAN_COMPLETE) { + uccp420wlan_scan_complete(p->context, + (struct host_event_scanres *) buff, + buff + sizeof(struct host_event_scanres), skb->len); + + } else if (event == UMAC_EVENT_RX /* || + event == EVENT_RX_MIC_FAILURE*/) { + if (dev->params->production_test) { + dev->stats->rx_packet_data_count++; + dev_kfree_skb_any(skb); + } else { + uccp420wlan_rx_frame(skb, p->context); + + } + + } else if (event == UMAC_EVENT_TX_DONE) { + if (dev->params->production_test && + dev->params->start_prod_mode) + uccp420wlan_proc_tx_complete((void *)buff, + p->context); + else { + /* Increment tx_done_recv_count to keep track of number + * of tx_done received do not count tx dones from host. + */ + dev->stats->tx_done_recv_count++; + +#ifdef MULTI_CHAN_SUPPORT + spin_lock(&dev->chanctx_lock); + curr_chanctx_idx = dev->curr_chanctx_idx; + spin_unlock(&dev->chanctx_lock); +#endif + uccp420wlan_tx_complete((void *)buff, +#ifdef MULTI_CHAN_SUPPORT + curr_chanctx_idx, +#endif + p->context); + } + + cmd_info.tx_done_recv_count++; + } else if (event == UMAC_EVENT_DISCONNECTED) { + struct host_event_disconnect *dis = + (struct host_event_disconnect *)buff; + struct mac80211_dev *dev = (struct mac80211_dev *)p->context; + struct ieee80211_vif *vif = NULL; + int i = 0; + + if (dis->reason_code == REASON_NW_LOST) { + for (i = 0; i < MAX_VIFS; i++) { + if (!(dev->active_vifs & (1 << i))) + continue; + + vif = rcu_dereference(dev->vifs[i]); + + if (ether_addr_equal(vif->addr, + dis->mac_addr)) { + ieee80211_connection_loss(vif); + break; + } + } + } + } else if (event == UMAC_EVENT_MIB_STAT) { + struct umac_event_mib_stats *mib_stats = + (struct umac_event_mib_stats *) buff; + + uccp420wlan_mib_stats(mib_stats, p->context); + } else if (event == UMAC_EVENT_MAC_STATS) { + struct umac_event_mac_stats *mac_stats = + (struct umac_event_mac_stats *) buff; + + uccp420wlan_mac_stats(mac_stats, p->context); + } else if (event == UMAC_EVENT_NW_FOUND) { + UCCP_DEBUG_IF("received event_found\n"); + } else if (event == UMAC_EVENT_PHY_STAT) { + int i; + struct host_event_phy_stats *phy = + (struct host_event_phy_stats *)buff; + UCCP_DEBUG_IF("received phy stats event\n"); + UCCP_DEBUG_IF("phy stats are\n"); + + for (i = 0; i < 32; i++) + UCCP_DEBUG_IF("%x ", phy->phy_stats[i]); + + UCCP_DEBUG_IF("\n\n\n"); + } else if (event == UMAC_EVENT_NOA) { + uccp420wlan_noa_event(FROM_EVENT_NOA, (void *)buff, + p->context, NULL); + + } else if (event == UMAC_EVENT_COMMAND_PROC_DONE) { + /*struct host_event_command_complete *cmd = + * (struct host_event_command_complete*)buff; + */ + UCCP_DEBUG_IF("Received PROC_DONE\n"); + + spin_lock_bh(&cmd_info.control_path_lock); + + if (cmd_info.outstanding_ctrl_req == 0) { + pr_err("%s-UMACIF: Unexpected: Spurious proc_done received. Ignoring and continuing\n", + p->name); + } else { + cmd_info.outstanding_ctrl_req--; + + UCCP_DEBUG_IF("After DEC: outstanding cmd: %d\n", + cmd_info.outstanding_ctrl_req); + + pending_cmd = skb_dequeue(&cmd_info.outstanding_cmd); + + if (unlikely(pending_cmd != NULL)) { + UCCP_DEBUG_IF("Send 1 outstanding cmd\n"); + hal_ops.send((void *)pending_cmd, HOST_MOD_ID, + UMAC_MOD_ID, 0); + dev->stats->gen_cmd_send_count++; + } + } + spin_unlock_bh(&cmd_info.control_path_lock); + + } else if (event == UMAC_EVENT_CH_PROG_DONE) { + uccp420wlan_ch_prog_complete(event, + (struct umac_event_ch_prog_complete *)buff, p->context); + } else if (event == UMAC_EVENT_RADAR_DETECTED) { + ieee80211_radar_detected(dev->hw); + } else if (event == UMAC_EVENT_RF_CALIB_DATA) { + struct umac_event_rf_calib_data *rf_data = (void *)buff; + + uccp420wlan_rf_calib_data(rf_data, p->context); + } else if (event == UMAC_EVENT_ROC_STATUS) { + struct umac_event_roc_status *roc_status = (void *)buff; + struct delayed_work *work = NULL; + + UCCP_DEBUG_ROC("%s:%d ROC status is %d\n", + __func__, __LINE__, roc_status->roc_status); + + switch (roc_status->roc_status) { + case UMAC_ROC_STAT_STARTED: + if (dev->roc_params.roc_in_progress == 0) { + dev->roc_params.roc_in_progress = 1; + ieee80211_ready_on_channel(dev->hw); + UCCP_DEBUG_ROC("%s-UMACIF: ROC READY..\n", + dev->name); + } + break; + case UMAC_ROC_STAT_DONE: + case UMAC_ROC_STAT_STOPPED: + if (dev->roc_params.roc_in_progress == 1) { + work = &dev->roc_complete_work; + ieee80211_queue_delayed_work(dev->hw, + work, + 0); + } + break; + } +#ifdef MULTI_CHAN_SUPPORT + } else if (event == UMAC_EVENT_CHAN_SWITCH) { + uccp420wlan_proc_ch_sw_event((void *)buff, + p->context); + +#endif + } else if (event == UMAC_EVENT_FW_ERROR) { + pr_err("%s: FW is in Error State, please reload.\n", __func__); + } else { + pr_warn("%s: Unknown event received %d\n", __func__, event); + } + + if (event != UMAC_EVENT_RX) + dev_kfree_skb_any(skb); + + rcu_read_unlock(); + + return 0; +} + + +int uccp420wlan_lmac_if_init(void *context, const char *name) +{ + struct lmac_if_data *p; + + UCCP_DEBUG_IF("%s-UMACIF: lmac_if init called\n", name); + + p = kzalloc(sizeof(struct lmac_if_data), GFP_KERNEL); + + if (!p) + return -ENOMEM; + + p->name = (char *)name; + p->context = context; + hal_ops.register_callback(uccp420wlan_msg_handler, UMAC_MOD_ID); + rcu_assign_pointer(lmac_if, p); + skb_queue_head_init(&cmd_info.outstanding_cmd); + spin_lock_init(&cmd_info.control_path_lock); + cmd_info.outstanding_ctrl_req = 0; + + return 0; +} + + +void uccp420wlan_lmac_if_deinit(void) +{ + struct lmac_if_data *p; + + UCCP_DEBUG_IF("%s-UMACIF: Deinit called\n", lmac_if->name); + + p = rcu_dereference(lmac_if); + rcu_assign_pointer(lmac_if, NULL); + synchronize_rcu(); + kfree(p); +} + + +void uccp420_lmac_if_free_outstnding(void) +{ + + struct sk_buff *skb; + + /* First free the outstanding commands, we are not sending + * anymore commands to the FW except RESET. + */ + while ((skb = __skb_dequeue(&cmd_info.outstanding_cmd))) + dev_kfree_skb_any(skb); + + cmd_info.outstanding_ctrl_req = 0; +} diff --git a/drivers/phy/phy-pistachio-usb.c b/drivers/phy/phy-pistachio-usb.c index c6db35e6bb637f..466b24f0b7e4ef 100644 --- a/drivers/phy/phy-pistachio-usb.c +++ b/drivers/phy/phy-pistachio-usb.c @@ -21,6 +21,10 @@ #include +#define USB_PHY_CONTROL0 0x00 +#define USB_PHY_CONTROL0_OTG_DRVVBUS_SHIFT 11 +#define USB_PHY_CONTROL0_OTG_DRVVBUS_MASK 1 + #define USB_PHY_CONTROL1 0x04 #define USB_PHY_CONTROL1_FSEL_SHIFT 2 #define USB_PHY_CONTROL1_FSEL_MASK 0x7 @@ -38,6 +42,7 @@ struct pistachio_usb_phy { struct device *dev; struct regmap *cr_top; struct clk *phy_clk; + bool vbus_drive; unsigned int refclk; }; @@ -70,6 +75,14 @@ static int pistachio_usb_phy_power_on(struct phy *phy) USB_PHY_STRAP_CONTROL_REFCLK_SHIFT, p_phy->refclk << USB_PHY_STRAP_CONTROL_REFCLK_SHIFT); + if (p_phy->vbus_drive) { + /* allow USB block to control VBUS */ + regmap_update_bits(p_phy->cr_top, USB_PHY_CONTROL0, + USB_PHY_CONTROL0_OTG_DRVVBUS_MASK << + USB_PHY_CONTROL0_OTG_DRVVBUS_SHIFT, + 1 << USB_PHY_CONTROL0_OTG_DRVVBUS_SHIFT); + } + rate = clk_get_rate(p_phy->phy_clk); if (p_phy->refclk == REFCLK_XO_CRYSTAL && rate != 12000000) { dev_err(p_phy->dev, "Unsupported rate for XO crystal: %ld\n", @@ -167,6 +180,8 @@ static int pistachio_usb_phy_probe(struct platform_device *pdev) return ret; } + p_phy->vbus_drive = of_property_read_bool(p_phy->dev->of_node, "enable-vbus-drive"); + phy = devm_phy_create(p_phy->dev, NULL, &pistachio_usb_phy_ops); if (IS_ERR(phy)) { dev_err(p_phy->dev, "Failed to create PHY: %ld\n", diff --git a/drivers/pinctrl/pinctrl-pistachio.c b/drivers/pinctrl/pinctrl-pistachio.c index 85c9046c690e2c..98a459b1c095a6 100644 --- a/drivers/pinctrl/pinctrl-pistachio.c +++ b/drivers/pinctrl/pinctrl-pistachio.c @@ -469,27 +469,27 @@ static const char * const pistachio_mips_pll_lock_groups[] = { "mfio83", }; -static const char * const pistachio_sys_pll_lock_groups[] = { +static const char * const pistachio_audio_pll_lock_groups[] = { "mfio84", }; -static const char * const pistachio_wifi_pll_lock_groups[] = { +static const char * const pistachio_rpu_v_pll_lock_groups[] = { "mfio85", }; -static const char * const pistachio_bt_pll_lock_groups[] = { +static const char * const pistachio_rpu_l_pll_lock_groups[] = { "mfio86", }; -static const char * const pistachio_rpu_v_pll_lock_groups[] = { +static const char * const pistachio_sys_pll_lock_groups[] = { "mfio87", }; -static const char * const pistachio_rpu_l_pll_lock_groups[] = { +static const char * const pistachio_wifi_pll_lock_groups[] = { "mfio88", }; -static const char * const pistachio_audio_pll_lock_groups[] = { +static const char * const pistachio_bt_pll_lock_groups[] = { "mfio89", }; @@ -559,12 +559,12 @@ enum pistachio_mux_option { PISTACHIO_FUNCTION_DREQ4, PISTACHIO_FUNCTION_DREQ5, PISTACHIO_FUNCTION_MIPS_PLL_LOCK, + PISTACHIO_FUNCTION_AUDIO_PLL_LOCK, + PISTACHIO_FUNCTION_RPU_V_PLL_LOCK, + PISTACHIO_FUNCTION_RPU_L_PLL_LOCK, PISTACHIO_FUNCTION_SYS_PLL_LOCK, PISTACHIO_FUNCTION_WIFI_PLL_LOCK, PISTACHIO_FUNCTION_BT_PLL_LOCK, - PISTACHIO_FUNCTION_RPU_V_PLL_LOCK, - PISTACHIO_FUNCTION_RPU_L_PLL_LOCK, - PISTACHIO_FUNCTION_AUDIO_PLL_LOCK, PISTACHIO_FUNCTION_DEBUG_RAW_CCA_IND, PISTACHIO_FUNCTION_DEBUG_ED_SEC20_CCA_IND, PISTACHIO_FUNCTION_DEBUG_ED_SEC40_CCA_IND, @@ -620,12 +620,12 @@ static const struct pistachio_function pistachio_functions[] = { FUNCTION(dreq4), FUNCTION(dreq5), FUNCTION(mips_pll_lock), + FUNCTION(audio_pll_lock), + FUNCTION(rpu_v_pll_lock), + FUNCTION(rpu_l_pll_lock), FUNCTION(sys_pll_lock), FUNCTION(wifi_pll_lock), FUNCTION(bt_pll_lock), - FUNCTION(rpu_v_pll_lock), - FUNCTION(rpu_l_pll_lock), - FUNCTION(audio_pll_lock), FUNCTION(debug_raw_cca_ind), FUNCTION(debug_ed_sec20_cca_ind), FUNCTION(debug_ed_sec40_cca_ind), @@ -809,17 +809,17 @@ static const struct pistachio_pin_group pistachio_groups[] = { PADS_FUNCTION_SELECT2, 12, 0x3), MFIO_MUX_PIN_GROUP(83, MIPS_PLL_LOCK, MIPS_TRACE_DATA, USB_DEBUG, PADS_FUNCTION_SELECT2, 14, 0x3), - MFIO_MUX_PIN_GROUP(84, SYS_PLL_LOCK, MIPS_TRACE_DATA, USB_DEBUG, + MFIO_MUX_PIN_GROUP(84, AUDIO_PLL_LOCK, MIPS_TRACE_DATA, USB_DEBUG, PADS_FUNCTION_SELECT2, 16, 0x3), - MFIO_MUX_PIN_GROUP(85, WIFI_PLL_LOCK, MIPS_TRACE_DATA, SDHOST_DEBUG, + MFIO_MUX_PIN_GROUP(85, RPU_V_PLL_LOCK, MIPS_TRACE_DATA, SDHOST_DEBUG, PADS_FUNCTION_SELECT2, 18, 0x3), - MFIO_MUX_PIN_GROUP(86, BT_PLL_LOCK, MIPS_TRACE_DATA, SDHOST_DEBUG, + MFIO_MUX_PIN_GROUP(86, RPU_L_PLL_LOCK, MIPS_TRACE_DATA, SDHOST_DEBUG, PADS_FUNCTION_SELECT2, 20, 0x3), - MFIO_MUX_PIN_GROUP(87, RPU_V_PLL_LOCK, DREQ2, SOCIF_DEBUG, + MFIO_MUX_PIN_GROUP(87, SYS_PLL_LOCK, DREQ2, SOCIF_DEBUG, PADS_FUNCTION_SELECT2, 22, 0x3), - MFIO_MUX_PIN_GROUP(88, RPU_L_PLL_LOCK, DREQ3, SOCIF_DEBUG, + MFIO_MUX_PIN_GROUP(88, WIFI_PLL_LOCK, DREQ3, SOCIF_DEBUG, PADS_FUNCTION_SELECT2, 24, 0x3), - MFIO_MUX_PIN_GROUP(89, AUDIO_PLL_LOCK, DREQ4, DREQ5, + MFIO_MUX_PIN_GROUP(89, BT_PLL_LOCK, DREQ4, DREQ5, PADS_FUNCTION_SELECT2, 26, 0x3), PIN_GROUP(TCK, "tck"), PIN_GROUP(TRSTN, "trstn"), diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c index 8a029f9bc18cb0..c5aa001c9f302a 100644 --- a/drivers/pwm/pwm-img.c +++ b/drivers/pwm/pwm-img.c @@ -35,7 +35,6 @@ #define PWM_CH_CFG_TMBASE_SHIFT 0 #define PWM_CH_CFG_DUTY_SHIFT 16 -#define PERIP_PWM_PDM_CONTROL 0x0140 #define PERIP_PWM_PDM_CONTROL_CH_MASK 0x1 #define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch) ((ch) * 4) @@ -54,6 +53,7 @@ struct img_pwm_soc_data { u32 max_timebase; + u32 syscon_offset; }; struct img_pwm_chip { @@ -140,12 +140,13 @@ static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { u32 val; struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + u32 pdm_mux = pwm_chip->data->syscon_offset; val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); val |= BIT(pwm->hwpwm); img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); - regmap_update_bits(pwm_chip->periph_regs, PERIP_PWM_PDM_CONTROL, + regmap_update_bits(pwm_chip->periph_regs, pdm_mux, PERIP_PWM_PDM_CONTROL_CH_MASK << PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm), 0); @@ -171,6 +172,7 @@ static const struct pwm_ops img_pwm_ops = { static const struct img_pwm_soc_data pistachio_pwm = { .max_timebase = 255, + .syscon_offset = 0x0140, }; static const struct of_device_id img_pwm_of_match[] = { diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 85d5904e5480f0..05ed163f120f93 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_RESET_CONTROLLER) += core.o obj-$(CONFIG_ARCH_LPC18XX) += reset-lpc18xx.o obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_ARCH_BERLIN) += reset-berlin.o +obj-$(CONFIG_MACH_PISTACHIO) += reset-pistachio.o obj-$(CONFIG_ARCH_SUNXI) += reset-sunxi.o obj-$(CONFIG_ARCH_STI) += sti/ obj-$(CONFIG_ARCH_ZYNQ) += reset-zynq.o diff --git a/drivers/reset/reset-pistachio.c b/drivers/reset/reset-pistachio.c new file mode 100755 index 00000000000000..059e385580f976 --- /dev/null +++ b/drivers/reset/reset-pistachio.c @@ -0,0 +1,154 @@ +/* + * Pistachio SoC Reset Controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PISTACHIO_SOFT_RESET 0 + +struct pistachio_reset_data { + struct reset_controller_dev rcdev; + struct regmap *periph_regs; +}; + +static inline int pistachio_reset_shift(unsigned long id) +{ + switch (id) { + case PISTACHIO_RESET_I2C0: + case PISTACHIO_RESET_I2C1: + case PISTACHIO_RESET_I2C2: + case PISTACHIO_RESET_I2C3: + case PISTACHIO_RESET_I2S_IN: + case PISTACHIO_RESET_PRL_OUT: + case PISTACHIO_RESET_SPDIF_OUT: + case PISTACHIO_RESET_SPI: + case PISTACHIO_RESET_PWM_PDM: + case PISTACHIO_RESET_UART0: + case PISTACHIO_RESET_UART1: + case PISTACHIO_RESET_QSPI: + case PISTACHIO_RESET_MDC: + case PISTACHIO_RESET_SDHOST: + case PISTACHIO_RESET_ETHERNET: + case PISTACHIO_RESET_IR: + case PISTACHIO_RESET_HASH: + case PISTACHIO_RESET_TIMER: + return id; + case PISTACHIO_RESET_I2S_OUT: + case PISTACHIO_RESET_SPDIF_IN: + case PISTACHIO_RESET_EVT: + return id + 6; + case PISTACHIO_RESET_USB_H: + case PISTACHIO_RESET_USB_PR: + case PISTACHIO_RESET_USB_PHY_PR: + case PISTACHIO_RESET_USB_PHY_PON: + return id + 7; + default: + return -EINVAL; + } +} + +static int pistachio_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct pistachio_reset_data *rd; + u32 mask; + int shift; + + rd = container_of(rcdev, struct pistachio_reset_data, rcdev); + shift = pistachio_reset_shift(id); + if (shift < 0) + return shift; + mask = BIT(shift); + + return regmap_update_bits(rd->periph_regs, PISTACHIO_SOFT_RESET, + mask, mask); +} + +static int pistachio_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct pistachio_reset_data *rd; + u32 mask; + int shift; + + rd = container_of(rcdev, struct pistachio_reset_data, rcdev); + shift = pistachio_reset_shift(id); + if (shift < 0) + return shift; + mask = BIT(shift); + + return regmap_update_bits(rd->periph_regs, PISTACHIO_SOFT_RESET, + mask, 0); +} + +static struct reset_control_ops pistachio_reset_ops = { + .assert = pistachio_reset_assert, + .deassert = pistachio_reset_deassert, +}; + +static int pistachio_reset_probe(struct platform_device *pdev) +{ + struct pistachio_reset_data *rd; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + + rd = devm_kzalloc(dev, sizeof(*rd), GFP_KERNEL); + if (!rd) + return -ENOMEM; + + rd->periph_regs = syscon_node_to_regmap(np->parent); + if (IS_ERR(rd->periph_regs)) + return PTR_ERR(rd->periph_regs); + + rd->rcdev.owner = THIS_MODULE; + rd->rcdev.nr_resets = PISTACHIO_RESET_MAX + 1; + rd->rcdev.ops = &pistachio_reset_ops; + rd->rcdev.of_node = np; + + return reset_controller_register(&rd->rcdev); +} + +static int pistachio_reset_remove(struct platform_device *pdev) +{ + struct pistachio_reset_data *data = platform_get_drvdata(pdev); + + reset_controller_unregister(&data->rcdev); + + return 0; +} + +static const struct of_device_id pistachio_reset_dt_ids[] = { + { .compatible = "img,pistachio-reset", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, pistachio_reset_dt_ids); + +static struct platform_driver pistachio_reset_driver = { + .probe = pistachio_reset_probe, + .remove = pistachio_reset_remove, + .driver = { + .name = "pistachio-reset", + .of_match_table = of_match_ptr(pistachio_reset_dt_ids), + }, +}; +module_platform_driver(pistachio_reset_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("Pistacho Reset Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 2a524244afec1d..65a31ed4a6a697 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1321,6 +1321,14 @@ config RTC_DRV_GENERIC RTC abstraction. If you do not know what you are doing, you should just say Y. +config RTC_DRV_PISTACHIO + tristate "PISTACHIO_RTC" + depends on MACH_PISTACHIO + help + If you say Y here you will get access to the real time clock + This RTC driver uses pistachio counter registers to provide + a rtc abstraction. + config RTC_DRV_PXA tristate "PXA27x/PXA3xx" depends on ARCH_PXA diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 231f7645161504..a7da79de6b3b53 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_RTC_DRV_PM8XXX) += rtc-pm8xxx.o obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o obj-$(CONFIG_RTC_DRV_PUV3) += rtc-puv3.o obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o +obj-$(CONFIG_RTC_DRV_PISTACHIO) += rtc-pistachio.o obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o diff --git a/drivers/rtc/rtc-pistachio.c b/drivers/rtc/rtc-pistachio.c new file mode 100644 index 00000000000000..8d8794fbbd1da3 --- /dev/null +++ b/drivers/rtc/rtc-pistachio.c @@ -0,0 +1,541 @@ +/* + * RTC driver for Pistachio Platform. + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Top level reg */ +#define CR_TIMER_CTRL_CFG 0x00 +#define TIMER_ME_GLOBAL BIT(0) +#define CR_TIMER_REV 0x10 + +/* Timer specific registers */ +#define TIMER_CFG 0x20 +#define TIMER_ME_LOCAL BIT(0) + +#define TIMER_MAX_OVERFLOW 0xf +#define TIMER_OVERFLOW_SHIFT 4 + +#define TIMER_RELOAD_VALUE 0x24 +#define TIMER_CURRENT_VALUE 0x28 +#define TIMER_CURRENT_OVERFLOW_VALUE 0x2C +#define TIMER_IRQ_STATUS 0x30 +#define TIMER_IRQ_CLEAR 0x34 +#define TIMER_IRQ_MASK 0x38 + +#define TIMER_OVERFLOW_MAX_INT 0x2 +#define PERIP_TIMER_CONTROL 0x90 + +/* Timer specific configuration Values */ +#define MAX_RELOAD_VALUE 0xffffffff +#define GP_TIMER1_MASK CYCLECOUNTER_MASK(32) + +#define TIMER1_IDX 1 +#define TIMER1_SLOW_CLOCK_MASK BIT(1) +#define TIMER1_SLOW_CLOCK_EN 0x2 + +#define TIMER2_IDX 2 +#define TIMER2_SLOW_CLOCK_MASK BIT(2) +#define TIMER2_SLOW_CLOCK_EN 0x4 + +struct pistachio_rtc { + struct rtc_device *rtc; + struct timecounter tc; + struct cyclecounter cc; + spinlock_t counter_lock, timer_lock; + struct clk *sys_clk, *slow_clk; + u64 timer_reload_ns, alarm_secs; + void __iomem *timer_base; + int timer_irq, alarm_irq; + bool alarm_wakeup_en; + u32 ct1_idx, ct2_idx; +}; + +static inline u32 gpt_readl(u32 offset, struct pistachio_rtc *priv, u32 idx) +{ + return readl(priv->timer_base + 0x20 * idx + offset); +} + +static inline void gpt_writel(u32 value, u32 offset, struct pistachio_rtc *priv, + u32 idx) +{ + writel(value, priv->timer_base + 0x20 * idx + offset); +} + +static inline u32 counter1_readl(u32 offset, struct pistachio_rtc *priv) +{ + return gpt_readl(offset, priv, priv->ct1_idx); +} + +static inline void counter1_writel(u32 value, u32 offset, + struct pistachio_rtc *priv) +{ + gpt_writel(value, offset, priv, priv->ct1_idx); +} + +static inline u32 counter2_readl(u32 offset, struct pistachio_rtc *priv) +{ + return gpt_readl(offset, priv, priv->ct2_idx); +} + +static inline void counter2_writel(u32 value, u32 offset, + struct pistachio_rtc *priv) +{ + gpt_writel(value, offset, priv, priv->ct2_idx); +} + +static void pistachio_counter_enable(struct pistachio_rtc *priv, int idx, + bool enable) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&priv->timer_lock, flags); + val = gpt_readl(TIMER_CFG, priv, idx); + if (enable) + val |= TIMER_ME_LOCAL; + else + val &= ~TIMER_ME_LOCAL; + + gpt_writel(val, TIMER_CFG, priv, idx); + spin_unlock_irqrestore(&priv->timer_lock, flags); +} + +static void pistachio_counter_setup(struct pistachio_rtc *priv, u32 reload_val, + u32 ovrflw_val, u32 idx) +{ + unsigned long flags; + + /* Disable GPT local before loading a new reload value */ + pistachio_counter_enable(priv, idx, false); + + spin_lock_irqsave(&priv->timer_lock, flags); + gpt_writel(reload_val, TIMER_RELOAD_VALUE, priv, idx); + gpt_writel(ovrflw_val << TIMER_OVERFLOW_SHIFT, TIMER_CFG, priv, idx); + spin_unlock_irqrestore(&priv->timer_lock, flags); + + pistachio_counter_enable(priv, idx, true); +} + +static int pistachio_get_time(struct device *dev, struct rtc_time *tm) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + unsigned long flags; + u64 time_in_nsecs; + + spin_lock_irqsave(&priv->counter_lock, flags); + time_in_nsecs = timecounter_read(&priv->tc); + rtc_time64_to_tm(div64_u64(time_in_nsecs, NSEC_PER_SEC), tm); + spin_unlock_irqrestore(&priv->counter_lock, flags); + + return rtc_valid_tm(tm); +} + +static cycle_t pistachio_timer1_cc_read(const struct cyclecounter *cc) +{ + struct pistachio_rtc *priv = container_of(cc, struct pistachio_rtc, cc); + unsigned long flags; + u32 counter, overflw; + cycle_t ovrflw_cyc; + u64 ovrflw_tm, tot_cyc; + + spin_lock_irqsave(&priv->timer_lock, flags); + overflw = counter1_readl(TIMER_CURRENT_OVERFLOW_VALUE, priv); + counter = counter1_readl(TIMER_CURRENT_VALUE, priv); + tot_cyc = ~counter; + if (overflw) { + /* In case of any overflows adjust time. */ + if (overflw - 1) + timecounter_adjtime(&priv->tc, + priv->timer_reload_ns * (overflw - 1)); + + if (priv->tc.cycle_last) { + ovrflw_cyc = MAX_RELOAD_VALUE - priv->tc.cycle_last; + priv->tc.cycle_last = 0; + ovrflw_tm = cyclecounter_cyc2ns(priv->tc.cc, ovrflw_cyc, + priv->tc.mask, &priv->tc.frac); + timecounter_adjtime(&priv->tc, ovrflw_tm); + } + } + + spin_unlock_irqrestore(&priv->timer_lock, flags); + return tot_cyc; +} + + +static int pistachio_set_time(struct device *dev, struct rtc_time *tm) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + unsigned long flags; + unsigned int ret = 0; + + ret = rtc_valid_tm(tm); + if (ret) + return ret; + + spin_lock_irqsave(&priv->counter_lock, flags); + /* + * Restart the timer block and initialize timecounter + * since we are setting a new time. + * Now we can use count from h/w timer and + * add to s/w timecounter to get correct timestamp. + */ + pistachio_counter_setup(priv, MAX_RELOAD_VALUE, TIMER_MAX_OVERFLOW, + priv->ct1_idx); + timecounter_init(&priv->tc, &priv->cc, + ktime_to_ns(rtc_tm_to_ktime(*tm))); + + spin_unlock_irqrestore(&priv->counter_lock, flags); + + return 0; +} + +static int pistachio_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + + if (priv->alarm_secs) { + alarm->enabled = 1; + rtc_time64_to_tm(priv->alarm_secs, &alarm->time); + } else { + alarm->enabled = 0; + alarm->pending = 0; + alarm->time.tm_mon = -1; + alarm->time.tm_mday = -1; + alarm->time.tm_year = -1; + alarm->time.tm_hour = -1; + alarm->time.tm_min = -1; + alarm->time.tm_sec = -1; + } + + return 0; +} + +static int pistachio_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + u64 reload_val = 0, ovrflw = 0, cyc = 0, cur_tm_sec, time_in_nsecs; + + if (alarm->enabled) { + /* + * find the alarm time in seconds using + * current time stamp and setup it up based on + * slow clock rate. + */ + time_in_nsecs = timecounter_read(&priv->tc); + cur_tm_sec = div64_u64(time_in_nsecs, NSEC_PER_SEC); + priv->alarm_secs = rtc_tm_to_time64(&alarm->time); + cyc = (priv->alarm_secs - cur_tm_sec) * + clk_get_rate(priv->slow_clk); + + ovrflw = TIMER_MAX_OVERFLOW; + reload_val = DIV_ROUND_UP_ULL(cyc, ovrflw - 1); + if (reload_val > MAX_RELOAD_VALUE) + return -EINVAL; + } + + if (reload_val || ovrflw) + pistachio_counter_setup(priv, reload_val, + ovrflw, priv->ct2_idx); + + return 0; +} + +static const struct rtc_class_ops pistachio_rtc_ops = { + .read_time = pistachio_get_time, + .set_time = pistachio_set_time, + .read_alarm = pistachio_read_alarm, + .set_alarm = pistachio_set_alarm, +}; + +static irqreturn_t pistachio_counter_irq(int irq, void *dev) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + int irq_status; + u64 time_in_nsecs; + + counter1_writel(TIMER_OVERFLOW_MAX_INT, TIMER_IRQ_CLEAR, priv); + /* + * we have overflowed counter max so update time_counter + * with the current time stamp. + */ + time_in_nsecs = timecounter_read(&priv->tc); + + irq_status = counter1_readl(TIMER_IRQ_STATUS, priv); + counter1_writel(irq_status, TIMER_IRQ_CLEAR, priv); + counter1_writel(0x0, TIMER_IRQ_CLEAR, priv); + + return IRQ_HANDLED; +} + +static irqreturn_t pistachio_alarm_handler(int irq, void *dev) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + int irq_status, val; + + counter2_writel(TIMER_OVERFLOW_MAX_INT, TIMER_IRQ_CLEAR, priv); + /* + * Need a to read the current overflow value + * and reload value to clear the current irq. + */ + val = counter2_readl(TIMER_CURRENT_OVERFLOW_VALUE, priv); + val = counter2_readl(TIMER_CURRENT_VALUE, priv); + + irq_status = counter2_readl(TIMER_IRQ_STATUS, priv); + counter2_writel(irq_status, TIMER_IRQ_CLEAR, priv); + priv->alarm_secs = 0; + counter2_writel(0x0, TIMER_IRQ_CLEAR, priv); + counter2_writel(0x0, TIMER_RELOAD_VALUE, priv); + + /* Disable alarm counter2 */ + counter2_writel(0x0, TIMER_CFG, priv); + + rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int pistachio_rtc_probe(struct platform_device *pdev) +{ + struct pistachio_rtc *priv; + struct regmap *periph_regs; + struct resource *res_regs; + unsigned long rate; + int ret = 0; + u32 mult, shift; + u64 mask; + ktime_t sys_time; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->timer_base = devm_ioremap_resource(&pdev->dev, res_regs); + if (IS_ERR(priv->timer_base)) + return PTR_ERR(priv->timer_base); + + spin_lock_init(&priv->counter_lock); + spin_lock_init(&priv->timer_lock); + priv->ct1_idx = TIMER1_IDX; + priv->ct2_idx = TIMER2_IDX; + + priv->timer_irq = platform_get_irq(pdev, 0); + if (priv->timer_irq < 0) { + dev_err(&pdev->dev, "Error getting counter1 platform irq\n"); + return priv->timer_irq; + } + + ret = devm_request_irq(&pdev->dev, priv->timer_irq, + pistachio_counter_irq, IRQ_TYPE_LEVEL_HIGH, + dev_name(&pdev->dev), &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Error requesting irq\n"); + return ret; + } + + priv->alarm_irq = platform_get_irq(pdev, 1); + if (priv->alarm_irq < 0) { + dev_err(&pdev->dev, "Error getting counter2 platform irq\n"); + return priv->alarm_irq; + } + + ret = devm_request_irq(&pdev->dev, priv->alarm_irq, + pistachio_alarm_handler, IRQ_TYPE_LEVEL_HIGH, + dev_name(&pdev->dev), &pdev->dev); + + if (ret < 0) { + dev_err(&pdev->dev, "Error requesting irq\n"); + return ret; + } + + periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-periph"); + if (IS_ERR(periph_regs)) { + dev_err(&pdev->dev, "cannot get peripheral regmap (%lu)\n", + PTR_ERR(periph_regs)); + return PTR_ERR(periph_regs); + } + + platform_set_drvdata(pdev, priv); + /* Switch to using the slow counter clock of gptimer1 for RTC */ + ret = regmap_update_bits(periph_regs, PERIP_TIMER_CONTROL, + TIMER1_SLOW_CLOCK_MASK, TIMER1_SLOW_CLOCK_EN); + if (ret) + return ret; + + /* Switch to using the slow counter clock of gptimer2 for alarm */ + ret = regmap_update_bits(periph_regs, PERIP_TIMER_CONTROL, + TIMER2_SLOW_CLOCK_MASK, TIMER2_SLOW_CLOCK_EN); + if (ret) + return ret; + + priv->sys_clk = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(priv->sys_clk)) { + dev_err(&pdev->dev, "clock get failed (%lu)\n", + PTR_ERR(priv->sys_clk)); + return PTR_ERR(priv->sys_clk); + } + + priv->slow_clk = devm_clk_get(&pdev->dev, "slow"); + if (IS_ERR(priv->slow_clk)) { + dev_err(&pdev->dev, "clock get failed (%lu)\n", + PTR_ERR(priv->slow_clk)); + return PTR_ERR(priv->slow_clk); + } + + ret = clk_prepare_enable(priv->sys_clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock (%d)\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->slow_clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock (%d)\n", ret); + goto sys_clk_err; + } + + rate = clk_get_rate(priv->slow_clk); + mask = GP_TIMER1_MASK; + clocks_calc_mult_shift(&mult, &shift, rate, + NSEC_PER_SEC, DIV_ROUND_UP_ULL(mask, rate)); + + priv->cc.mult = mult; + priv->cc.shift = shift; + priv->cc.read = pistachio_timer1_cc_read; + priv->cc.mask = mask; + + counter1_writel(TIMER_OVERFLOW_MAX_INT, TIMER_IRQ_MASK, priv); + counter2_writel(TIMER_OVERFLOW_MAX_INT, TIMER_IRQ_MASK, priv); + sys_time = ktime_get(); + pistachio_counter_setup(priv, MAX_RELOAD_VALUE, TIMER_MAX_OVERFLOW, + priv->ct1_idx); + /* Initialize timecounter with system time */ + timecounter_init(&priv->tc, &priv->cc, ktime_to_ns(sys_time)); + + /* + * max time value on a single overflow, used to adjust time + * on overflow scenarios. + */ + priv->timer_reload_ns = cyclecounter_cyc2ns(priv->tc.cc, + MAX_RELOAD_VALUE, priv->tc.mask, &priv->tc.frac); + + /* + * Enable Irq wake in case of system suspend, we need to wakeup + * and update our time stamp. + * rtc based on counter1 runs on slow clock at 31.25Khz this will + * max_overflow after 25.45 days. + * (1÷31,250)×(2^32)×(2^4)÷(60×60×24) = 25.45days + * 32 bits counter and 4 overflow bits. + */ + device_init_wakeup(&pdev->dev, true); + + priv->rtc = devm_rtc_device_register(&pdev->dev, "rtc-pistachio", + &pistachio_rtc_ops, THIS_MODULE); + if (IS_ERR(priv->rtc)) { + ret = PTR_ERR(priv->rtc); + dev_err(&pdev->dev, "failed to rtc (%d)\n", ret); + goto slow_clk_err; + } + + + return ret; + +slow_clk_err: + clk_disable_unprepare(priv->slow_clk); +sys_clk_err: + clk_disable_unprepare(priv->sys_clk); + + return ret; +} + +static int pistachio_rtc_remove(struct platform_device *pdev) +{ + struct pistachio_rtc *priv = platform_get_drvdata(pdev); + + clk_disable_unprepare(priv->slow_clk); + clk_disable_unprepare(priv->sys_clk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +int pistachio_rtc_suspend(struct device *dev) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + enable_irq_wake(priv->timer_irq); + + /* + * if counter2 is enabled then enable irq wake for alarm + */ + if (counter2_readl(TIMER_CFG, priv) & TIMER_ME_LOCAL) { + enable_irq_wake(priv->alarm_irq); + priv->alarm_wakeup_en = true; + + } + } + + return 0; +} + +int pistachio_rtc_resume(struct device *dev) +{ + struct pistachio_rtc *priv = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + disable_irq_wake(priv->timer_irq); + + if (priv->alarm_wakeup_en) { + priv->alarm_wakeup_en = false; + disable_irq_wake(priv->alarm_irq); + } + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(pistachio_rtc_pmops, pistachio_rtc_suspend, + pistachio_rtc_resume); + +static const struct of_device_id pistachio_rtc_of_match[] = { + { .compatible = "img,rtc-pistachio" }, +}; +MODULE_DEVICE_TABLE(of, pistachio_rtc_of_match); + +static struct platform_driver pistachio_rtc_driver = { + .driver = { + .name = "rtc-pistachio", + .of_match_table = pistachio_rtc_of_match, + .pm = &pistachio_rtc_pmops, + }, + .probe = pistachio_rtc_probe, + .remove = pistachio_rtc_remove, +}; +module_platform_driver(pistachio_rtc_driver); + +MODULE_AUTHOR("Imagination Technologies Ltd."); +MODULE_DESCRIPTION("Pistachio RTC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 4e853ed2c82b93..48e4dc2bdbf638 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -1,6 +1,7 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/brcmstb/Kconfig" +source "drivers/soc/img/Kconfig" source "drivers/soc/mediatek/Kconfig" source "drivers/soc/qcom/Kconfig" source "drivers/soc/rockchip/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index f2ba2e932ae10c..598c45538e430b 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ +obj-$(CONFIG_SOC_IMG) += img/ obj-$(CONFIG_MACH_DOVE) += dove/ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ obj-$(CONFIG_ARCH_QCOM) += qcom/ diff --git a/drivers/soc/img/Kconfig b/drivers/soc/img/Kconfig new file mode 100644 index 00000000000000..bdf667dd4eb812 --- /dev/null +++ b/drivers/soc/img/Kconfig @@ -0,0 +1,49 @@ +menuconfig SOC_IMG + bool "IMG SOC drivers support" + +if SOC_IMG + +config IMG_EFUSE + tristate "Imagination Technologies Generic eFuse driver" + depends on HAS_IOMEM + help + Imagination Technologies Generic eFuse driver which exposes the status + of 128 EFUSE bits. + + To compile this driver as a module, choose M here: the module will + be called img-efuse. + +config IMG_CONNECTIVITY + tristate "Imagination Technologies UCCP base driver" + depends on HAS_IOMEM && CMA + default m + help + Imagination Technologies UCCP driver which loads the firmware + and sets it up for operation. Drivers providing separate + features like Bluetooth or WLAN use this driver. + + To compile this driver as a module, choose M here: the module will + be called img-connectivity. + +config IMG_HOSTPORT + tristate "Imagination Technologies Hostport driver" + depends on IMG_CONNECTIVITY + default m + help + Imagination Technologies Hostport driver which provides the API to + communicate with the UCCP through shared memory. + + To compile this driver as a module, choose M here: the module will + be called img-hostport. + +config IMG_HOSTPORT_DUMMY_CLIENT + tristate "Imagination Technologies Hostport dummy client" + depends on IMG_HOSTPORT + default n + help + An example how to use the API exported by the Hostport driver. + + To compile this driver as a module, choose M here: the module will + be called img-hostport. + +endif # SOC_IMG diff --git a/drivers/soc/img/Makefile b/drivers/soc/img/Makefile new file mode 100644 index 00000000000000..5347ed9c3fd60d --- /dev/null +++ b/drivers/soc/img/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SOC_IMG) += fuse/ +obj-$(CONFIG_SOC_IMG) += connectivity/ diff --git a/drivers/soc/img/connectivity/Makefile b/drivers/soc/img/connectivity/Makefile new file mode 100644 index 00000000000000..543cd233db2b0e --- /dev/null +++ b/drivers/soc/img/connectivity/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_IMG_CONNECTIVITY) += img-connectivity.o +obj-$(CONFIG_IMG_HOSTPORT) += img-hostport.o +obj-$(CONFIG_IMG_HOSTPORT_DUMMY_CLIENT) += img-dummy.o + +img-connectivity-y := img-connectivity-main.o img-fwldr.o mem-region.o diff --git a/drivers/soc/img/connectivity/devres-ext.h b/drivers/soc/img/connectivity/devres-ext.h new file mode 100644 index 00000000000000..147d2c9bf0b36a --- /dev/null +++ b/drivers/soc/img/connectivity/devres-ext.h @@ -0,0 +1,7 @@ +static void devm_iounmap_resource(struct device *d, struct resource *r, + void __iomem *addr) +{ + devm_iounmap(d, addr); + devm_release_mem_region(d, r->start, resource_size(r)); +} + diff --git a/drivers/soc/img/connectivity/img-connectivity-main.c b/drivers/soc/img/connectivity/img-connectivity-main.c new file mode 100644 index 00000000000000..c07ce2d04614b7 --- /dev/null +++ b/drivers/soc/img/connectivity/img-connectivity-main.c @@ -0,0 +1,438 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-connectivity.c + *** + *** File Description: + *** This file contains the implementation of the UCCP base driver. + *** + ****************************************************************************** + *END**************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "devres-ext.h" +#include "img-connectivity-platform.h" +#include "img-fwldr.h" + +#define MOD_NAME "img-connectivity" +#define mod_err(format, ...) pr_err(MOD_NAME ": " format "\n", ##__VA_ARGS__) +#define mod_info(format, ...) pr_info(MOD_NAME ": " format "\n", ##__VA_ARGS__) + +#define MAX_LOADERS 2 +#define MB (1024*1024) +#define SCRATCHBUF_SIZE (4*MB) + +enum { + BOOT_OFF = 0, + BOOT_MTX = 1, + BOOT_MCP = 2, +}; +static uint boot = BOOT_MTX; +module_param(boot, uint, 0400); +MODULE_PARM_DESC(boot, + "Boot flag: \n\t0 - skip booting altogether" + "\n\t1 - boot META" + "\n\t2 - boot META and MCP"); + +/* + * The following assumes BT configuration, i.e. one loader file and two + * META threads. + * # modprobe img-connectivity + * + * WIFI only configuration is two loaders (MCP, META) and one META thread. + * Though binaries' names are just examples, remember that they must be relative + * to [/usr]/lib/firmware/. + * # modprobe img-connectivity mtx_threads=1 mcp_ldr="img/mcp.ldr" + * mtx_ldr="img/mtx.ldr" boot=2 + * + * Combo configuration is the best of both worlds: two loaders (MCP, META) + * and two META threads. You use it like that: + * # modprobe img-connectivity mcp_ldr="img/mcp.ldr" mtx_ldr="img/mtx.ldr" + * boot=2 + */ + +static unsigned int mtx_threads = 2; +module_param(mtx_threads, uint, 0400); +MODULE_PARM_DESC(mtx_threads, "Number of available meta threads"); + +static char *mtx_ldr = "img/connectivity-mtx.ldr"; +module_param(mtx_ldr, charp, 0400); +MODULE_PARM_DESC(mtx_ldr, "META loader binary"); + +static char *mcp_ldr = NULL; +module_param(mcp_ldr, charp, 0400); +MODULE_PARM_DESC(mcp_ldr, "MCP loader binary"); + +struct img_connectivity { + void *scratch; + dma_addr_t scratch_bus; + unsigned char __iomem *uccp_sbus_v; + unsigned char __iomem *uccp_gram_v; + struct resource *uccp_sbus; + struct resource *uccp_gram; +}; +static struct img_connectivity *module; + +struct clock { + struct list_head xs; + struct clk *p; +}; +static LIST_HEAD(clocks); + +/* + * *** Public API *** + */ +struct img_version_info img_connectivity_version(void) +{ + struct img_version_info vi = { .bt = 0, .wlan = 0 }; + return vi; +} +EXPORT_SYMBOL(img_connectivity_version); + +struct img_scratch_info img_connectivity_scratch(void) +{ + struct img_scratch_info si = { + .virt_addr = module->scratch, + .bus_addr = module->scratch_bus + }; + return si; +} +EXPORT_SYMBOL(img_connectivity_scratch); + +/* + * *** Private API *** + */ +static int img_connectivity_memsetup(struct platform_device *d) +{ + module = devm_kzalloc(&d->dev, sizeof(struct img_connectivity), + GFP_KERNEL); + if (NULL == module) { + return -ENOMEM; + } + + module->scratch = dmam_alloc_coherent(&d->dev, SCRATCHBUF_SIZE, + &module->scratch_bus, GFP_KERNEL); + if (NULL == module->scratch) + return -ENOMEM; + + return 0; +} + +static void img_connectivity_memsetup_rollback(struct platform_device *d) +{} + +#define until(i, max) for (i = 0; i < max; i++) +static int boot_cpu(struct device *d, const char *fw_name, + unsigned int num_threads) +{ + int err, t_idx; + const struct firmware *fw = NULL; + + err = request_firmware(&fw, fw_name, d); + if (err) { + mod_err("firmware request failed for %s", fw_name); + return err; + } + + until(t_idx, num_threads) + fwldr_soft_reset(t_idx); + + err = fwldr_load_fw(fw->data); + if (!err) + mod_info("firmware %s loaded", fw_name); + else + mod_err("firmware %s load failed", fw_name); + + release_firmware(fw); + return err; +} + +static int remap_uccp_regions(struct device *d) +{ + module->uccp_sbus_v = devm_ioremap_resource(d, module->uccp_sbus); + if (IS_ERR(module->uccp_sbus_v)) + return PTR_ERR(module->uccp_sbus_v); + + module->uccp_gram_v = devm_ioremap_resource(d, module->uccp_gram); + if (IS_ERR(module->uccp_gram_v)) + return PTR_ERR(module->uccp_gram_v); + + return 0; +} + +static void unmap_uccp_regions(struct device *d) +{ + devm_iounmap_resource(d, module->uccp_gram, module->uccp_gram_v); + devm_iounmap_resource(d, module->uccp_sbus, module->uccp_sbus_v); +} + +static int img_connectivity_boot(struct platform_device *d) +{ + int err, t_idx; + + err = remap_uccp_regions(&d->dev); + if (err) + return err; + + fwldr_init(module->uccp_sbus_v, module->uccp_gram_v, NULL); + + soc_set_uccp_extram_base(module->uccp_sbus_v, module->scratch_bus); + + if (BOOT_OFF == boot) { + unmap_uccp_regions(&d->dev); + mod_info("skipping boot"); + return 0; + } + + /* + * MCP code, if provided, has to be loaded first. After that it is + * necessary to stop all META threads. + */ + if(BOOT_MCP == boot) { + if (!mcp_ldr) { + mod_err("MCP boot requested, but MCP loader binary " + "not specified"); + return -ENOENT; + } + + err = boot_cpu(&d->dev, mcp_ldr, mtx_threads); + if (err) + return err; + + until(t_idx, mtx_threads) + fwldr_stop_thrd(t_idx); + } + + err = boot_cpu(&d->dev, mtx_ldr, mtx_threads); + if (err) + return err; + + unmap_uccp_regions(&d->dev); + + return 0; +} + +static void img_connectivity_boot_rollback(struct platform_device *d) +{} + +static int img_connectivity_clock_setup(struct platform_device *d) +{ + int ret; + struct clock *clock; + + list_for_each_entry(clock, &clocks, xs) { + ret = clk_prepare_enable(clock->p); + if (ret) { + list_for_each_entry_continue_reverse(clock, &clocks, xs) + clk_disable_unprepare(clock->p); + return ret; + } + } + + return 0; +} + +static void img_connectivity_clock_setup_rollback(struct platform_device *d) +{ + struct clock *clock; + + list_for_each_entry(clock, &clocks, xs) + clk_disable_unprepare(clock->p); +} + +static struct clock *alloc_single_clock(struct device *d, int index) +{ + struct clock *tmp; + struct clk *tmpclk; + + tmpclk = of_clk_get(d->of_node, index); + if (IS_ERR(tmpclk)) { + return ERR_PTR(-ENODEV); + } + + tmp = devm_kzalloc(d, sizeof(struct clock), GFP_KERNEL); + if (NULL == tmp) { + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&tmp->xs); + tmp->p = tmpclk; + return tmp; +} + +static int img_connectivity_dtsetup(struct platform_device *d) +{ + int i; + struct clock *tmp; + int clocks_no; + + /* + * Check how many clocks we have defined. + * + * Note that 'clocks' is an optional property. + */ + if (of_property_read_u32(d->dev.of_node, "clocks-number", &clocks_no)) { + mod_info("img-connectivity: could not find 'clocks-number' " + "dt property"); + return 0; + } + + mod_info("img-connectivity: detected %d clocks", clocks_no); + + INIT_LIST_HEAD(&clocks); + for (i = 0; i < clocks_no; i++) { + tmp = alloc_single_clock(&d->dev, i); + if (!IS_ERR(tmp)) { + list_add(&tmp->xs, &clocks); + } else if (ERR_PTR(-ENODEV) == tmp) { + mod_err("img-connectivity: invalid clock reference %d", + i); + return PTR_ERR(tmp); + } else if (ERR_PTR(-ENOMEM) == tmp) { + mod_err("img-connectivity: failed to allocate " + "clock descriptor"); + return PTR_ERR(tmp); + } else { + mod_err("img-connectivity: BUG: unknown return value " + "%ld", PTR_ERR(tmp)); + return PTR_ERR(tmp); + } + } + + /* + * Parse 'reg' property + */ + module->uccp_sbus = platform_get_resource_byname(d, IORESOURCE_MEM, + "UCCP system bus"); + if (IS_ERR(module->uccp_sbus)) + return -ENOENT; + + module->uccp_gram = platform_get_resource_byname(d, IORESOURCE_MEM, + "UCCP packed GRAM"); + if (IS_ERR(module->uccp_gram)) + return -ENOENT; + + return 0; +} + +static void img_connectivity_dtsetup_rollback(struct platform_device *d) +{} + +static int img_connectivity_memmap(struct platform_device *d) +{ + return 0; +} + +static void img_connectivity_memmap_rollback(struct platform_device *d) +{} + +/* + * * platform driver code & data + */ +static int __init img_connectivity_probe(struct platform_device *d) +{ + int ret; + + ret = img_connectivity_memsetup(d); + if (ret) + goto memsetup_failed; + + ret = img_connectivity_dtsetup(d); + if (ret) + goto dtsetup_failed; + + ret = img_connectivity_memmap(d); + if (ret) + goto memmap_failed; + + ret = img_connectivity_clock_setup(d); + if (ret) + goto clock_setup_failed; + + ret = img_connectivity_boot(d); + if (ret) + goto boot_failed; + + return 0; +boot_failed: + img_connectivity_clock_setup_rollback(d); +clock_setup_failed: + img_connectivity_memmap_rollback(d); +memmap_failed: + img_connectivity_dtsetup_rollback(d); +dtsetup_failed: + img_connectivity_memsetup_rollback(d); +memsetup_failed: + return ret; +} + +static int img_connectivity_remove(struct platform_device *d) +{ + img_connectivity_boot_rollback(d); + img_connectivity_clock_setup_rollback(d); + img_connectivity_dtsetup_rollback(d); + img_connectivity_memsetup_rollback(d); + return 0; +} + +static const struct of_device_id img_connectivity_dt_ids[] = { + { .compatible = "img,pistachio-uccp-system" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_connectivity_dt_ids); + +static struct platform_driver img_connectivity_pd = { + .remove = img_connectivity_remove, + .driver = { + .name = "img-connectivity", + .of_match_table = of_match_ptr(img_connectivity_dt_ids), + }, +}; + +/* + * * .ko entry and exit points + */ +static int __init img_connectivity_entry(void) +{ + return platform_driver_probe(&img_connectivity_pd, img_connectivity_probe); +} + +static void __exit img_connectivity_leave(void) +{ + platform_driver_unregister(&img_connectivity_pd); +} + +module_init(img_connectivity_entry); +module_exit(img_connectivity_leave); + +/* + * * module metadata + */ +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bartosz Flis "); +MODULE_DESCRIPTION("Imagination Technologies RPU base driver - www.imgtec.com"); diff --git a/drivers/soc/img/connectivity/img-connectivity-pistachio.h b/drivers/soc/img/connectivity/img-connectivity-pistachio.h new file mode 100644 index 00000000000000..6beb1c83c26bc0 --- /dev/null +++ b/drivers/soc/img/connectivity/img-connectivity-pistachio.h @@ -0,0 +1,47 @@ +/* + * File Name : img-connectivity-pistachio.h + * + * File Description: Platform specific definitions for Pistachio SoC + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _IMG_CONNECTIVITY_PISTACHIO_H_ +#define _IMG_CONNECTIVITY_PISTACHIO_H_ + +#include + +#define CR_MC_SYS_MEM_BASE0_OFFSET 0x38218 + +static inline void soc_set_uccp_extram_base(void __iomem *sbus, + phys_addr_t addr) +{ + /* + * For Pistachio platform this setup is sufficient to use DRAM from + * UCCP. For other platforms additional registers inside the UCCP may + * need to be set up. + * + * For details of the bitshifts refer to Pistachio TRM p.481 + * + */ + iowrite32((addr >> 12) << 10, + (void __iomem *)((u32)sbus + CR_MC_SYS_MEM_BASE0_OFFSET)); +} + +#endif diff --git a/drivers/soc/img/connectivity/img-connectivity-platform.h b/drivers/soc/img/connectivity/img-connectivity-platform.h new file mode 100644 index 00000000000000..37a54da9e6ea57 --- /dev/null +++ b/drivers/soc/img/connectivity/img-connectivity-platform.h @@ -0,0 +1,25 @@ +/* + * File Name : img-connectivity-platform.h + * + * File Description: Platform-specific headers dispatch + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "img-connectivity-pistachio.h" diff --git a/drivers/soc/img/connectivity/img-dummy.c b/drivers/soc/img/connectivity/img-dummy.c new file mode 100644 index 00000000000000..10434f4d502354 --- /dev/null +++ b/drivers/soc/img/connectivity/img-dummy.c @@ -0,0 +1,168 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-dummy.c + *** + *** File Description: + *** This file contains an example of how to use the Hostport + *** transport protocol. + *** + ****************************************************************************** + *END**************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FIVE_S (5 * HZ) +#define DUMMY_ID 2 + +static DEFINE_SPINLOCK(busy_writing); + +static bool strneq(const char *c1, const char *c2, __kernel_size_t count) +{ + return 0 == strncmp(c1, c2, count); +} + +static void async_request(int number, const char *err_msg) +{ + spin_lock(&busy_writing); + if (-ETIME == img_transport_notify_timeout(number, DUMMY_ID, FIVE_S)) { + pr_err("img-dummy : %s", err_msg); + } + spin_unlock(&busy_writing); +} + +static void async_on(void) +{ + async_request(0xFFFF, "could not turn on async messages"); +} + +static void async_off(void) +{ + async_request(0, "could not turn off async messages"); +} + +static ssize_t write(struct file *file, const char *buf, size_t count, + loff_t *pos) +{ + printk("%.*s\n", count, buf); + if (strneq("on", buf, min(count,(size_t)2))) { + pr_info("img-dummy : requesting async on\n"); + async_on(); + } else if (strneq("off", buf, min(count,(size_t)3))) { + pr_info("img-dummy : requesting async off\n"); + async_off(); + } + return count; +} + +static struct file_operations ops = { + .owner = THIS_MODULE, + .write = write, +}; + +void received_message(u16 user_data) +{ + pr_info("img-dummy : received message 0x%04X\n", user_data); +} + +/* + * * platform driver code & data + */ +static int __init img_dummy_probe(struct platform_device *d) +{ + int result; + struct proc_dir_entry *entry; + struct img_scratch_info si; + + /* + * This is how you can get hold of the pointer to an auxiliary, + * physically contiguous buffer. + * si.virt_addr is the address you most probably want to use. + */ + si = img_connectivity_scratch(); + (void)si; + + entry = proc_create("hsdummy", 0600, NULL, &ops); + if (IS_ERR_OR_NULL(entry)) { + result = PTR_ERR(entry); + goto proc_create_failed; + } + + result = img_transport_register_callback(received_message, DUMMY_ID); + if (result) + goto register_callback_failed; + + return 0; + +register_callback_failed: + remove_proc_entry("hsdummy", NULL); +proc_create_failed: + return result; +} + +static int img_dummy_remove(struct platform_device *d) +{ + img_transport_remove_callback(DUMMY_ID); + remove_proc_entry("hsdummy", NULL); + return 0; +} + +static const struct of_device_id img_dummy_dt_ids[] = { + { .compatible = "img,pistachio-uccp-dummy" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_dummy_dt_ids); + +static struct platform_driver img_dummy_pd = { + .remove = img_dummy_remove, + .driver = { + .name = "img-dummy", + .of_match_table = of_match_ptr(img_dummy_dt_ids), + }, +}; + +static int __init img_bt_init(void) +{ + return platform_driver_probe(&img_dummy_pd, img_dummy_probe); +} + +static void __exit img_bt_exit(void) +{ + platform_driver_unregister(&img_dummy_pd); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bartosz Flis "); +MODULE_DESCRIPTION("Imagination Technologies dummy Hostport endpoint \ +- www.imgtec.com"); + +module_init(img_bt_init); +module_exit(img_bt_exit); diff --git a/drivers/soc/img/connectivity/img-fwldr-private.h b/drivers/soc/img/connectivity/img-fwldr-private.h new file mode 100644 index 00000000000000..82121141a70c06 --- /dev/null +++ b/drivers/soc/img/connectivity/img-fwldr-private.h @@ -0,0 +1,342 @@ +/* + * File Name : fwldr.h + * + * File Description: This file contains definitions used for firmware loader + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _FWLDR_H_ +#define _FWLDR_H_ + +#include "img-fwldr.h" + +#ifdef DRIVER_DEBUG + +#define fwldr_dbg_err(...) pr_err(__VA_ARGS__) +#define fwldr_dbg_info(...) pr_info(__VA_ARGS__) +#define fwldr_dbg_dump(...) pr_info(__VA_ARGS__) + +#else +#define fwldr_dbg_err(...) do { } while (0) +#define fwldr_dbg_info(...) do { } while (0) +#define fwldr_dbg_dump(...) do { } while (0) + +#endif + +#define FWLDR_PLRCRD_WORDS 16 /* 64-bit WORDS */ +#define FWLDR_PLRCRD_BYTES (FWLDR_PLRCRD_WORDS * 8) +#define FWLDR_PLRCRD_TRAIL_BYTES 8 +#define FWLDR_PLRCRD_DATA_BYTES (FWLDR_PLRCRD_BYTES - FWLDR_PLRCRD_TRAIL_BYTES) + + +/****************************************************************************** +* These constants are used to access various fields within an L1 record as well +* as other constants that are used. +******************************************************************************/ + +/* The maximum number of bytes in an L1 record. */ +#define FWLDR_L1_MAXSIZE 32 + +/* The maximum number of bytes in an L2 record. */ +#define FWLDR_L2_MAXSIZE 4096 + +/* The size in bytes of the 'cmd' field in an L1 Record. */ +#define FWLDR_L1_CMD_SIZE 2 + +/* The size in bytes of the 'length' field in an L1 Record. */ +#define FWLDR_L1_LEN_SIZE 2 + +/* The size in bytes of the 'next' field in an L1 Record. */ +#define FWLDR_L1_NXT_SIZE 4 + +/* The size in bytes of the 'arg1' field in an L1 Record. */ +#define FWLDR_L1_ARG1_SIZE 4 + +/* The size in bytes of the 'arg2' field in an L1 Record. */ +#define FWLDR_L1_ARG2_SIZE 4 + +/* The size in bytes of the 'l2offset' field in an L1 Record. */ +#define FWLDR_L1_L2OFF_SIZE 4 + +/* The size in bytes of the 'xsum' field in an L1 Record. */ +#define FWLDR_L1_XSUM_SIZE 2 + +/* The offset in bytes of the 'cmd' field in an L1 record. */ +#define FWLDR_L1_CMD_OFF 0 + +/* The offset in bytes of the 'length' field in an L1 record. */ +#define FWLDR_L1_LEN_OFF (FWLDR_L1_CMD_OFF + FWLDR_L1_CMD_SIZE) + +/* The offset in bytes of the 'next' field in an L1 record. */ +#define FWLDR_L1_NXT_OFF (FWLDR_L1_LEN_OFF + FWLDR_L1_LEN_SIZE) + +/* The offset in bytes of the 'arg1' field in an L1 record. */ +#define FWLDR_L1_ARG1_OFF (FWLDR_L1_NXT_OFF + FWLDR_L1_NXT_SIZE) + +/* The offset in bytes of the 'arg2' field in an L1 record. */ +#define FWLDR_L1_ARG2_OFF (FWLDR_L1_ARG1_OFF + FWLDR_L1_ARG1_SIZE) + +/* The following is the value used to terminate a chain of L1 records */ +#define FWLDR_L1_TERMINATE 0xffffffff + + +/****************************************************************************** +* These constants are used to access various fields within an L2 record as well +* as other constants that are used. +******************************************************************************/ + +/* The size in bytes of the 'cmd' field in an L2 Record. */ +#define FWLDR_L2_CMD_SIZE 2 + +/* The size in bytes of the 'length' field in an L2 Record. */ +#define FWLDR_L2_LEN_SIZE 2 + +/* The size in bytes of the 'xsum' field in an L2 Record. */ +#define FWLDR_L2_XSUM_SIZE 2 + +/* The offset in bytes from the beginning of an L2 record to the data + * payload + */ +#define FWLDR_L2_DATA (FWLDR_L2_CMD_SIZE + FWLDR_L2_LEN_SIZE) + + +/****************************************************************************** +* Various combined values... +******************************************************************************/ + +/* Sizes of common items between L1 and L2 records */ +#define FWLDR_L1_L2LEN_SIZE FWLDR_L2_LEN_SIZE + +/* The size in bytes of an L1 record when it contains no data */ +#define FWLDR_L1_BASIC_SIZE (FWLDR_L1_CMD_SIZE + \ + FWLDR_L1_LEN_SIZE + \ + FWLDR_L1_NXT_SIZE + \ + FWLDR_L1_L2OFF_SIZE + \ + FWLDR_L1_L2LEN_SIZE + \ + FWLDR_L1_XSUM_SIZE) + +/* The size in bytes of an L2 record when it contains no data */ +#define FWLDR_L2_BASIC_SIZE (FWLDR_L2_CMD_SIZE + \ + FWLDR_L2_LEN_SIZE + \ + FWLDR_L2_XSUM_SIZE) + + +/* Offsets in bytes from the end of an L1 record for various fields */ +#define FWLDR_L1_L2LEN_OFF (FWLDR_L1_XSUM_SIZE + FWLDR_L1_L2LEN_SIZE) +#define FWLDR_L1_L2OFF_OFF (FWLDR_L1_L2LEN_OFF + FWLDR_L1_L2OFF_SIZE) + +#define UCCP_GRAM_BASE 0xB7000000 + +#define UCCP_SLAVE_PORT_OFFSET 0x3C000 +#define UCCP_OFFSET_MASK 0x00FFFFFF +#define UCCP_BASE_MASK 0xFF000000 +#define UCCP_SYSBUS_REG 0x02 +#define UCCP_GRAM_PACKED 0xB7 +#define UCCP_GRAM_MSB 0xB4 + +#define THREAD_STRIDE 0x1000 +#define ON_THREAD_INDIRECT(n, ind_addr) ((ind_addr) | ((n) & 0x3) << 12) +#define ON_THREAD(n, addr) ((addr) + THREAD_STRIDE*(n)) +#define MTX_REG_INDIRECT(unit, reg) (((reg & 0x7) << 4) | (unit & 0xF)) + +#define MTX_PC_REG_IND_ADDR MTX_REG_INDIRECT(5, 0) +#define MTX_A0STP_REG_IND_ADDR MTX_REG_INDIRECT(3, 0) + +#define MTX_PCX_REG_IND_ADDR MTX_REG_INDIRECT(5, 1) +#define MTX_TXMASK_REG_IND_ADDR MTX_REG_INDIRECT(7, 1) +#define MTX_TXMASKI_REG_IND_ADDR MTX_REG_INDIRECT(7, 3) +#define MTX_TXPOLL_REG_IND_ADDR MTX_REG_INDIRECT(7, 4) +#define MTX_TXPOLLI_REG_IND_ADDR MTX_REG_INDIRECT(7, 6) +#define MTX_TXSTAT_REG_IND_ADDR MTX_REG_INDIRECT(7, 0) +#define MTX_TXSTATI_REG_IND_ADDR MTX_REG_INDIRECT(7, 2) + +#define REG_IND_READ_FLAG (1 << 16) + +#define MTX_TXPRIVEXT_ADDR 0x048000E8 +#define MTX_TXSTATUS_ADDR 0x48000010 +#define MTX_TXENABLE_ADDR 0x04800000 +#define MTX_START_EXECUTION 1 +#define MTX_STOP_EXECUTION 0 + +#define MTX_TXUXXRXDT 0x0480FFF0 +#define MTX_TXUXXRXRQ 0x0480FFF8 + +#define MSLV_BASE_ADDR 0x0203C000 + +/* DATA Exchange Register */ +#define MSLVDATAX (MSLV_BASE_ADDR + 0x2000) + +/* DATA Transfer Register */ +#define MSLVDATAT (MSLV_BASE_ADDR + 0x2040) + +/* Control Register 0 */ +#define MSLVCTRL0 (MSLV_BASE_ADDR + 0x2080) + +/* Soft Reset register */ +#define MSLVSRST (MSLV_BASE_ADDR + 0x2600) + +#define SLAVE_ADDR_MODE_MASK 0xFFFFFFFC +#define SLAVE_SINGLE_WRITE 0x00 +#define SLAVE_SINGLE_READ 0x01 +#define SLAVE_BLOCK_WRITE 0x02 +#define SLAVE_BLOCK_READ 0x03 + +/* Control Register 1 */ +#define MSLVCTRL1 (MSLV_BASE_ADDR + 0x20c0) + +#define MSLVCTRL1_POLL_MASK 0x07000000 +#define MSLAVE_READY(v) ((v & MSLVCTRL1_POLL_MASK) == MSLVCTRL1_POLL_MASK) +#define LTP_THREAD_NUM 0 /* Since, only one thread exists */ + +/* Thread completion signature */ +#define UCCP_THRD_EXEC_SIG_OFFSET 0x00000434 +#define UCCP_THRD_EXEC_SIG 0x00ADF00D + +#define MAX_LOAD_MEM_LEN 4096 + + +enum fwldr_status { + FWLDR_SUCCESS, + FWLDR_FAIL +}; + +/* Cmd or Tag values used in the L1/L2 records */ +enum fwldr_cmd_tag_l1_l2 { + FWLDR_L1_CMD_LOAD_MEM = 0x0000, /* Command - L1 LoadMem. */ + FWLDR_L1_CMD_START_THRDS = 0x0003, /* Command - L1 StartThrds. */ + FWLDR_L1_CMD_ZERO_MEM = 0x0004, /* Command - L1 ZeroMem. */ + FWLDR_L1_CMD_CONFIG = 0x0005, /* Command - L1 Config. */ + FWLDR_L1_CMD_FILENAME = 0x0010, /* Command - L1 FileName. */ +}; + +/* Enumerates all possible types of configuration commands */ +enum fwldr_conf_cmd { + FWLDR_CONF_CMD_PAUSE = 0x0000, /* Pause */ + FWLDR_CONF_CMD_READ, /* Read */ + FWLDR_CONF_CMD_WRITE, /* Write */ + FWLDR_CONF_CMD_MEMSET, /* MemSet */ + FWLDR_CONF_CMD_MEMCHK, /* MemChk */ + FWLDR_CONF_CMD_USER, /* User */ +}; + +/* Information contained within an .ldr file */ +enum fwldr_ldr_sec { + FWLDR_SEC_NONE = 0, /* Element is undefined */ + FWLDR_SEC_BOOT_HEADER, /* Boot header */ + FWLDR_LDR_CODE, /* Secondary loader executable */ + FWLDR_SEC_DATA_L1, /* Secondary loader top level data stream */ + FWLDR_SEC_DATA_L2 /* Secondary loader raw data stream */ +}; + +enum uccp_mem_region { + UCCP_MEM_CORE, + UCCP_MEM_DIRECT, + UCCP_MEM_ERR +}; + +struct fwldr_bootdevhdr { + unsigned int dev_id; /* Value used to verify access to boot device */ + unsigned int sl_code; /* Offset to secondary loader code */ +#define BOOTDEV_SLCSECURE_BIT 0x80000000 +#define BOOTDEV_SLCCRITICAL_BIT 0x40000000 + + unsigned int sl_data; /* Offset to data used by secondary loader */ + unsigned short pl_ctrl; /* Primary loader control */ +#define BOOTDEV_PLCREMAP_BITS 0x00FF +#define BOOTDEV_PLCREMAP_S 0 + + unsigned short CRC; /* CRC value */ +}; + +struct fwldr_load_mem_info { + unsigned int dst_addr; + unsigned int len; + unsigned char *src_buf; +}; + +struct fwldr_thrd_info { + unsigned int thrd_num; + unsigned int stack_ptr; + unsigned int prog_ctr; + unsigned int catch_state_addr; +}; + +struct fwldr_cfg_rw { + unsigned int addr; + unsigned int val; +}; + +/* Represents a secondary loader top level data stream record. */ +struct fwldr_sec_ldr_l1_record { + unsigned short cmd_tag; /* Command TagMember comment goes here */ + unsigned short len; /* Total length os this record */ + unsigned short crc; /* X25 CRC checksum for this record (including the + * checksum itself) + */ + unsigned int nxt; /* Offset within the .ldr to the next L1RECORD */ + unsigned int arg1; /* The first command argument for the command */ + unsigned int arg2; /* The second command argument for the command */ + unsigned int l2_offset; /* Offset within the .ldr to the corresponding + * raw data record + */ + unsigned int l2_len; /* The expected length of the raw data record */ +}; + +struct fwldr_memhdr_tag { + struct fwldr_memhdr_tag *p_next; + unsigned int addr; /* Target byte address */ + unsigned char *data; /* Data block pointer */ + unsigned int len; /* Len in bytes of data block */ + +}; + +struct fwload_priv { + unsigned char *gram_addr; + unsigned char *core_addr; + unsigned char *gram_b4_addr; +}; + +static inline void fwload_uccp_read(struct fwload_priv *fpriv, + unsigned long base, + unsigned long offset, + unsigned int *data) +{ + if (base == UCCP_SYSBUS_REG) + *data = readl((void __iomem *)fpriv->core_addr + (offset)); + else if (base == UCCP_GRAM_PACKED) + *data = readl((void __iomem *)fpriv->gram_addr + (offset)); + else if (base == UCCP_GRAM_MSB) + *data = readl((void __iomem *)fpriv->gram_b4_addr + (offset)); +} + +static inline void fwload_uccp_write(struct fwload_priv *fpriv, + unsigned long base, + unsigned long offset, + unsigned int data) +{ + if (base == UCCP_SYSBUS_REG) + writel(data, (void __iomem *)(fpriv->core_addr + (offset))); + else if (base == UCCP_GRAM_PACKED) + writel(data, (void __iomem *)(fpriv->gram_addr + (offset))); + else if (base == UCCP_GRAM_MSB) + writel(data, (void __iomem *)(fpriv->gram_b4_addr + (offset))); +} + +#endif /* _FWLDR_H_ */ diff --git a/drivers/soc/img/connectivity/img-fwldr.c b/drivers/soc/img/connectivity/img-fwldr.c new file mode 100644 index 00000000000000..45c521d1a33497 --- /dev/null +++ b/drivers/soc/img/connectivity/img-fwldr.c @@ -0,0 +1,1197 @@ +/* + * File Name : fwldr.c + * + * This file contains contains functions related to firmware loading + * functionality. + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include +#include + +#include "img-fwldr-private.h" +#include "mem-region.h" + +struct fwload_priv *fpriv, fpv; + +static struct mem_region core_regions[] = { + {0x03000000, 0x04FFFFFF}, + {0x02009000, 0x0203BFFF}, + {0x80000000, 0x87FFFFFF}, + {0xB0000000, 0xB7FFFFFF}, +}; + +static unsigned short fwldr_read_le2(unsigned char *buf); +static unsigned int fwldr_read_le4(unsigned char *buf); +static unsigned fwldr_virt_to_linear_off(unsigned page_size, + unsigned offset); + + +static void fwldr_load_mem(unsigned int dst_addr, + unsigned int len, + unsigned char *src_buf); + +static void fwldr_start_thrd(unsigned int thrd_number, + unsigned int stack_ptr, + unsigned int program_ctr, + unsigned int catch_state_addr); + +static void fwldr_zero_mem(unsigned int dst_addr, + unsigned int len); + +static unsigned int fwldr_config_read(unsigned int dst_addr); + +static void fwldr_config_write(unsigned int dst_addr, + unsigned int val); + +static enum uccp_mem_region fwldr_chk_region(unsigned int src_addr, + int length); + +static int fwldr_parser(const unsigned char *fw_data); + +static int fwldr_wait_for_completion(void); + +static void dir_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len); + +static void dir_mem_set(unsigned int addr, + unsigned char data, + unsigned int len); + +static void dir_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len); + +static void dir_mem_write(unsigned int addr, + unsigned int data); + +static void core_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len); + +static void core_mem_set(unsigned int addr, + unsigned int data, + unsigned int len); + +static void core_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len); + +/* dir_mem_cpy + * + * Perform a memcpy of 'len' bytes from 'src_addr' to the UCCP memory location + * pointed by 'dst_addr'. + * + * dst_addr is always a 4 byte aligned address + * data is always a 4 byte aligned address + * len is always a multiple of 4 when dst_addr is of type 0xB4xxxxxx + * len may NOT be a multiple of 4 when dst_addr is of type 0xB7xxxxxx + * + * + * When dst_addr is of type 0xB4xxxxxx, perform only 32 bit writes to these + * locations + * + */ +static void dir_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len) +{ + int i; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + unsigned int *data_addr = (unsigned int *)data; + unsigned char *gram_byte_addr, *data_byte_addr; + + if ((fpriv->gram_b4_addr == NULL) && (base == UCCP_GRAM_MSB)) { + + /* The HAL didn't provide a virtual address for 0xB4xxxxxx alias + * Convert into 0xB7 and do the writes by ignoring MSB of the + * 32-bit word + */ + + addr &= 0x00FFFFFF; + addr |= 0xB7000000; + + data_byte_addr = (unsigned char *)data_addr; + gram_byte_addr = (void *)(fpriv->gram_addr + (offset / 4) * 3); + + + if (len % 4 == 0) { + for (i = 0; i < len / 4; i++) { + memcpy(gram_byte_addr, data_byte_addr + 1, 3); + gram_byte_addr += 3; + data_byte_addr += 4; + } + } else { + fwldr_dbg_err("%s:Unexpected length(base:%lx)\n", + __func__, base); + } + + } else { + + if (len % 4 == 0) { + for (i = 0; i < len / 4; i++) { + fwload_uccp_write(fpriv, base, offset, + data_addr[i]); + offset += 4; + } + } else { + if (base == UCCP_GRAM_PACKED) + memcpy((void *)(fpriv->gram_addr + offset), + (void *)data_addr, len); + else + fwldr_dbg_err("%s:Unexpected length(%lx)\n", + __func__, base); + } + } + + return; +} + + +/* dir_mem_set + * + * Perform a memset of 'len' bytes with value of 'val' to the UCCP memory + * location pointed by 'dst_addr'. + * + * dst_addr is always a 4 byte aligned address + * len is always a multiple of 4 when dst_addr is of type 0xB4xxxxxx + * len may NOT be a multiple of 4 when dst_addr is of type 0xB7xxxxxx + * + * + * When dst_addr is of type 0xB4xxxxxx, perform only 32 bit writes to these + * locations + * + */ +static void dir_mem_set(unsigned int addr, + unsigned char data, + unsigned int len) +{ + int i; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + unsigned char *gram_byte_addr; + + if ((fpriv->gram_b4_addr == NULL) && (base == UCCP_GRAM_MSB)) { + + /* The HAL didn't provide a virtual address for 0xB4xxxxxx alias + * Convert into 0xB7 and do the writes by ignoring MSB of the + * 32-bit word + */ + + addr &= 0x00FFFFFF; + addr |= 0xB7000000; + + gram_byte_addr = (void *)(fpriv->gram_addr + (offset / 4) * 3); + + + if (len % 4 == 0) { + memset(gram_byte_addr, data, (len / 4) * 3); + } else { + fwldr_dbg_err("%s :Unexpected length (base : %lx)\n", + __func__, base); + } + + } else { + + + if (len % 4 == 0) { + for (i = 0; i <= len / 4; i++) { + fwload_uccp_write(fpriv, base, offset, data); + offset += 4; + } + } else if (base == UCCP_GRAM_PACKED) { + memset((void *)(fpriv->gram_addr + offset), + data, len); + } else { + fwldr_dbg_err("%s: Unexpected length (base %lx)\n", + __func__, base); + } + } + + return; +} + +/* Perform 'len' 32 bit reads from a UCCP memory location 'addr' + * 'addr' is always a 4 byte aligned address + */ +static void dir_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len) +{ + int i = 0; + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + + + for (i = 0; i <= len / 4; i++) { + fwload_uccp_read(fpriv, base, offset, data+i); + offset += 4; + } +} + +/* 32 bit write to UCCP memory location 'addr' + * 'addr' is always a 4 byte aligned address + */ +static void dir_mem_write(unsigned int addr, + unsigned int data) +{ + unsigned long offset = (unsigned long)addr & UCCP_OFFSET_MASK; + unsigned long base = ((unsigned long)addr & UCCP_BASE_MASK) >> 24; + + fwload_uccp_write(fpriv, base, offset, data); +} + + +static void core_mem_cpy(unsigned int addr, + unsigned char *data, + unsigned int len) +{ + unsigned int i = 0; + unsigned int *src_data = (unsigned int *)data; + unsigned int flag = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (len > 1) + flag = SLAVE_BLOCK_WRITE; + else + flag = SLAVE_SINGLE_WRITE; + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | flag)); + + for (i = 0; i < len / 4; i++) { + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (data != NULL) + dir_mem_write(MSLVDATAT, src_data[i]); + else + dir_mem_write(MSLVDATAT, 0x00); + } + +} + + +static void core_mem_set(unsigned int addr, + unsigned int data, + unsigned int len) +{ + unsigned int flag = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + if (len > 1) + flag = SLAVE_BLOCK_WRITE; + else + flag = SLAVE_SINGLE_WRITE; + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | flag)); + dir_mem_write(MSLVDATAT, data); +} + + +static void core_mem_read(unsigned int addr, + unsigned int *data, + unsigned int len) +{ + unsigned int i = 0; + unsigned int val = 0; + + /* Poll MSLVCTRL1 */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_write(MSLVCTRL0, + ((addr & SLAVE_ADDR_MODE_MASK) | SLAVE_BLOCK_READ)); + + for (i = 0; i < len-1; i++) { + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_read(MSLVDATAT, &data[i], 1); + } + + /* Read the last word */ + do { + dir_mem_read(MSLVCTRL1, &val, 1); + } while (!MSLAVE_READY(val)); + + dir_mem_read(MSLVDATAX, &data[len-1], 1); + + +} + +static void clear_exec_signature(void) +{ + fwload_uccp_write(fpriv, UCCP_SYSBUS_REG, UCCP_THRD_EXEC_SIG_OFFSET, 0); +} + +static bool check_exec_signature(u32 signature) +{ + u32 sig; + fwload_uccp_read(fpriv, UCCP_SYSBUS_REG, UCCP_THRD_EXEC_SIG_OFFSET, + &sig); + return ((sig & 0x00FFFFFF) == signature); +} + +void fwldr_init(unsigned char *core_addr, unsigned char *gram_addr, + unsigned char *gram_b4_addr) +{ + fpriv = &fpv; + fpriv->gram_addr = gram_addr; + fpriv->core_addr = core_addr; + fpriv->gram_b4_addr = gram_b4_addr; +} + +int fwldr_load_fw(const unsigned char *fw_data) +{ + int err = FWLDR_SUCCESS; + + clear_exec_signature(); + + err = fwldr_parser(fw_data); + + if (err != FWLDR_SUCCESS) { + pr_err("FW load failed\n"); + return err; + } + + if (!fwldr_wait_for_completion()) { + pr_err("FW load timed out waiting for completion\n"); + return FWLDR_FAIL; + } + + return err; +} + +static void fwldr_load_mem(unsigned int dst_addr, + unsigned int len, + unsigned char *src_buf) +{ + enum uccp_mem_region mem_region = UCCP_MEM_ERR; + int i = 0; + + mem_region = fwldr_chk_region(dst_addr, len); + + fwldr_dbg_info("%s dst_addr = 0x%X, length = 0x%X, srcaddr = 0x%X\n", + __func__, dst_addr, len, (unsigned int)src_buf); + + fwldr_dbg_info("Dump upto 16 bytes\n"); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned\n"); + + for (i = 0; i < 16; i += 2) + fwldr_dbg_dump("0x%X \t 0x%X\n", src_buf[i], src_buf[i + 1]); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_cpy(dst_addr, src_buf, len); + break; + + case UCCP_MEM_DIRECT: + dir_mem_cpy(dst_addr, src_buf, len); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped writing\n"); + break; + } +} + +static void fwldr_start_thrd(unsigned int thrd_num, + unsigned int stack_ptr, + unsigned int prog_ctr, + unsigned int catch_state_addr) +{ + fwldr_dbg_info("%s PC = 0x%X,\tSP = 0x%X\n", + __func__, prog_ctr, stack_ptr); + + /* Program Counter */ + core_mem_set(MTX_TXUXXRXDT, prog_ctr, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_PC_REG_IND_ADDR), 1); + + /* Stack Pointer */ + core_mem_set(MTX_TXUXXRXDT, stack_ptr, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_A0STP_REG_IND_ADDR), + 1); + + /* Thread Enable */ + core_mem_set(ON_THREAD(thrd_num, MTX_TXENABLE_ADDR), MTX_START_EXECUTION, 1); + + fwldr_dbg_info("Thread %d is Enabled\n", thrd_num); +} + +void fwldr_stop_thrd(unsigned int thrd_num) +{ + unsigned int val; + + /* Thread Disable */ + core_mem_set(ON_THREAD(thrd_num, MTX_TXENABLE_ADDR), MTX_STOP_EXECUTION, 1); + + core_mem_read(ON_THREAD(thrd_num, MTX_TXENABLE_ADDR), &val, 1); + + while ((val & 0x2) != 0x2) { + core_mem_read(ON_THREAD(thrd_num, MTX_TXENABLE_ADDR), &val, 1); + + fwldr_dbg_info("%s val = 0x%X\n", __func__, val); + + } + + fwldr_dbg_info("TXENABLE = 0x%X\n", val); + fwldr_dbg_info("Thread %d is Stopped\n", thrd_num); +} + +static void fwldr_zero_mem(unsigned int dst_addr, + unsigned int len) +{ + int mem_region = 0; + + fwldr_dbg_info("%s DestAddr = 0x%X, length = 0x%X\n", + __func__, dst_addr, len); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, len); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_cpy(dst_addr, NULL, len); + break; + + case UCCP_MEM_DIRECT: + dir_mem_set(dst_addr, 0x00, len); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped setting\n"); + break; + } +} + +static unsigned int fwldr_config_read(unsigned int dst_addr) +{ + int mem_region = 0; + int val = 0; + + fwldr_dbg_info("%s dst_addr = 0x%X\n", __func__, dst_addr); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, 0); + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_read(dst_addr, &val, 1); + return val; + + case UCCP_MEM_DIRECT: + dir_mem_read(dst_addr, &val, 1); + return val; + + default: + fwldr_dbg_err("Region unknown. Skipped reading\n"); + return 0; + } + + return 0; +} + +static void fwldr_config_write(unsigned int dst_addr, + unsigned int val) +{ + int mem_region = 0; + + fwldr_dbg_info("%s dst_addr = 0x%X,\tValue = 0x%X\n", + __func__, dst_addr, val); + + if (0 != (dst_addr % 4)) + fwldr_dbg_info("Destination Address is not 4 - byte aligned"); + + mem_region = fwldr_chk_region(dst_addr, 0); + + + switch (mem_region) { + case UCCP_MEM_CORE: + core_mem_set(dst_addr, val, 1); + break; + + case UCCP_MEM_DIRECT: + dir_mem_write(dst_addr, val); + break; + + default: + fwldr_dbg_err("Region unknown. Skipped writing\n"); + break; + } + +} + +static enum uccp_mem_region fwldr_chk_region(unsigned int src_addr, int len) +{ + struct mem_region r; + r.from = src_addr; + r.to = src_addr + len; + + fwldr_dbg_info("Checking range 0x%08X - 0x%08X\n", r.from, r.to); + fwldr_dbg_info("\tLegal: %s\n", legal(&r) ? "yes" : "no"); + fwldr_dbg_info("\tOverlap: %s\n", overlaps_any(core_regions, ARRAY_SIZE(core_regions), &r) ? "yes" : "no"); + + if (!legal(&r) || overlaps_any(core_regions, ARRAY_SIZE(core_regions), &r)) + return UCCP_MEM_ERR; + + if (within_any(core_regions, ARRAY_SIZE(core_regions), &r)) + return UCCP_MEM_CORE; + else + return UCCP_MEM_DIRECT; +} + +void fwldr_soft_reset(unsigned int thrd_num) +{ + unsigned int val, temp; + unsigned int retries = 3; + + /* + * If the thread is running, then stop it and clear the registers, + * otherwise do nothing + */ + core_mem_read(ON_THREAD(thrd_num, MTX_TXENABLE_ADDR), &val, 1); + + fwldr_dbg_info("Resetting UCCP420\n"); + + /* Soft Reset */ + dir_mem_read(MSLVSRST, &val, 1); + dir_mem_write(MSLVSRST, (val | 1)); + + /* Wait for 16 core clock cycles. Core runs at 320MHz */ + udelay(10); + + /* Clear the Soft Reset */ + dir_mem_write(MSLVSRST, (val & 0xFFFFFFFE)); + + /* Give additional 20 ms for the DA to do its own reset */ + mdelay(20); + + /* Clear the Minim Bit in PrivExt */ + core_mem_set(ON_THREAD(thrd_num, MTX_TXPRIVEXT_ADDR), 0, 1); + + /* Set the PCX value i to 0 */ + core_mem_set(MTX_TXUXXRXDT, 0, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_PCX_REG_IND_ADDR), 1); + + + /* + * Clear TXPOLL{I} to clear TXSTAT{I} + * Writing 0xFFFFFFFF clears TXSTATI, but TXMASKI must + * be all set too for this to work. + */ + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXMASK_REG_IND_ADDR), + 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXMASKI_REG_IND_ADDR), + 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXPOLL_REG_IND_ADDR), + 1); + + core_mem_set(MTX_TXUXXRXDT, 0xFFFFFFFF, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXPOLLI_REG_IND_ADDR), + 1); + + /* Clear TXMASK and TXMASKI */ + core_mem_set(MTX_TXUXXRXDT, 0x0, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXMASK_REG_IND_ADDR), + 1); + + + core_mem_set(MTX_TXUXXRXDT, 0x0, 1); + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, MTX_TXMASKI_REG_IND_ADDR), + 1); + + /* Ensure all kicks are cleared */ + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, + MTX_TXPOLLI_REG_IND_ADDR | REG_IND_READ_FLAG), + 1); + + core_mem_read(MTX_TXUXXRXDT, &temp, 1); + + while (temp && retries--) { + core_mem_set(MTX_TXUXXRXDT, 0x2, 1); + + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, + MTX_TXPOLLI_REG_IND_ADDR), + 1); + + core_mem_set(MTX_TXUXXRXRQ, + ON_THREAD_INDIRECT(thrd_num, + MTX_TXPOLLI_REG_IND_ADDR | REG_IND_READ_FLAG), + 1); + + core_mem_read(MTX_TXUXXRXDT, &temp, 1); + } + + /* Reset TXSTATUS */ + core_mem_set(ON_THREAD(thrd_num, MTX_TXSTATUS_ADDR), 0x00020000, 1); + + fwldr_dbg_info("Soft Reset core\n"); +} + +/* Reads a 16-bit little endian value from the specified position in a buffer */ +static unsigned short fwldr_read_le2(unsigned char *buf) +{ + unsigned short val = 0; + + val = buf[0]; + val |= buf[1] << 8; + + return val; +} + +/* Reads a 32-bit little endian value from the specified position in a buffer */ +static unsigned int fwldr_read_le4(unsigned char *buf) +{ + unsigned int val = 0; + + val = buf[0]; + val |= buf[1] << 8; + val |= buf[2] << 16; + val |= buf[3] << 24; + + return val; +} + +/* Converts a virtual (paged) offset to a linear (non-paged) offset */ +static unsigned fwldr_virt_to_linear_off(unsigned page_size, unsigned offset) +{ + static unsigned virt_page_size; + + unsigned val = offset; + + if (page_size) { + if (virt_page_size == 0) { + virt_page_size = 1; + while (virt_page_size < page_size) + virt_page_size <<= 1; + } + + val = ((offset / virt_page_size) * page_size) + + (offset % virt_page_size); + } + + return val; +} + +static int fwldr_parser(const unsigned char *fw_data) +{ + int quit = 0; + signed int nxt = 0; + signed int file_offset = 0; + signed int page_size = 0; + signed int orig_offset = 0; + signed int prev_offset = 0; + struct fwldr_bootdevhdr boot_dev_hdr; + char info_buf[256]; + char *cfg_str = NULL; + char cfg_buf[256]; + char *cfg_buf_ptr = NULL; + struct fwldr_sec_ldr_l1_record l1_rec; + struct fwldr_load_mem_info lm_v; + struct fwldr_cfg_rw rw_v; + unsigned char l1_buf[FWLDR_L1_MAXSIZE]; + signed int seek_off; + char *str_curr = NULL; + char *str_end = NULL; + char *str_new = NULL; + int buf_len = 0; + int res = FWLDR_SUCCESS; + + /* Lets really do it */ + memcpy(&boot_dev_hdr, fw_data, sizeof(struct fwldr_bootdevhdr)); + + fwldr_dbg_info("DevID: 0x%08X\n", boot_dev_hdr.dev_id); + fwldr_dbg_info("SLCode: 0x%08X\n", boot_dev_hdr.sl_code); + fwldr_dbg_info("SLData: 0x%08X\n", boot_dev_hdr.sl_data); + fwldr_dbg_info("PLCtrl: 0x%04X\n", boot_dev_hdr.pl_ctrl); + fwldr_dbg_info("CRC: 0x%04X\n", boot_dev_hdr.CRC); + fwldr_dbg_info("%d", sizeof(boot_dev_hdr)); + fwldr_dbg_info("\n"); + + file_offset = fwldr_virt_to_linear_off(page_size, boot_dev_hdr.sl_code); + orig_offset = fwldr_virt_to_linear_off(page_size, boot_dev_hdr.sl_data); + + nxt = sizeof(struct fwldr_bootdevhdr); + + do { + unsigned char sec_ldr_code[FWLDR_PLRCRD_BYTES]; + + memcpy(&sec_ldr_code, + fw_data + file_offset, + FWLDR_PLRCRD_BYTES); + + nxt = fwldr_read_le4(&sec_ldr_code[FWLDR_PLRCRD_DATA_BYTES]); + + file_offset = fwldr_virt_to_linear_off(page_size, + nxt); + } while (nxt); + + file_offset = orig_offset; + + while (!quit) { + unsigned char *l2_buf = NULL, *l2_blk = NULL; + unsigned int l2_len = 0U; + + memcpy(&l1_buf, + fw_data + file_offset, + FWLDR_L1_MAXSIZE); + + l1_rec.cmd_tag = fwldr_read_le2(&l1_buf[FWLDR_L1_CMD_OFF]); + l1_rec.len = fwldr_read_le2(&l1_buf[FWLDR_L1_LEN_OFF]); + l1_rec.nxt = fwldr_read_le4(&l1_buf[FWLDR_L1_NXT_OFF]); + + if ((l1_rec.len > FWLDR_L1_MAXSIZE) || + (l1_rec.len < FWLDR_L1_L2OFF_OFF) || + (l1_rec.len < FWLDR_L1_L2LEN_OFF)) { + fwldr_dbg_err("Maximum L1 length exceeded\n"); + res = FWLDR_FAIL; + break; + } + + /* Extract generic L1 fields */ + l1_rec.l2_offset = fwldr_read_le4(&l1_buf[l1_rec.len - + FWLDR_L1_L2OFF_OFF]); + + l1_rec.l2_len = fwldr_read_le2(&l1_buf[l1_rec.len - + FWLDR_L1_L2LEN_OFF]); + + if (l1_rec.l2_len > FWLDR_L2_BASIC_SIZE) { + /* Read the L2 data */ + seek_off = fwldr_virt_to_linear_off(page_size, + l1_rec.l2_offset); + + if (l1_rec.l2_len > FWLDR_L2_MAXSIZE) { + fwldr_dbg_err("Maximum L2 length exceeded\n"); + res = FWLDR_FAIL; + break; + } + + l2_blk = kmalloc(l1_rec.l2_len + 1, + GFP_KERNEL); + + if (l2_blk == NULL) { + fwldr_dbg_err("Not Enough Memory\n"); + res = FWLDR_FAIL; + break; + } + + memcpy(l2_blk, + fw_data + seek_off, + l1_rec.l2_len); + + l2_blk[l1_rec.l2_len] = '\0'; + + l2_buf = l2_blk + + (FWLDR_L2_CMD_SIZE + FWLDR_L2_LEN_SIZE); + + l2_len = l1_rec.l2_len - FWLDR_L2_BASIC_SIZE; + } + + switch (l1_rec.cmd_tag) { + case FWLDR_L1_CMD_LOAD_MEM: + if (!l2_buf) { + fwldr_dbg_err("Invalid params to Load Mem\n"); + res = FWLDR_FAIL; + quit = 1; + break; + } + + /* Load mem record */ + l1_rec.arg1 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG1_OFF]); + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: Addr: 0x%08X: Size: 0x%08X\n", + "LoadMem", l1_rec.arg1, l2_len); + + lm_v.dst_addr = l1_rec.arg1; + lm_v.len = l2_len; + lm_v.src_buf = l2_buf; + + fwldr_load_mem(lm_v.dst_addr, + lm_v.len, + lm_v.src_buf); + break; + + case FWLDR_L1_CMD_START_THRDS: + /* Start each thread with initial SP */ + if (!l2_buf) { + fwldr_dbg_err("%s : %d Invalid params\n", + __func__, __LINE__); + res = FWLDR_FAIL; + quit = 1; + break; + } + + cfg_buf[0] = '\0'; + cfg_buf_ptr = cfg_buf; + + while (l2_len > 0) { + struct fwldr_thrd_info tinfo_v; + + snprintf(cfg_buf_ptr, + sizeof(cfg_buf), + "\tThrd %d: SP: 0x%08X: PC: 0x%08X: Catch: 0x%08X\n", + fwldr_read_le4(l2_buf), + fwldr_read_le4(l2_buf + 4), + fwldr_read_le4(l2_buf + 8), + fwldr_read_le4(l2_buf + 12)); + + tinfo_v.thrd_num = fwldr_read_le4(l2_buf); + tinfo_v.stack_ptr = fwldr_read_le4(l2_buf + 4); + tinfo_v.prog_ctr = fwldr_read_le4(l2_buf + 8); + tinfo_v.catch_state_addr = + fwldr_read_le4(l2_buf + 12); + + fwldr_start_thrd(tinfo_v.thrd_num, + tinfo_v.stack_ptr, + tinfo_v.prog_ctr, + tinfo_v.catch_state_addr); + + l2_buf += (4 * sizeof(unsigned int)); + l2_len -= (4 * sizeof(unsigned int)); + cfg_buf_ptr += strlen(cfg_buf_ptr); + } + + snprintf(info_buf, + sizeof(info_buf), + "%-12s:\n%s", + "StartThrds", + cfg_buf); + + break; + + case FWLDR_L1_CMD_ZERO_MEM: + /* Zero memory */ + l1_rec.arg1 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG1_OFF]); + l1_rec.arg2 = + fwldr_read_le4(&l1_buf[FWLDR_L1_ARG2_OFF]); + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: Addr: 0x%08X: Size: 0x%08X\n", + "ZeroMem", + l1_rec.arg1, + l1_rec.arg2); + + lm_v.dst_addr = l1_rec.arg1; + lm_v.len = l1_rec.arg2; + + fwldr_zero_mem(lm_v.dst_addr, lm_v.len); + + break; + + case FWLDR_L1_CMD_CONFIG: + /* Configuration commands */ + buf_len = (l1_rec.l2_len / 8) * 40; + + if (!l2_buf) { + fwldr_dbg_err("%s:%d:Invalid params\n", + __func__, __LINE__); + res = FWLDR_FAIL; + quit = 1; + break; + } + + cfg_str = kmalloc(buf_len, GFP_KERNEL); + + if (cfg_str == NULL) { + fwldr_dbg_err("Failed to allocate cfg_str\n"); + } else { + str_curr = cfg_str; + str_end = cfg_str + buf_len; + } + + do { + int rec_len = 0, len = 0; + unsigned int cmd = fwldr_read_le4(l2_buf); + + if ((str_curr && cfg_str) && + ((str_end - str_curr) < 256)) { + size_t pos = str_curr - cfg_str; + + /* Extend buffer */ + buf_len *= 2; + + str_new = krealloc(cfg_str, + buf_len, + GFP_KERNEL); + + if (str_new == NULL) { + fwldr_dbg_err("%s : %d %s\n", + __func__, + __LINE__, + "realloc failed"); + kfree(cfg_str); + cfg_str = NULL; + str_curr = NULL; + str_end = NULL; + } else { + cfg_str = str_new; + /* Relocate pointers */ + str_curr = cfg_str + + pos; + str_end = cfg_str + + buf_len; + } + } + + switch (cmd) { + case FWLDR_CONF_CMD_PAUSE: + rec_len = 8; + /* TODO: Calculate the exact delay */ + mdelay(2); + break; + case FWLDR_CONF_CMD_READ: + rec_len = 8; + + rw_v.addr = fwldr_read_le4(&l2_buf[4]); + rw_v.val = fwldr_config_read(rw_v.addr); + + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tRead : 0x%08X\n", + rw_v.addr); + } + + + /* Value read is in rw_v */ + break; + + case FWLDR_CONF_CMD_WRITE: + rec_len = 12; + + rw_v.addr = fwldr_read_le4(&l2_buf[4]); + rw_v.val = fwldr_read_le4(&l2_buf[8]); + + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tWrite: 0x%08X: 0x%08X\n", + rw_v.addr, + rw_v.val); + } + + + fwldr_config_write(rw_v.addr, rw_v.val); + + break; + + case FWLDR_CONF_CMD_USER: + if (str_curr) { + unsigned int v1 = 0; + unsigned int v2 = 0; + unsigned int v3 = 0; + unsigned int v4 = 0; + + v1 = + fwldr_read_le4(&l2_buf[4]), + v2 = + fwldr_read_le4(&l2_buf[8]), + v3 = + fwldr_read_le4(&l2_buf[12]), + v4 = + fwldr_read_le4(&l2_buf[16]), + + len = snprintf(str_curr, + buf_len, + "\tUser: 0x%08X: 0x%08X: 0x%08X: 0x%08X\n", + v1, + v2, + v3, + v4); + } + + rec_len = 20; + break; + + default: + if (str_curr) { + len = snprintf(str_curr, + buf_len, + "\tUnknown: %08X (%d bytes remain)\n", + cmd, + l2_len); + } + break; + } + + if ((rec_len == 0) || (res == FWLDR_FAIL)) + break; + + if (str_curr) + str_curr += len; + + l2_buf += rec_len; + l2_len -= rec_len; + } while (l2_len > 0); + + snprintf(info_buf, sizeof(info_buf), + "%-12s: %d bytes %s\n", "Config", + (unsigned int)(l2_buf-l2_blk), + ((l2_len != 0) ? ": ERROR!!" : "")); + break; + + case FWLDR_L1_CMD_FILENAME: + if (!l2_blk) { + fwldr_dbg_err("Invalid params to Filename\n"); + res = FWLDR_FAIL; + quit = 1; + break; + } + + snprintf(info_buf, + sizeof(info_buf), + "%-12s: %s\n", + "FileName", + l2_blk + 8); + break; + + default: + /* Not expected */ + snprintf(info_buf, + sizeof(info_buf), + "%-12s\n", + "Unknown"); + break; + } + + kfree(l2_blk); + + if (cfg_str) { + fwldr_dbg_info("0x%08X: %s%s", + file_offset, + info_buf, + cfg_str); + kfree(cfg_str); + cfg_str = NULL; + } else { + fwldr_dbg_info("0x%08X: %s ", + file_offset, + info_buf); + } + + if (l1_rec.nxt == FWLDR_L1_TERMINATE) { + unsigned int overlay_off = 0; + + /* There is the possibility of further overlays. + * Without additional information, the best guess is + * that they start immediately after the L2 data of the + * last record. + */ + l1_rec.l2_offset = fwldr_read_le4(&l1_buf[l1_rec.len - + FWLDR_L1_L2OFF_OFF]); + l1_rec.l2_len = fwldr_read_le2(&l1_buf[l1_rec.len - + FWLDR_L1_L2LEN_OFF]); + + overlay_off = fwldr_virt_to_linear_off(page_size, + l1_rec.l2_offset); + overlay_off += l1_rec.l2_len; + + /* Round to next 32-bit boundary */ + overlay_off += 3; + overlay_off &= ~3; + + fwldr_dbg_info("\n"); + fwldr_dbg_info("Possible next L1 Record 0x%08X\n", + overlay_off); + + quit = 1; + } else { + /* Move on to next L1 record */ + prev_offset = file_offset; + file_offset = fwldr_virt_to_linear_off(page_size, + l1_rec.nxt); + + if (file_offset <= prev_offset) { + /* Possibly incorrect page size specified. + * Stopping + */ + fwldr_dbg_err("Out of sequence record found\n"); + quit = 1; + } + } + } + + return res; +} + +static int fwldr_wait_for_completion(void) +{ + int result = 1; + unsigned int i = 0; + + /* + * We want to wait until a magic value appears in the specified + * location. That should only happen once the RPU is ready. + */ + while (!check_exec_signature(UCCP_THRD_EXEC_SIG) && i++ < 1000) + mdelay(10); + + if (i == 1001) + result = 0; + + return result; +} diff --git a/drivers/soc/img/connectivity/img-fwldr.h b/drivers/soc/img/connectivity/img-fwldr.h new file mode 100644 index 00000000000000..43b8a6961f1590 --- /dev/null +++ b/drivers/soc/img/connectivity/img-fwldr.h @@ -0,0 +1,10 @@ +#ifndef __IMG_FWLDR +#define __IMG_FWLDR 1 + +void fwldr_init(unsigned char *core_addr, unsigned char *gram_addr, + unsigned char *gram_b4_addr); +void fwldr_stop_thrd(unsigned int tno); +void fwldr_soft_reset(unsigned int tno); +int fwldr_load_fw(const unsigned char *fw_data); + +#endif diff --git a/drivers/soc/img/connectivity/img-hostport.c b/drivers/soc/img/connectivity/img-hostport.c new file mode 100644 index 00000000000000..0c8c496884e739 --- /dev/null +++ b/drivers/soc/img/connectivity/img-hostport.c @@ -0,0 +1,484 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. + *** + *** 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-hostport-main.c + *** + *** File Description: + *** This file contains the implementation of the IMG low level + *** shared memory based transport. + *** + ****************************************************************************** + *END**************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "img-hostport.h" + +typedef void (*gen_handler)(void *); + +static struct img_hostport *module; +static const char *hal_name = "img-hostport"; +#define dbg(format, ...) pr_debug("%s: " format, hal_name, ## __VA_ARGS__) +#define err(format, ...) pr_err("%s: " format, hal_name, ## __VA_ARGS__) +#define dbgn(format, ...) dbg(format "\n", ## __VA_ARGS__) +#define errn(format, ...) err(format "\n", ## __VA_ARGS__) +#define diagerrn(format, ...) \ + errn("%s : %d : " format, __func__, __LINE__, ## __VA_ARGS__) +#define diagdbgn(format, ...) \ + dbgn("%s : %d : " format, __func__, __LINE__, ## __VA_ARGS__) + +#define COMMON_HOST_ID 0 +#define CALLEE_MASK 0x000000f0 +#define CALLEE_SHIFT 4 +#define CALLER_MASK 0x0000000f +#define USERMSG_MASK 0x00ffff00 +#define USERMSG_SHIFT 8 +#define CALLEE(reg) ((reg & CALLEE_MASK) >> CALLEE_SHIFT) +#define CALLER(reg) (reg & CALLER_MASK) +#define USERMSG(reg) ((reg & USERMSG_MASK) >> USERMSG_SHIFT) +#define IS_BUSY(reg) (ioread32(reg) & 0x80000000) +#define mtx_int_en_WIDTH 4 + +DEFINE_SPINLOCK(host_to_uccp_core_lock); + +/* + * Forward declarations + */ +static void notify_common(u16 user_data, int user_id, gen_handler poke_ready, + void *poke_ready_arg); + +/* + * Public interface procs + */ + +void img_transport_notify(u16 user_data, int user_id) +{ + img_transport_notify_callback(user_data, user_id, NULL, NULL); +} +EXPORT_SYMBOL(img_transport_notify); + +int __must_check img_transport_notify_timeout(u16 user_data, + int user_id, + long jiffies_timeout) +{ + return img_transport_notify_callback_timeout(user_data, user_id, + jiffies_timeout, NULL, NULL); +} +EXPORT_SYMBOL(img_transport_notify_timeout); + +void img_transport_notify_callback(u16 user_data, + int user_id, + gen_handler poke_ready, + void *poke_ready_arg) +{ + unsigned long flags; + spin_lock_irqsave(&host_to_uccp_core_lock, flags); + while(IS_BUSY(H2C_CMD_ADDR(module->vbase))) + continue; + notify_common(user_data, user_id, poke_ready, poke_ready_arg); + spin_unlock_irqrestore(&host_to_uccp_core_lock, flags); +} +EXPORT_SYMBOL(img_transport_notify_callback); + +int __must_check img_transport_notify_callback_timeout(u16 user_data, + int user_id, + long jiffies_timeout, + gen_handler poke_ready, + void *poke_ready_arg) +{ + unsigned long start_time = jiffies, flags; + spin_lock_irqsave(&host_to_uccp_core_lock, flags); + while(IS_BUSY(H2C_CMD_ADDR(module->vbase))) { + if (time_after_eq(start_time + jiffies_timeout, jiffies)) { + spin_unlock_irqrestore(&host_to_uccp_core_lock, flags); + return -ETIME; + } + } + + notify_common(user_data, user_id, poke_ready, poke_ready_arg); + spin_unlock_irqrestore(&host_to_uccp_core_lock, flags); + return 0; +} +EXPORT_SYMBOL(img_transport_notify_callback_timeout); + +int img_transport_register_callback( + img_transport_handler poke, + unsigned int client_id) +{ + /* + * Make sure that the slot is free, i.e. null + */ + if (0 == client_id || client_id > MAX_ENDPOINT_ID || module->endpoints.f[client_id]) + return -EBADSLT; + + spin_lock(module->endpoints.in_use + client_id); + module->endpoints.f[client_id] = poke; + spin_unlock(module->endpoints.in_use + client_id); + + return 0; +} +EXPORT_SYMBOL(img_transport_register_callback); + +int img_transport_remove_callback(unsigned int client_id) +{ + if (client_id > MAX_ENDPOINT_ID || !module->endpoints.f[client_id]) + return -EBADSLT; + + spin_lock(module->endpoints.in_use + client_id); + module->endpoints.f[client_id] = NULL; + spin_unlock(module->endpoints.in_use + client_id); + + return 0; +} +EXPORT_SYMBOL(img_transport_remove_callback); + +/* + * Private procs + */ + +static u8 id_to_field(int id) +{ + id &= 0xF; + return (id << 4) | id; +} + +static void notify_common(u16 user_data, int user_id, gen_handler poke_ready, + void *poke_ready_arg) +{ + trace_printk("img-hostport: snd -- %d:%d:%02X\n", user_id, user_id, user_data); + if (poke_ready) + poke_ready(poke_ready_arg); + iowrite32(0x87 << 24 | user_data << 8 | id_to_field(user_id), + (void __iomem *)H2C_CMD_ADDR(module->vbase)); +} + +static irqreturn_t hal_irq_handler(int irq, void *p) +{ + /* p is module here! */ + unsigned long flags; + unsigned int reg_value; + unsigned int value, caller_id, callee_id, user_message, first_bit; + img_transport_handler handler; + spinlock_t *handler_in_use; + + reg_value = + readl((void __iomem *)(C2H_CMD_ADDR(module->vbase))); + + /* TODO: need to change that to support platforms other that 32 bit */ + first_bit = (reg_value & (1 << 31)) >> 31; + if (0 == first_bit) { + trace_printk("img-hostport: unexpected spurious interrupt detected (0x%08X)!\n", + reg_value); + goto exit; + } + + callee_id = CALLEE(reg_value); + caller_id = CALLER(reg_value); + user_message = USERMSG(reg_value); + trace_printk("img-hostport: rcv -%c %d:%d:%02X\n", first_bit ? '-' : '*', callee_id, caller_id, user_message); + + /* + * callee_id is tainted, therefore must be checked. + */ + if (callee_id > MAX_ENDPOINT_ID) { + trace_printk("img-hostport: endpoint with id = %u doesn't exist\n", callee_id); + goto deassert; + } + + handler = module->endpoints.f[callee_id]; + handler_in_use = module->endpoints.in_use + callee_id; + if (NULL == handler) { + trace_printk("img-hostport: endpoint with id = %u not registered\n", callee_id); + goto deassert; + } + spin_lock_irqsave(handler_in_use, flags); + handler((u16)user_message); + spin_unlock_irqrestore(handler_in_use, flags); + +deassert: + /* Clear the uccp interrupt */ + value = 0; + value |= BIT(C_INT_CLR_SHIFT); + writel(value, (void __iomem *)(H2C_ACK_ADDR(module->vbase))); + + /* + * Send ACK to the RPU + */ + img_transport_notify(0, COMMON_HOST_ID); +exit: + return IRQ_HANDLED; +} + +static void img_hostport_irq_on(void) +{ + unsigned int value = 0; + + /* + * Both mtx_irq and mtx_int must be asserted in order to + * receive inerrupts on the host + */ + + iowrite32(0x80000000, H2C_ACK_ADDR(module->vbase)); + iowrite32(0x80000000, C2H_ACK_ADDR(module->vbase)); + + value = readl(module->vmtx_irq_en); + value |= BIT(C_IRQ_EN_SHIFT); + writel(value, module->vmtx_irq_en); + + value = 0; + value |= BIT(C_INT_EN_SHIFT); + writel(value, module->vmtx_int_en); +} + +static void img_hostport_irq_off(void) +{ + unsigned int value = 0; + + value = 0; + value &= ~(BIT(C_INT_EN_SHIFT)); + writel(value, module->vmtx_int_en); + + value = readl(module->vmtx_irq_en); + value &= ~(BIT(C_IRQ_EN_SHIFT)); + writel(value, module->vmtx_irq_en); +} + +static int img_hostport_pltfr_irqregist(int irq_line) +{ + dbg("requesting interrupt line %d\n", irq_line); + + return request_irq(irq_line, hal_irq_handler, 0, hal_name, module); +} + +static int img_hostport_pltfr_irqregist_rollback(int irq_line) +{ + dbg("releasing interrupt line %d\n", irq_line); + + free_irq(irq_line, module); + + return 0; +} + +static int img_hostport_pltfr_dtsetup(struct platform_device *pdev) +{ + int irq_or_error; + /* Get resources from platform device */ + irq_or_error = platform_get_irq(pdev, 0); + if (irq_or_error < 0) { /* it's an error */ + err("cannot find IRQ resource\n"); + return irq_or_error; /* it's now error code */ + } + module->irq_line = irq_or_error; /* it's now a valid IRQ line */ + + module->base = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "Hostport control block"); + if (IS_ERR_OR_NULL(module->base)) { + errn("hostport base address not found"); + return PTR_ERR(module->base); + } + + module->mtx_int_en = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "Hostport mtx_int enable"); + if (IS_ERR_OR_NULL(module->mtx_int_en)) { + errn("mtx_int enable address not found"); + return PTR_ERR(module->base); + } + + module->mtx_irq_en = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "Hostport mtx_irq enable"); + if (IS_ERR_OR_NULL(module->mtx_irq_en)) { + errn("mtx_irq enable address not found"); + return PTR_ERR(module->mtx_irq_en); + } + + return 0; +} + +static void img_hostport_pltfr_dtsetup_rollback(void) +{ + module->base = 0; + module->mtx_int_en = 0; + module->irq_line = 0; +} + +static int img_hostport_pltfr_memmap(struct platform_device *d) +{ + /* Map RPU sbus */ + module->vbase = devm_ioremap_resource(&d->dev, module->base); + if (NULL == module->vbase) { + errn("failed to remap Hostport control block"); + return -ENOMEM; + } + + module->vmtx_int_en = devm_ioremap_resource(&d->dev, + module->mtx_int_en); + if (module->vmtx_int_en == 0) { + errn("failed to remap mtx_int enable register"); + return -ENOMEM; + } + + module->vmtx_irq_en = devm_ioremap_resource(&d->dev, + module->mtx_irq_en); + if (module->vmtx_irq_en == 0) { + errn("faield to remap mtx_irq enable register"); + return -ENOMEM; + } + + return 0; +} + +static void img_hostport_pltfr_memmap_rollback(void) +{ + module->vmtx_int_en = module->vbase = 0; +} + +static int img_hostport_pltfr_memsetup(void) +{ + int i; + + module = kzalloc(sizeof(struct img_hostport), GFP_KERNEL); + + if (IS_ERR_OR_NULL(module)) + return PTR_ERR(module); + + for (i = 0; i < MAX_ENDPOINTS; i++) + spin_lock_init(module->endpoints.in_use + i); + return 0; +} + +static void img_hostport_pltfr_memsetup_rollback(void) +{ + kfree(module); +} + +static int img_hostport_pltfr_probe(struct platform_device *pdev) +{ + int result = 0; + + result = img_hostport_pltfr_memsetup(); + if (result) { + err("Memory setup failed"); + goto memsetup_failed; + } + + result = img_hostport_pltfr_dtsetup(pdev); + if (result) { + err("DT setup failed"); + goto dtsetup_failed; + } + + result = img_hostport_pltfr_memmap(pdev); + if (result) { + errn("Memory remapping failed"); + goto memmap_failed; + } + + /* Register irq handler, irq_line comes from dtsetup */ + result = img_hostport_pltfr_irqregist(module->irq_line); + if (result) { + err("Unable to register IRQ handler\n"); + goto irqsetup_failed; + } + + dbg("activating hostport interrupt"); + img_hostport_irq_on(); + + dbg("releasing C2H register"); + img_transport_notify(0, COMMON_HOST_ID); + + dbg("hostport driver registration completed"); + return result; + +irqsetup_failed: + img_hostport_pltfr_memmap_rollback(); +memmap_failed: + img_hostport_pltfr_dtsetup_rollback(); +dtsetup_failed: + img_hostport_pltfr_memsetup_rollback(); +memsetup_failed: + return result; +} + +static int img_hostport_pltfr_remove(struct platform_device *pdev) +{ + img_hostport_irq_off(); + img_hostport_pltfr_irqregist_rollback(module->irq_line); + img_hostport_pltfr_memmap_rollback(); + img_hostport_pltfr_dtsetup_rollback(); + img_hostport_pltfr_memsetup_rollback(); + + return 0; +} + +static const struct of_device_id img_hostport_dt_ids[] = { + { .compatible = "img,pistachio-uccp-hostport" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, img_hostport_dt_ids); + +struct platform_driver img_uccp_driver = { + .probe = img_hostport_pltfr_probe, + .remove = img_hostport_pltfr_remove, + .driver = { + .name = "uccp420", + .of_match_table = of_match_ptr(img_hostport_dt_ids), + }, +}; + +static void __exit img_hostport_leave(void) +{ + platform_driver_unregister(&img_uccp_driver); +} + +static int __init img_hostport_entry(void) +{ + /* + * The following line is here purely to make sure that the current + * module depends on img-connectivity when it's loaded as a module. + */ + img_connectivity_version(); + + return platform_driver_probe(&img_uccp_driver, + img_hostport_pltfr_probe); +} + +module_init(img_hostport_entry); +module_exit(img_hostport_leave); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bartosz Flis "); +MODULE_DESCRIPTION("Imagination Technologies Host Port driver - www.imgtec.com"); diff --git a/drivers/soc/img/connectivity/img-hostport.h b/drivers/soc/img/connectivity/img-hostport.h new file mode 100644 index 00000000000000..2b8f724a714558 --- /dev/null +++ b/drivers/soc/img/connectivity/img-hostport.h @@ -0,0 +1,83 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-hostport-main.h + *** + *** File Description: + *** This file contains private definitions specific to Host Port comms + *** + ****************************************************************************** + *END**************************************************************************/ + +#ifndef _IMGBT_HOSTPORT_H_ +#define _IMGBT_HOSTPORT_H_ + +/* Include files */ +#include + +#include + +#define MAX_ENDPOINTS 3 +#define MAX_ENDPOINT_ID (MAX_ENDPOINTS - 1) + +struct img_hostport_endpoints { + img_transport_handler f[MAX_ENDPOINTS]; + spinlock_t in_use[MAX_ENDPOINTS]; +}; + +struct img_hostport { + struct img_hostport_endpoints endpoints; + /* RPU system bus remapped addresses */ + void __iomem *vbase; + void __iomem *vmtx_int_en; + void __iomem *vmtx_irq_en; + /* DTS entries */ + struct resource *base; + struct resource *mtx_int_en; + struct resource *mtx_irq_en; + unsigned int irq_line; +}; + +/* Register H2C_CMD */ +#define H2C_CMD 0x0 +#define H2C_CMD_ADDR(base) ((base) + H2C_CMD) +#define C_HOST_INT_SHIFT 31 + +/* Register C2H_CMD */ +#define C2H_CMD 0x4 +#define C2H_CMD_ADDR(base) ((base) + C2H_CMD) + +/* Register H2C_ACK */ +#define H2C_ACK 0x8 +#define H2C_ACK_ADDR(base) ((base) + H2C_ACK) +#define C_INT_CLR_SHIFT 31 + +/* Register C2H_ACK */ +#define C2H_ACK 0xC +#define C2H_ACK_ADDR(base) ((base) + C2H_ACK) + +/* Register C_INT_ENABLE */ +#define C_INT_EN_SHIFT 31 +#define C_IRQ_EN_SHIFT 15 + +#endif + +/* EOF */ diff --git a/drivers/soc/img/connectivity/mem-region.c b/drivers/soc/img/connectivity/mem-region.c new file mode 100644 index 00000000000000..f166acbaf7f67b --- /dev/null +++ b/drivers/soc/img/connectivity/mem-region.c @@ -0,0 +1,79 @@ +/* + * File Name : mem-region.c + * + * File Description : Memory regions manipulation - implementation + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "mem-region.h" + +static bool any(struct mem_region haystack[], unsigned int n, + mr_relation_fn predicate, + struct mem_region *needle) +{ + int i; + for (i = 0; i < n; i++) { + if (predicate(haystack + i, needle)) + return true; + } + return false; +} + +static bool addr_within(struct mem_region *r, u32 addr) +{ + return addr >= r->from && addr <= r->to; +} + +static bool addr_beyond(struct mem_region *r, u32 addr) +{ + return addr > r->to; +} + +static bool addr_before(struct mem_region *r, u32 addr) +{ + return addr < r->from; +} + +bool legal(struct mem_region *r) +{ + return r->from <= r->to; +} + +bool within(struct mem_region *legal, struct mem_region *r) +{ + return (r->from >= legal->from) && (r->to <= legal->to); +} + +bool overlaps(struct mem_region *legal, struct mem_region *r) +{ + return (addr_within(legal, r->from) && addr_beyond(legal, r->to)) || + (addr_before(legal, r->from) && addr_within(legal, r->to)); +} + +bool within_any(struct mem_region rs[], unsigned int n, struct mem_region *r) +{ + return any(rs, n, within, r); +} + +bool overlaps_any(struct mem_region rs[], unsigned int n, struct mem_region *r) +{ + return any(rs, n, overlaps, r); +} + diff --git a/drivers/soc/img/connectivity/mem-region.h b/drivers/soc/img/connectivity/mem-region.h new file mode 100644 index 00000000000000..756c75aed10b9c --- /dev/null +++ b/drivers/soc/img/connectivity/mem-region.h @@ -0,0 +1,43 @@ +/* + * File Name : mem_region.h + * + * File Description: Memory regions manipulation - interface declaration + * + * Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef _MEM_REGION_H_ +#define _MEM_REGION_H_ + +#include + +struct mem_region { + u32 from; + u32 to; +}; + +typedef bool (*mr_relation_fn)(struct mem_region *, struct mem_region *); + +bool legal(struct mem_region *r); +bool within(struct mem_region *legal, struct mem_region *r); +bool overlaps(struct mem_region *legal, struct mem_region *r); +bool within_any(struct mem_region rs[], unsigned int n, struct mem_region *r); +bool overlaps_any(struct mem_region rs[], unsigned int n, struct mem_region *r); + +#endif diff --git a/drivers/soc/img/fuse/Makefile b/drivers/soc/img/fuse/Makefile new file mode 100644 index 00000000000000..e873dc0c9a8447 --- /dev/null +++ b/drivers/soc/img/fuse/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_IMG_EFUSE) += img-efuse.o diff --git a/drivers/soc/img/fuse/img-efuse.c b/drivers/soc/img/fuse/img-efuse.c new file mode 100644 index 00000000000000..2155d5af2f5bb4 --- /dev/null +++ b/drivers/soc/img/fuse/img-efuse.c @@ -0,0 +1,201 @@ +/* + * Imagination Technologies Generic eFuse driver + * + * Copyright (c) 2015 Imagination Technologies Ltd. + * + * 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. + * + * Based on drivers/misc/eeprom/sunxi_sid.c Copyright (c) 2013 Oliver Schinagl + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct img_efuse_soc_data { + unsigned int size; +}; + +struct img_efuse { + void __iomem *base; + const struct img_efuse_soc_data *data; + struct clk *osc_clk; + struct clk *sys_clk; +}; + +static struct img_efuse *efuse_dev; +static bool efuse_initialized; + +static u8 img_efuse_read_byte(unsigned int offset) +{ + u32 data; + + if (offset >= efuse_dev->data->size) + return 0; + + data = readl(efuse_dev->base + round_down(offset, 4)); + data = (data >> ((offset % 4) * 8)) && 0xff; + + return data; +} + +int img_efuse_readl(unsigned int offset, u32 *value) +{ + if (!efuse_initialized) + return -ENODEV; + + if ((offset > (efuse_dev->data->size - 4)) || (offset % 4 != 0)) + return -EINVAL; + + *value = readl(efuse_dev->base + offset); + + return 0; +} +EXPORT_SYMBOL_GPL(img_efuse_readl); + +static ssize_t img_efuse_read(struct file *fd, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t pos, size_t size) +{ + int i; + struct platform_device *pdev; + struct img_efuse *efuse; + + pdev = to_platform_device(kobj_to_dev(kobj)); + efuse = platform_get_drvdata(pdev); + + if (pos < 0 || pos >= efuse->data->size) + return 0; + + if (size > efuse->data->size - pos) + size = efuse->data->size - pos; + + for (i = 0; i < size; i++) + buf[i] = img_efuse_read_byte(pos + i); + + return i; +} + +static struct bin_attribute img_efuse_bin_attr = { + .attr = { .name = "efuse", .mode = S_IRUGO, }, + .read = img_efuse_read, +}; + +static const struct img_efuse_soc_data pistachio_efuse = { + .size = 16, +}; + +static const struct of_device_id img_efuse_of_match[] = { + { .compatible = "img,pistachio-efuse", .data = &pistachio_efuse }, + {}, +}; +MODULE_DEVICE_TABLE(of, img_efuse_of_match); + +static int img_efuse_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + const struct of_device_id *of_dev_id; + + efuse_dev = devm_kzalloc(&pdev->dev, sizeof(struct img_efuse *), + GFP_KERNEL); + if (!efuse_dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + efuse_dev->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(efuse_dev->base)) + return PTR_ERR(efuse_dev->base); + + of_dev_id = of_match_device(img_efuse_of_match, &pdev->dev); + if (!of_dev_id) + return -ENODEV; + efuse_dev->data = of_dev_id->data; + + efuse_dev->sys_clk = devm_clk_get(&pdev->dev, "sys"); + if (!IS_ERR(efuse_dev->sys_clk)) { + ret = clk_prepare_enable(efuse_dev->sys_clk); + if (ret < 0) { + dev_err(&pdev->dev, "could not enable system clock\n"); + return ret; + } + } + + efuse_dev->osc_clk = devm_clk_get(&pdev->dev, "osc"); + if (!IS_ERR(efuse_dev->osc_clk)) { + ret = clk_prepare_enable(efuse_dev->osc_clk); + if (ret < 0) { + dev_err(&pdev->dev, + "could not enable oscillator clock\n"); + goto disable_sysclk; + } + } + + platform_set_drvdata(pdev, efuse_dev); + + img_efuse_bin_attr.size = efuse_dev->data->size; + if (device_create_bin_file(&pdev->dev, &img_efuse_bin_attr)) { + ret = -ENODEV; + goto disable_oscclk; + } + + efuse_initialized = true; + + return 0; + +disable_oscclk: + if (!IS_ERR(efuse_dev->osc_clk)) + clk_disable_unprepare(efuse_dev->osc_clk); +disable_sysclk: + if (!IS_ERR(efuse_dev->sys_clk)) + clk_disable_unprepare(efuse_dev->sys_clk); + + return ret; +} + +static int img_efuse_remove(struct platform_device *pdev) +{ + struct img_efuse *efuse = platform_get_drvdata(pdev); + + efuse_initialized = false; + device_remove_bin_file(&pdev->dev, &img_efuse_bin_attr); + + if (!IS_ERR(efuse->osc_clk)) + clk_disable_unprepare(efuse->osc_clk); + + if (!IS_ERR(efuse->sys_clk)) + clk_disable_unprepare(efuse->sys_clk); + + return 0; +} + +static struct platform_driver img_efuse_driver = { + .probe = img_efuse_probe, + .remove = img_efuse_remove, + .driver = { + .name = "img-efuse", + .of_match_table = img_efuse_of_match, + }, +}; +module_platform_driver(img_efuse_driver); + +MODULE_AUTHOR("Arul Ramasamy"); +MODULE_AUTHOR("Jude Abraham"); +MODULE_DESCRIPTION("Imagination Technologies Generic eFuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c index 823cbc92d1e754..37b0d369c4244f 100644 --- a/drivers/spi/spi-img-spfi.c +++ b/drivers/spi/spi-img-spfi.c @@ -40,7 +40,8 @@ #define SPFI_CONTROL_SOFT_RESET BIT(11) #define SPFI_CONTROL_SEND_DMA BIT(10) #define SPFI_CONTROL_GET_DMA BIT(9) -#define SPFI_CONTROL_SE BIT(8) +#define SPFI_CONTROL_SE BIT(8) +#define SPFI_CONTROL_TX_RX BIT(1) #define SPFI_CONTROL_TMODE_SHIFT 5 #define SPFI_CONTROL_TMODE_MASK 0x7 #define SPFI_CONTROL_TMODE_SINGLE 0 @@ -51,6 +52,10 @@ #define SPFI_TRANSACTION 0x18 #define SPFI_TRANSACTION_TSIZE_SHIFT 16 #define SPFI_TRANSACTION_TSIZE_MASK 0xffff +#define SPFI_TRANSACTION_CMD_SHIFT 13 +#define SPFI_TRANSACTION_CMD_MASK 0x7 +#define SPFI_TRANSACTION_ADDR_SHIFT 10 +#define SPFI_TRANSACTION_ADDR_MASK 0x7 #define SPFI_PORT_STATE 0x1c #define SPFI_PORT_STATE_DEV_SEL_SHIFT 20 @@ -87,6 +92,7 @@ */ #define SPFI_32BIT_FIFO_SIZE 64 #define SPFI_8BIT_FIFO_SIZE 16 +#define SPFI_DATA_REQUEST_MAX_SIZE 8 struct img_spfi { struct device *dev; @@ -103,6 +109,8 @@ struct img_spfi { struct dma_chan *tx_ch; bool tx_dma_busy; bool rx_dma_busy; + + bool complete; }; struct img_spfi_device_data { @@ -123,9 +131,11 @@ static inline void spfi_start(struct img_spfi *spfi) { u32 val; - val = spfi_readl(spfi, SPFI_CONTROL); - val |= SPFI_CONTROL_SPFI_EN; - spfi_writel(spfi, val, SPFI_CONTROL); + if (spfi->complete) { + val = spfi_readl(spfi, SPFI_CONTROL); + val |= SPFI_CONTROL_SPFI_EN; + spfi_writel(spfi, val, SPFI_CONTROL); + } } static inline void spfi_reset(struct img_spfi *spfi) @@ -138,12 +148,21 @@ static int spfi_wait_all_done(struct img_spfi *spfi) { unsigned long timeout = jiffies + msecs_to_jiffies(50); + if (!(spfi->complete)) + return 0; + while (time_before(jiffies, timeout)) { u32 status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); if (status & SPFI_INTERRUPT_ALLDONETRIG) { spfi_writel(spfi, SPFI_INTERRUPT_ALLDONETRIG, SPFI_INTERRUPT_CLEAR); + /* + * Disable SPFI for it not to interfere with + * pending transactions + */ + spfi_writel(spfi, spfi_readl(spfi, SPFI_CONTROL) + & ~SPFI_CONTROL_SPFI_EN, SPFI_CONTROL); return 0; } cpu_relax(); @@ -327,12 +346,11 @@ static int img_spfi_start_dma(struct spi_master *master, if (xfer->len % 4 == 0) { rxconf.src_addr = spfi->phys + SPFI_RX_32BIT_VALID_DATA; rxconf.src_addr_width = 4; - rxconf.src_maxburst = 4; } else { rxconf.src_addr = spfi->phys + SPFI_RX_8BIT_VALID_DATA; rxconf.src_addr_width = 1; - rxconf.src_maxburst = 4; } + rxconf.src_maxburst = 8; dmaengine_slave_config(spfi->rx_ch, &rxconf); rxdesc = dmaengine_prep_slave_sg(spfi->rx_ch, xfer->rx_sg.sgl, @@ -351,12 +369,11 @@ static int img_spfi_start_dma(struct spi_master *master, if (xfer->len % 4 == 0) { txconf.dst_addr = spfi->phys + SPFI_TX_32BIT_VALID_DATA; txconf.dst_addr_width = 4; - txconf.dst_maxburst = 4; } else { txconf.dst_addr = spfi->phys + SPFI_TX_8BIT_VALID_DATA; txconf.dst_addr_width = 1; - txconf.dst_maxburst = 4; } + txconf.dst_maxburst = 4; dmaengine_slave_config(spfi->tx_ch, &txconf); txdesc = dmaengine_prep_slave_sg(spfi->tx_ch, xfer->tx_sg.sgl, @@ -418,15 +435,23 @@ static int img_spfi_prepare(struct spi_master *master, struct spi_message *msg) struct img_spfi *spfi = spi_master_get_devdata(master); u32 val; + /* + * The chip select line is controlled externally so + * we can use the CS0 configuration for all devices + */ val = spfi_readl(spfi, SPFI_PORT_STATE); + + /* 0 for device selection */ + val &= ~(SPFI_PORT_STATE_DEV_SEL_MASK << + SPFI_PORT_STATE_DEV_SEL_SHIFT); if (msg->spi->mode & SPI_CPHA) - val |= SPFI_PORT_STATE_CK_PHASE(msg->spi->chip_select); + val |= SPFI_PORT_STATE_CK_PHASE(0); else - val &= ~SPFI_PORT_STATE_CK_PHASE(msg->spi->chip_select); + val &= ~SPFI_PORT_STATE_CK_PHASE(0); if (msg->spi->mode & SPI_CPOL) - val |= SPFI_PORT_STATE_CK_POL(msg->spi->chip_select); + val |= SPFI_PORT_STATE_CK_POL(0); else - val &= ~SPFI_PORT_STATE_CK_POL(msg->spi->chip_select); + val &= ~SPFI_PORT_STATE_CK_POL(0); spfi_writel(spfi, val, SPFI_PORT_STATE); return 0; @@ -494,35 +519,94 @@ static void img_spfi_config(struct spi_master *master, struct spi_device *spi, struct spi_transfer *xfer) { struct img_spfi *spfi = spi_master_get_devdata(spi->master); - u32 val, div; + u32 val, div, transact; + bool is_pending; + + /* + * For read or write transfers of less than 8 bytes (cmd = 1 byte, + * addr up to 7 bytes), SPFI will be configured, but not enabled + * (unless it is the last transfer in the queue).The transfer will + * be enabled by the subsequent transfer. + * A pending transfer is determined by the content of the + * transaction register: if command part is set and tsize + * is not + */ + transact = spfi_readl(spfi, SPFI_TRANSACTION); + is_pending = ((transact >> SPFI_TRANSACTION_CMD_SHIFT) & + SPFI_TRANSACTION_CMD_MASK) && + (!((transact >> SPFI_TRANSACTION_TSIZE_SHIFT) & + SPFI_TRANSACTION_TSIZE_MASK)); + + /* If there are no pending transactions it's OK to soft reset */ + if (!is_pending) { + /* Start the transaction from a known (reset) state */ + spfi_reset(spfi); + } /* + * Before anything else, set up parameters. * output = spfi_clk * (BITCLK / 512), where BITCLK must be a * power of 2 up to 128 */ div = DIV_ROUND_UP(clk_get_rate(spfi->spfi_clk), xfer->speed_hz); div = clamp(512 / (1 << get_count_order(div)), 1, 128); - val = spfi_readl(spfi, SPFI_DEVICE_PARAMETER(spi->chip_select)); + /* + * The chip select line is controlled externally so + * we can use the CS0 parameters for all devices + */ + val = spfi_readl(spfi, SPFI_DEVICE_PARAMETER(0)); val &= ~(SPFI_DEVICE_PARAMETER_BITCLK_MASK << SPFI_DEVICE_PARAMETER_BITCLK_SHIFT); val |= div << SPFI_DEVICE_PARAMETER_BITCLK_SHIFT; - spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(spi->chip_select)); - - spfi_writel(spfi, xfer->len << SPFI_TRANSACTION_TSIZE_SHIFT, - SPFI_TRANSACTION); + spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(0)); + + if (!list_is_last(&xfer->transfer_list, &master->cur_msg->transfers) && + /* + * For duplex mode (both the tx and rx buffers are !NULL) the + * CMD, ADDR, and DUMMY byte parts of the transaction register + * should always be 0 and therefore the pending transfer + * technique cannot be used. + */ + (xfer->tx_buf) && (!xfer->rx_buf) && + (xfer->len <= SPFI_DATA_REQUEST_MAX_SIZE) && !is_pending) { + transact = (1 & SPFI_TRANSACTION_CMD_MASK) << + SPFI_TRANSACTION_CMD_SHIFT; + transact |= ((xfer->len - 1) & SPFI_TRANSACTION_ADDR_MASK) << + SPFI_TRANSACTION_ADDR_SHIFT; + spfi->complete = false; + } else { + spfi->complete = true; + if (is_pending) { + /* Keep setup from pending transfer */ + transact |= ((xfer->len & SPFI_TRANSACTION_TSIZE_MASK) << + SPFI_TRANSACTION_TSIZE_SHIFT); + } else { + transact = ((xfer->len & SPFI_TRANSACTION_TSIZE_MASK) << + SPFI_TRANSACTION_TSIZE_SHIFT); + } + } + spfi_writel(spfi, transact, SPFI_TRANSACTION); val = spfi_readl(spfi, SPFI_CONTROL); val &= ~(SPFI_CONTROL_SEND_DMA | SPFI_CONTROL_GET_DMA); - if (xfer->tx_buf) + /* + * We set up send DMA for pending transfers also, as + * those are always send transfers + */ + if ((xfer->tx_buf) || is_pending) val |= SPFI_CONTROL_SEND_DMA; - if (xfer->rx_buf) + if (xfer->tx_buf) + val |= SPFI_CONTROL_TX_RX; + if (xfer->rx_buf) { val |= SPFI_CONTROL_GET_DMA; + val &= ~SPFI_CONTROL_TX_RX; + } val &= ~(SPFI_CONTROL_TMODE_MASK << SPFI_CONTROL_TMODE_SHIFT); - if (xfer->tx_nbits == SPI_NBITS_DUAL && + if (xfer->tx_nbits == SPI_NBITS_DUAL || xfer->rx_nbits == SPI_NBITS_DUAL) val |= SPFI_CONTROL_TMODE_DUAL << SPFI_CONTROL_TMODE_SHIFT; - else if (xfer->tx_nbits == SPI_NBITS_QUAD && + else if (xfer->tx_nbits == SPI_NBITS_QUAD || xfer->rx_nbits == SPI_NBITS_QUAD) val |= SPFI_CONTROL_TMODE_QUAD << SPFI_CONTROL_TMODE_SHIFT; val |= SPFI_CONTROL_SE; diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index d0e7dfc647cf21..5fb06a7aac3867 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -695,6 +695,7 @@ static struct class *spidev_class; static const struct of_device_id spidev_dt_ids[] = { { .compatible = "rohm,dh2228fv" }, { .compatible = "lineartechnology,ltc2488" }, + { .compatible = "img,click-interface" }, {}, }; MODULE_DEVICE_TABLE(of, spidev_dt_ids); diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index a5d319e4aae65d..0e9846c5e9e6eb 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -415,6 +415,7 @@ static int dw8250_probe(struct platform_device *pdev) data->msr_mask_off |= UART_MSR_TERI; } + p->set_termios = dw8250_set_termios; /* Always ask for fixed clock rate from a property. */ device_property_read_u32(p->dev, "clock-frequency", &p->uartclk); diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index edb5305b9d4da7..7f9f968a8e83e1 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1120,9 +1120,13 @@ static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip, struct sc16is7xx_port *s = container_of(chip, struct sc16is7xx_port, gpio); struct uart_port *port = &s->p[0].port; + u8 state = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG); - sc16is7xx_port_update(port, SC16IS7XX_IOSTATE_REG, BIT(offset), - val ? BIT(offset) : 0); + if (val) + state |= BIT(offset); + else + state &= ~BIT(offset); + sc16is7xx_port_write(port, SC16IS7XX_IOSTATE_REG, state); sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset), BIT(offset)); diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index f105bada2fd13b..e7dc5e1e193353 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -389,6 +389,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) return (dwc2_readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; } +static inline int dwc2_is_connected(struct dwc2_hsotg *hsotg) +{ + return (readl(hsotg->regs + HPRT0) & HPRT0_CONNSTS) == 1; +} + /* * Reads HPRT0 in preparation to modify. It keeps the WC bits 0 so that if they * are read as 1, they won't clear when written back. diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 39c1cbf0e75d9a..d4d61afc707028 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -464,6 +464,10 @@ static int __maybe_unused dwc2_suspend(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; + /* PHY clocks needs to be active if device is connected */ + if (dwc2_is_connected(dwc2)) + return 0; + if (dwc2_is_device_mode(dwc2)) dwc2_hsotg_suspend(dwc2); diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index 0edc128561476a..b1082e55b9aace 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -176,7 +176,7 @@ static int do_readpage(struct page *page) out: SetPageUptodate(page); ClearPageError(page); - flush_dcache_page(page); + flush_kernel_dcache_page(page); kunmap(page); return 0; @@ -184,7 +184,7 @@ static int do_readpage(struct page *page) kfree(dn); ClearPageUptodate(page); SetPageError(page); - flush_dcache_page(page); + flush_kernel_dcache_page(page); kunmap(page); return err; } @@ -687,7 +687,7 @@ static int populate_page(struct ubifs_info *c, struct page *page, SetPageUptodate(page); ClearPageError(page); - flush_dcache_page(page); + flush_kernel_dcache_page(page); kunmap(page); *n = nn; return 0; @@ -695,7 +695,7 @@ static int populate_page(struct ubifs_info *c, struct page *page, out_err: ClearPageUptodate(page); SetPageError(page); - flush_dcache_page(page); + flush_kernel_dcache_page(page); kunmap(page); ubifs_err(c, "bad data node (block %u, inode %lu)", page_block, inode->i_ino); @@ -1046,7 +1046,7 @@ static int ubifs_writepage(struct page *page, struct writeback_control *wbc) */ kaddr = kmap_atomic(page); memset(kaddr + len, 0, PAGE_CACHE_SIZE - len); - flush_dcache_page(page); + flush_kernel_dcache_page(page); kunmap_atomic(kaddr); if (i_size > synced_i_size) { diff --git a/include/dt-bindings/clock/pistachio-clk.h b/include/dt-bindings/clock/pistachio-clk.h index 039f83facb6866..bfb915dfe92161 100644 --- a/include/dt-bindings/clock/pistachio-clk.h +++ b/include/dt-bindings/clock/pistachio-clk.h @@ -21,6 +21,7 @@ /* Fixed-factor clocks */ #define CLK_WIFI_DIV4 16 #define CLK_WIFI_DIV8 17 +#define CLK_SDHOST_DIV4 18 /* Gate clocks */ #define CLK_MIPS 32 @@ -104,8 +105,9 @@ #define CLK_SD_HOST_MUX 110 #define CLK_BT_PLL_MUX 111 #define CLK_DEBUG_MUX 112 +#define CLK_RPU_CORE_MUX_1 113 -#define CLK_NR_CLKS 113 +#define CLK_NR_CLKS 114 /* Peripheral gate clocks */ #define PERIPH_CLK_SYS 0 diff --git a/include/dt-bindings/reset/pistachio-resets.h b/include/dt-bindings/reset/pistachio-resets.h new file mode 100644 index 00000000000000..60a189b1faef8a --- /dev/null +++ b/include/dt-bindings/reset/pistachio-resets.h @@ -0,0 +1,36 @@ +/* + * This header provides constants for the reset controller + * present in the Pistachio SoC + */ + +#ifndef _PISTACHIO_RESETS_H +#define _PISTACHIO_RESETS_H + +#define PISTACHIO_RESET_I2C0 0 +#define PISTACHIO_RESET_I2C1 1 +#define PISTACHIO_RESET_I2C2 2 +#define PISTACHIO_RESET_I2C3 3 +#define PISTACHIO_RESET_I2S_IN 4 +#define PISTACHIO_RESET_PRL_OUT 5 +#define PISTACHIO_RESET_SPDIF_OUT 6 +#define PISTACHIO_RESET_SPI 7 +#define PISTACHIO_RESET_PWM_PDM 8 +#define PISTACHIO_RESET_UART0 9 +#define PISTACHIO_RESET_UART1 10 +#define PISTACHIO_RESET_QSPI 11 +#define PISTACHIO_RESET_MDC 12 +#define PISTACHIO_RESET_SDHOST 13 +#define PISTACHIO_RESET_ETHERNET 14 +#define PISTACHIO_RESET_IR 15 +#define PISTACHIO_RESET_HASH 16 +#define PISTACHIO_RESET_TIMER 17 +#define PISTACHIO_RESET_I2S_OUT 18 +#define PISTACHIO_RESET_SPDIF_IN 19 +#define PISTACHIO_RESET_EVT 20 +#define PISTACHIO_RESET_USB_H 21 +#define PISTACHIO_RESET_USB_PR 22 +#define PISTACHIO_RESET_USB_PHY_PR 23 +#define PISTACHIO_RESET_USB_PHY_PON 24 +#define PISTACHIO_RESET_MAX 24 + +#endif diff --git a/include/dt-bindings/sound/pistachio-audio.h b/include/dt-bindings/sound/pistachio-audio.h new file mode 100644 index 00000000000000..77ee75af625c83 --- /dev/null +++ b/include/dt-bindings/sound/pistachio-audio.h @@ -0,0 +1,8 @@ +#ifndef __PISTACHIO_BUB_AUDIO_H +#define __PISTACHIO_BUB_AUDIO_H + +#define PISTACHIO_MCLK_NONE 0 +#define PISTACHIO_MCLK_I2S 1 +#define PISTACHIO_MCLK_DAC 2 + +#endif diff --git a/include/linux/atu_clk.h b/include/linux/atu_clk.h new file mode 100644 index 00000000000000..bcaf138c483c76 --- /dev/null +++ b/include/linux/atu_clk.h @@ -0,0 +1,27 @@ +/* + * Atu Clock Header File + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#ifndef _ATU_CLK_H +#define _ATU_CLK_H + +#include +#include + +struct cyclecounter; +struct clk; + +extern u64 atu_get_current_time(void); +extern int frc_to_atu(u32 frc, u64 *patu, s32 dir); +extern int atu_to_frc(u64 atu, u32 *pfrc, u64 min_nsec); +extern int +atu_cyclecounter_register(struct cyclecounter *pcc, struct clk *clk_atu); +extern int atu_cyclecounter_unregister(struct cyclecounter *pcc); + +#endif /* _ATU_CLK_H */ diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 452c0b0d2f3219..545d754b69b456 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1510,6 +1510,7 @@ struct ieee80211_vht_operation { #define IEEE80211_VHT_CAP_RXSTBC_3 0x00000300 #define IEEE80211_VHT_CAP_RXSTBC_4 0x00000400 #define IEEE80211_VHT_CAP_RXSTBC_MASK 0x00000700 +#define IEEE80211_VHT_CAP_RXSTBC_SHIFT 8 #define IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE 0x00000800 #define IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE 0x00001000 #define IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT 13 diff --git a/include/linux/img_pdm.h b/include/linux/img_pdm.h new file mode 100644 index 00000000000000..4cb2549a9349be --- /dev/null +++ b/include/linux/img_pdm.h @@ -0,0 +1,27 @@ +/** + * Imagination Technologies Pulse Density Modulator driver + * + * Copyright (C) 2014-2015 Imagination Technologies Ltd. + * + * 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 __IMG_PDM_H +#define __IMG_PDM_H + +struct img_pdm_device; + +struct img_pdm_channel { + unsigned int pdm_id; + unsigned long flags; + struct img_pdm_device *pdm_dev; +}; + +void img_pdm_channel_put(struct device *dev); +struct img_pdm_channel *img_pdm_channel_get(struct device *dev); +int img_pdm_channel_enable(struct img_pdm_channel *chan, bool state); +int img_pdm_channel_config(struct img_pdm_channel *chan, unsigned int val); + +#endif /* ifndef _IMG_PDM_H */ diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 5a9d1d4c2487fc..74f04869a13bc4 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -736,6 +736,7 @@ struct nand_chip { #define NAND_MFR_SANDISK 0x45 #define NAND_MFR_INTEL 0x89 #define NAND_MFR_ATO 0x9b +#define NAND_MFR_GIGADEVICE 0xc8 /* The maximum expected count of bytes in the NAND ID sequence */ #define NAND_MAX_ID_LEN 8 @@ -792,6 +793,7 @@ struct nand_chip { * @ecc_step_ds in nand_chip{}, also from the datasheet. * For example, the "4bit ECC for each 512Byte" can be set with * NAND_ECC_INFO(4, 512). + * @ecc.layout: If the device has on-die ECC, it can provide its own ECC layout. * @onfi_timing_mode_default: the default ONFI timing mode entered after a NAND * reset. Should be deduced from timings described * in the datasheet. @@ -815,6 +817,7 @@ struct nand_flash_dev { struct { uint16_t strength_ds; uint16_t step_ds; + struct nand_ecclayout *layout; } ecc; int onfi_timing_mode_default; }; diff --git a/include/linux/mtd/spi-nand.h b/include/linux/mtd/spi-nand.h new file mode 100644 index 00000000000000..9dd2ad62dc7178 --- /dev/null +++ b/include/linux/mtd/spi-nand.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Imagination Technologies 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; version 2 of the License. + */ + +#ifndef __LINUX_MTD_SPI_NAND_H +#define __LINUX_MTD_SPI_NAND_H + +#include +#include + +struct spi_nand { + struct mtd_info mtd; + struct nand_chip nand_chip; + struct device *dev; + const char *name; + + u8 *buf, *data_buf; + size_t buf_size; + off_t buf_start; + unsigned int page_addr; + unsigned int bitflips; + bool ecc; + + int (*reset)(struct spi_nand *snand); + int (*read_id)(struct spi_nand *snand, u8 *buf); + + int (*write_disable)(struct spi_nand *snand); + int (*write_enable)(struct spi_nand *snand); + + int (*read_reg)(struct spi_nand *snand, u8 opcode, u8 *buf); + int (*write_reg)(struct spi_nand *snand, u8 opcode, u8 *buf); + void (*get_ecc_status)(unsigned int status, + unsigned int *corrected, + unsigned int *ecc_errors); + + int (*store_cache)(struct spi_nand *snand, unsigned int page_offset, + size_t length, u8 *write_buf); + int (*write_page)(struct spi_nand *snand, unsigned int page_addr); + int (*load_page)(struct spi_nand *snand, unsigned int page_addr); + int (*read_cache)(struct spi_nand *snand, unsigned int page_offset, + size_t length, u8 *read_buf); + int (*block_erase)(struct spi_nand *snand, unsigned int page_addr); + + void *priv; +}; + +int spi_nand_register(struct spi_nand *snand, struct nand_flash_dev *flash_ids); +void spi_nand_unregister(struct spi_nand *snand); + +#endif diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index bc742dac7d3a10..04dd74fc3bb516 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -104,6 +104,33 @@ enum read_mode { SPI_NOR_QUAD, }; +/** + * struct spi_nor_xfer_cfg - Structure for defining a Serial Flash transfer + * @wren: command for "Write Enable", or 0x00 for not required + * @cmd: command for operation + * @cmd_pins: number of pins to send @cmd (1, 2, 4) + * @addr: address for operation + * @addr_pins: number of pins to send @addr (1, 2, 4) + * @addr_width: number of address bytes + * (3,4, or 0 for address not required) + * @mode: mode data + * @mode_pins: number of pins to send @mode (1, 2, 4) + * @mode_cycles: number of mode cycles (0 for mode not required) + * @dummy_cycles: number of dummy cycles (0 for dummy not required) + */ +struct spi_nor_xfer_cfg { + u8 wren; + u8 cmd; + u8 cmd_pins; + u32 addr; + u8 addr_pins; + u8 addr_width; + u8 mode; + u8 mode_pins; + u8 mode_cycles; + u8 dummy_cycles; +}; + #define SPI_NOR_MAX_CMD_SIZE 8 enum spi_nor_ops { SPI_NOR_OPS_READ = 0, @@ -169,6 +196,10 @@ struct spi_nor { int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops); void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops); + int (*read_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg, + u8 *buf, size_t len, size_t *retlen); + int (*write_xfer)(struct spi_nor *nor, struct spi_nor_xfer_cfg *cfg, + u8 *buf, size_t len, size_t *retlen); int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len); int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len); diff --git a/include/linux/spi/cc2520.h b/include/linux/spi/cc2520.h index 85b8ee67e93795..ad44416d50e5e4 100644 --- a/include/linux/spi/cc2520.h +++ b/include/linux/spi/cc2520.h @@ -21,6 +21,8 @@ struct cc2520_platform_data { int sfd; int reset; int vreg; + unsigned int extclockfreq; + bool registerclk; }; #endif diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 760bc4d5a2cfe8..b038b3b56f7148 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1271,7 +1271,8 @@ enum ieee80211_smps_mode { */ struct ieee80211_conf { u32 flags; - int power_level, dynamic_ps_timeout; + int power_level, dynamic_ps_timeout, dynamic_ps_rx_timeout ; + int dynamic_ps_rx_traffic_timeout; u16 listen_interval; u8 ps_dtim_period; diff --git a/include/soc/img/img-connectivity.h b/include/soc/img/img-connectivity.h new file mode 100644 index 00000000000000..398c466ddcaea3 --- /dev/null +++ b/include/soc/img/img-connectivity.h @@ -0,0 +1,45 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-connectivity.h + *** + *** File Description: + *** This file contains public definitions specific to UCCP base driver + *** + ****************************************************************************** + *END**************************************************************************/ +#ifndef __IMG_CONNECTIVITY_H +#define __IMG_CONNECTIVITY_H 1 + +struct img_version_info { + int bt; + int wlan; +}; + +struct img_scratch_info { + void *virt_addr; + dma_addr_t bus_addr; +}; + +struct img_version_info img_connectivity_version(void); +struct img_scratch_info img_connectivity_scratch(void); + +#endif /* __IMG_CONNECTIVITY_H */ diff --git a/include/soc/img/img-efuse.h b/include/soc/img/img-efuse.h new file mode 100644 index 00000000000000..64bf4ad897d77c --- /dev/null +++ b/include/soc/img/img-efuse.h @@ -0,0 +1,15 @@ +/* + * Imagination Technologies Generic eFuse driver + * + * Copyright (C) 2014 Imagination Technologies Ltd. + * + * 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 __IMG_EFUSE_H +#define __IMG_EFUSE_H + +int img_efuse_readl(unsigned int offset, u32 *value); + +#endif diff --git a/include/soc/img/img-transport.h b/include/soc/img/img-transport.h new file mode 100644 index 00000000000000..069d8193731205 --- /dev/null +++ b/include/soc/img/img-transport.h @@ -0,0 +1,110 @@ +/*HEADER********************************************************************** + ****************************************************************************** + *** + *** Copyright (c) 2011, 2012, 2013, 2014 Imagination Technologies 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. 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 Street, Fifth Floor, Boston, MA 02110-1301, + *** USA. + *** + *** File Name : img-transport.h + *** + *** File Description: + *** This file contains interface definition of the low level IMG transport + *** mechanism. + *** + ****************************************************************************** + *END**************************************************************************/ + + +#ifndef __IMG_TRANSPORT_H__ +#define __IMG_TRANSPORT_H__ + +#include + +/* + * Note that this procedure is going to be executed + * in the interrupt context, so it has to be as lean + * as possible and should preferably defer all heavy + * lifting. + */ +typedef void (*img_transport_handler)(u16 user_data); + +/* + * The following 4 procedures issue pokes to the RPU. They are guaranteed not + * to sleep. + */ + +/* + * May spin forever when, for example, RPU is unable to respond. If you can't + * afford that, use *_timeout variant. + */ +void img_transport_notify(u16 user_data, int user_id); + +/* + * Times out after jiffies_timeout kernel ticks have passed. + * + * Possible return values: + * @ -ETIME : request timed out + * @ 0 : RPU has been notified + */ +int __must_check img_transport_notify_timeout(u16 user_data, + int user_id, + long jiffies_timeout); + +/* + * May spin forever when, for example, RPU is unable to respond. If you can't + * afford that, use *_timeout variant. + */ +void img_transport_notify_callback(u16 user_data, + int user_id, + void (*poke_ready)(void *), + void *poke_ready_arg); + +/* + * Times out after jiffies_timeout kernel ticks have passed. 'poke_ready' called + * just before the poke is issued. + * + * Possible return values: + * @ -ETIME : request timed out + * @ 0 : RPU has been notified + */ +int __must_check img_transport_notify_callback_timeout(u16 user_data, + int user_id, + long jiffies_timeout, + void (*poke_ready)(void *), + void *poke_ready_arg); + +/* + * Register a routine which will be invoked whenever a message for client_id + * is received. + * + * Possible return values: + * @ -EBADSLT : id unavailable + * @ 0 : callback registered + */ +int img_transport_register_callback(img_transport_handler, + unsigned int client_id); + +/* + * Remove previously registerd routine. + * + * Possible return values: + * @ -EBADSLT : client id not found + * @ 0 : callback removed + */ +int img_transport_remove_callback(unsigned int client_id); + +#endif diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h index f86ef5ea9b0147..5f54abb7301c9a 100644 --- a/include/sound/dmaengine_pcm.h +++ b/include/sound/dmaengine_pcm.h @@ -35,6 +35,7 @@ snd_pcm_substream_to_dma_direction(const struct snd_pcm_substream *substream) int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, const struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config); +int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream); int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd); snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream); snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream); @@ -99,6 +100,11 @@ void snd_dmaengine_pcm_set_config_from_dai_data( * The PCM streams have custom channel names specified. */ #define SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME BIT(4) +/* + * DMA needs to be started early when start_at is used (eg to allow pre-loading + * of internal fifos before assertion of an enable signal) + */ +#define SND_DMAENGINE_PCM_FLAG_EARLY_START BIT(5) /** * struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM @@ -139,11 +145,17 @@ struct snd_dmaengine_pcm_config { int snd_dmaengine_pcm_register(struct device *dev, const struct snd_dmaengine_pcm_config *config, unsigned int flags); +int snd_dmaengine_pcm_register_id_name(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags, + unsigned int id, char *platform_name); void snd_dmaengine_pcm_unregister(struct device *dev); int devm_snd_dmaengine_pcm_register(struct device *dev, const struct snd_dmaengine_pcm_config *config, unsigned int flags); +int devm_snd_dmaengine_pcm_register_id_name(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags, + unsigned int id, char *platform_name); int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, diff --git a/include/sound/pcm.h b/include/sound/pcm.h index b0be09279943fc..7962ed03c900f8 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -30,6 +30,9 @@ #include #include #include +#if defined(CONFIG_HIGH_RES_TIMERS) +#include +#endif #define snd_pcm_substream_chip(substream) ((substream)->private_data) #define snd_pcm_chip(pcm) ((pcm)->private_data) @@ -73,6 +76,9 @@ struct snd_pcm_ops { int (*hw_free)(struct snd_pcm_substream *substream); int (*prepare)(struct snd_pcm_substream *substream); int (*trigger)(struct snd_pcm_substream *substream, int cmd); + int (*start_at)(struct snd_pcm_substream *substream, + int audio_clock_type, const struct timespec *ts); + int (*start_at_abort)(struct snd_pcm_substream *substream); snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); int (*get_time_info)(struct snd_pcm_substream *substream, struct timespec *system_ts, struct timespec *audio_ts, @@ -422,6 +428,7 @@ struct snd_pcm_runtime { /* -- OSS things -- */ struct snd_pcm_oss_runtime oss; #endif + }; struct snd_pcm_group { /* keep linked substreams */ @@ -482,6 +489,16 @@ struct snd_pcm_substream { #endif /* CONFIG_SND_VERBOSE_PROCFS */ /* misc flags */ unsigned int hw_opened: 1; + /* start at wait queue */ + wait_queue_head_t start_at_wait; + /* start at status info */ + bool start_at_pending; + /* Clock type for pending start at */ + int start_at_clock_class; +#ifdef CONFIG_HIGH_RES_TIMERS + /* start at timer for use with system startat */ + struct hrtimer start_at_timer; +#endif }; #define SUBSTREAM_BUSY(substream) ((substream)->ref_count > 0) @@ -564,6 +581,10 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, int snd_pcm_status(struct snd_pcm_substream *substream, struct snd_pcm_status *status); int snd_pcm_start(struct snd_pcm_substream *substream); +int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state); +int snd_pcm_do_start(struct snd_pcm_substream *substream, int state); +void snd_pcm_undo_start(struct snd_pcm_substream *substream, int state); +void snd_pcm_post_start(struct snd_pcm_substream *substream, int state); int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t status); int snd_pcm_drain_done(struct snd_pcm_substream *substream); int snd_pcm_stop_xrun(struct snd_pcm_substream *substream); @@ -1086,6 +1107,9 @@ snd_pcm_sframes_t snd_pcm_lib_writev(struct snd_pcm_substream *substream, snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream, void __user **bufs, snd_pcm_uframes_t frames); +void snd_pcm_start_at_trigger(struct snd_pcm_substream *substream); +void snd_pcm_start_at_cleanup(struct snd_pcm_substream *substream); + extern const struct snd_pcm_hw_constraint_list snd_pcm_known_rates; int snd_pcm_limit_hw_rates(struct snd_pcm_runtime *runtime); diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 212eaaf172ed49..b502ba078377b7 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -205,6 +205,11 @@ struct snd_soc_dai_ops { */ snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, struct snd_soc_dai *); + + int (*start_at)(struct snd_pcm_substream *, struct snd_soc_dai *, + int, const struct timespec *); + int (*start_at_abort)(struct snd_pcm_substream *, + struct snd_soc_dai *); }; /* diff --git a/include/sound/soc.h b/include/sound/soc.h index fb955e69a78ea2..1cc8d4bf394704 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -110,6 +110,14 @@ .put = snd_soc_put_volsw, \ .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ max, invert, 0) } +#define SOC_DOUBLE_STS(xname, reg, shift_left, shift_right, max, invert) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \ + max, invert, 0) } #define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw, \ @@ -737,6 +745,9 @@ struct snd_soc_ops { int (*hw_free)(struct snd_pcm_substream *); int (*prepare)(struct snd_pcm_substream *); int (*trigger)(struct snd_pcm_substream *, int); + int (*start_at)(struct snd_pcm_substream *, int, + const struct timespec *); + int (*start_at_abort)(struct snd_pcm_substream *); }; struct snd_soc_compr_ops { @@ -1640,6 +1651,10 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np, struct device_node **framemaster); int snd_soc_of_get_dai_name(struct device_node *of_node, const char **dai_name); +int snd_soc_of_get_dai_name_alt(struct device_node *of_node, char *name, + int index, const char **dai_name); +int snd_soc_of_get_platform_name(struct device_node *of_node, char *name, + int index, const char **platform_name); int snd_soc_of_get_dai_link_codecs(struct device *dev, struct device_node *of_node, struct snd_soc_dai_link *dai_link); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 93ba148f923e5f..8668304d8a79cd 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -261,4 +261,7 @@ /* STM32 USART */ #define PORT_STM32 113 +/* Pistachio SOC host port */ +#define PORT_HOSTPORT 114 + #endif /* _UAPILINUX_SERIAL_CORE_H */ diff --git a/include/uapi/misc/Kbuild b/include/uapi/misc/Kbuild index e96cae7d58c9f7..2d44fe1e9f9aa9 100644 --- a/include/uapi/misc/Kbuild +++ b/include/uapi/misc/Kbuild @@ -1,2 +1,3 @@ # misc Header export list +header-y += atu_ioctl.h header-y += cxl.h diff --git a/include/uapi/misc/atu_ioctl.h b/include/uapi/misc/atu_ioctl.h new file mode 100644 index 00000000000000..70063dc459d057 --- /dev/null +++ b/include/uapi/misc/atu_ioctl.h @@ -0,0 +1,43 @@ +/* + * Atu Ioctl Header file + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * 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. + */ + +#ifndef ATU_IOCTL_H +#define ATU_IOCTL_H + +#include +#include + +#define ATU_MAX_COUNTERS 6 +#define ATU_PAST 0 +#define ATU_FUTURE 1 + +struct atu_event { + u32 counter; + u32 source; + u32 timestamp; + u32 timestamp_counter; + u32 txtimer; + u32 timeofday_sec; + u32 timeofday_ns; + u32 timekeeping_shift; + u32 timekeeping_mult; +}; + +#define ATUIO (0xF2) +/*get event timestamp*/ +#define ATUIO_GETEVTS _IOWR(ATUIO, 0x42, struct atu_event) +/*set timex adjustments*/ +#define ATUIO_ADJTIMEX _IOWR(ATUIO, 0x43, struct timex) +/*set timeofday*/ +#define ATUIO_SETTIMEOFDAY _IOWR(ATUIO, 0x44, struct timex) +/*get timespec of atu clock*/ +#define ATUIO_GETTIMESPEC _IOWR(ATUIO, 0x45, struct timex) + +#endif /* ATU_IOCTL_H */ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index a82108e5d1c0cc..76919b9df6a788 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -293,6 +293,7 @@ typedef int __bitwise snd_pcm_state_t; #define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ #define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ #define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ +#define SNDRV_PCM_STATE_STARTING ((__force snd_pcm_state_t) 9) /* stream start has been delegated to the kernel */ #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED enum { @@ -503,6 +504,18 @@ enum { SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC_RAW, }; +enum { + SNDRV_PCM_CLOCK_CLASS_SYSTEM = 0, + SNDRV_PCM_CLOCK_CLASS_AUDIO, + SNDRV_PCM_CLOCK_CLASS_LAST = SNDRV_PCM_CLOCK_CLASS_AUDIO, +}; + +struct snd_startat { + int clock_class; + int clock_type; + struct timespec start_time; +}; + /* channel positions */ enum { SNDRV_CHMAP_UNKNOWN = 0, @@ -582,6 +595,8 @@ enum { #define SNDRV_PCM_IOCTL_READN_FRAMES _IOR('A', 0x53, struct snd_xfern) #define SNDRV_PCM_IOCTL_LINK _IOW('A', 0x60, int) #define SNDRV_PCM_IOCTL_UNLINK _IO('A', 0x61) +#define SNDRV_PCM_IOCTL_START_AT _IOW('A', 0x62, struct snd_startat) + /***************************************************************************** * * diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index a9b76a40319e86..35f445f1bf39e2 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -281,16 +281,28 @@ static int clockevents_program_min_delta(struct clock_event_device *dev) { unsigned long long clc; int64_t delta; + int i; - delta = dev->min_delta_ns; - dev->next_event = ktime_add_ns(ktime_get(), delta); + for (i = 0;;) { + delta = dev->min_delta_ns; + dev->next_event = ktime_add_ns(ktime_get(), delta); - if (clockevent_state_shutdown(dev)) - return 0; + if (clockevent_state_shutdown(dev)) + return 0; - dev->retries++; - clc = ((unsigned long long) delta * dev->mult) >> dev->shift; - return dev->set_next_event((unsigned long) clc, dev); + dev->retries++; + clc = ((unsigned long long) delta * dev->mult) >> dev->shift; + if (dev->set_next_event((unsigned long) clc, dev) == 0) + return 0; + + if (++i > 9) { + /* + * We tried 10 times to program the device with the + * given min_delta_ns. Get out of here. + */ + return -ETIME; + } + } } #endif /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */ diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index 37ea30e0754c2b..e93b266bd5b193 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -538,6 +538,102 @@ static ssize_t ieee80211_if_parse_tsf( } IEEE80211_IF_FILE_RW(tsf); +static ssize_t ieee80211_if_fmt_max_more_data_cnt( + const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) +{ + return snprintf(buf, buflen, "max_more_data_cnt value is %d\n",sdata->max_more_data_cnt); + +} +static ssize_t ieee80211_if_parse_max_more_data_cnt( + struct ieee80211_sub_if_data *sdata, const char *buf, int buflen) +{ + unsigned int ret = 0; + unsigned int max_more_data_cnt; + + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + + ret = kstrtouint(buf, 0 ,&max_more_data_cnt); + + if (ret) + return ret; + + if (max_more_data_cnt < 0) + return -EINVAL; + + sdata->max_more_data_cnt = max_more_data_cnt; + + return buflen; +} +IEEE80211_IF_FILE_RW(max_more_data_cnt); + +static ssize_t ieee80211_if_fmt_dynamic_ps_rx_timeout( + const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_conf *conf = &local->hw.conf; + + return snprintf(buf, buflen, "dynamic_ps_rx_timeout value is %dms\n",conf->dynamic_ps_rx_timeout); +} + +static ssize_t ieee80211_if_parse_dynamic_ps_rx_timeout( + struct ieee80211_sub_if_data *sdata, const char *buf, int buflen) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_conf *conf = &local->hw.conf; + unsigned int ret = 0; + unsigned int dynamic_ps_rx_timeout; + + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + + ret = kstrtouint(buf, 0 ,&dynamic_ps_rx_timeout); + + if (ret) + return ret; + + if (dynamic_ps_rx_timeout < 0) + return -EINVAL; + + conf->dynamic_ps_rx_timeout= dynamic_ps_rx_timeout; + + return buflen; +} +IEEE80211_IF_FILE_RW(dynamic_ps_rx_timeout); + +static ssize_t ieee80211_if_fmt_dynamic_ps_rx_traffic_timeout( + const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_conf *conf = &local->hw.conf; + + return snprintf(buf, buflen, "dynamic_ps_rx_traffic_timeout (starts after 1st MD) value is %dms\n",conf->dynamic_ps_rx_traffic_timeout); +} +static ssize_t ieee80211_if_parse_dynamic_ps_rx_traffic_timeout( + struct ieee80211_sub_if_data *sdata, const char *buf, int buflen) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_conf *conf = &local->hw.conf; + unsigned int ret = 0; + unsigned int dynamic_ps_rx_traffic_timeout; + + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + + ret = kstrtouint(buf, 0 ,&dynamic_ps_rx_traffic_timeout); + + if (ret) + return ret; + + if (dynamic_ps_rx_traffic_timeout < 0) + return -EINVAL; + + conf->dynamic_ps_rx_traffic_timeout= dynamic_ps_rx_traffic_timeout; + + return buflen; +} +IEEE80211_IF_FILE_RW(dynamic_ps_rx_traffic_timeout); + /* WDS attributes */ IEEE80211_IF_FILE(peer, u.wds.remote_addr, MAC); @@ -630,7 +726,9 @@ static void add_sta_files(struct ieee80211_sub_if_data *sdata) DEBUGFS_ADD_MODE(beacon_loss, 0200); DEBUGFS_ADD_MODE(uapsd_queues, 0600); DEBUGFS_ADD_MODE(uapsd_max_sp_len, 0600); - DEBUGFS_ADD_MODE(tdls_wider_bw, 0600); + DEBUGFS_ADD_MODE(max_more_data_cnt, 0600); + DEBUGFS_ADD_MODE(dynamic_ps_rx_timeout, 0600); + DEBUGFS_ADD_MODE(dynamic_ps_rx_traffic_timeout, 0600); } static void add_ap_files(struct ieee80211_sub_if_data *sdata) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 6837a46ca4a2dd..705c2dcd9882a6 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -833,6 +833,12 @@ struct ieee80211_sub_if_data { unsigned long state; + unsigned int max_more_data_cnt; + + unsigned int more_data_cnt; + + unsigned int rx_packet_count; + char name[IFNAMSIZ]; /* Fragment table for host-based reassembly */ @@ -1309,7 +1315,10 @@ struct ieee80211_local { struct ieee80211_sub_if_data *ps_sdata; struct work_struct dynamic_ps_enable_work; struct work_struct dynamic_ps_disable_work; + struct work_struct dynamic_ps_rx_recalc_ps_work; struct timer_list dynamic_ps_timer; + struct timer_list dynamic_ps_rx_timer; + struct timer_list dynamic_ps_rx_traffic_timer; struct notifier_block ifa_notifier; struct notifier_block ifa6_notifier; @@ -1846,7 +1855,11 @@ static inline int ieee80211_ac_from_tid(int tid) void ieee80211_dynamic_ps_enable_work(struct work_struct *work); void ieee80211_dynamic_ps_disable_work(struct work_struct *work); +void ieee80211_dynamic_ps_rx_recalc_ps_work (struct work_struct *work); void ieee80211_dynamic_ps_timer(unsigned long data); +void ieee80211_dynamic_ps_rx_timer(unsigned long data); +void ieee80211_dynamic_ps_rx_traffic_timer(unsigned long data); + void ieee80211_send_nullfunc(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata, bool powersave); @@ -2023,7 +2036,7 @@ void ieee80211_dfs_cac_cancel(struct ieee80211_local *local); void ieee80211_dfs_radar_detected_work(struct work_struct *work); int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata, struct cfg80211_csa_settings *csa_settings); - +bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata); bool ieee80211_cs_valid(const struct ieee80211_cipher_scheme *cs); bool ieee80211_cs_list_valid(const struct ieee80211_cipher_scheme *cs, int n); const struct ieee80211_cipher_scheme * diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index c9e325d2e120c0..ca4eeda6b81a58 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1815,6 +1815,10 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, sdata->ap_power_level = IEEE80211_UNSET_POWER_LEVEL; sdata->user_power_level = local->user_power_level; + sdata->more_data_cnt = 0; + sdata->max_more_data_cnt = 3; + sdata->rx_packet_count = 0; + sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; /* setup type-dependent data */ diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 175ffcf7fb06bf..5931b6eea8b373 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -621,8 +621,17 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, ieee80211_dynamic_ps_enable_work); INIT_WORK(&local->dynamic_ps_disable_work, ieee80211_dynamic_ps_disable_work); + INIT_WORK(&local->dynamic_ps_rx_recalc_ps_work, + ieee80211_dynamic_ps_rx_recalc_ps_work); + setup_timer(&local->dynamic_ps_timer, ieee80211_dynamic_ps_timer, (unsigned long) local); + /*Timers for Dynamic RX power save*/ + setup_timer(&local->dynamic_ps_rx_timer, + ieee80211_dynamic_ps_rx_timer, (unsigned long) local); + + setup_timer(&local->dynamic_ps_rx_traffic_timer, + ieee80211_dynamic_ps_rx_traffic_timer, (unsigned long) local); INIT_WORK(&local->sched_scan_stopped_work, ieee80211_sched_scan_stopped_work); @@ -865,6 +874,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) max_bitrates = 0; supp_ht = false; supp_vht = false; + + local->hw.conf.dynamic_ps_rx_timeout = 100; + local->hw.conf.dynamic_ps_rx_traffic_timeout = 1500; + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { struct ieee80211_supported_band *sband; diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index 6f85b6ab8e5101..d3b858ffed5367 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1370,7 +1370,6 @@ void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata) sdata_unlock(sdata); } - void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata) { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 83097c3832d129..077c8b7ba1b173 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -1449,7 +1449,7 @@ static void ieee80211_change_ps(struct ieee80211_local *local) } } -static bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata) +bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata) { struct ieee80211_if_managed *mgd = &sdata->u.mgd; struct sta_info *sta = NULL; @@ -1483,6 +1483,7 @@ static bool ieee80211_powersave_allowed(struct ieee80211_sub_if_data *sdata) void ieee80211_recalc_ps(struct ieee80211_local *local) { struct ieee80211_sub_if_data *sdata, *found = NULL; + struct ieee80211_conf *conf = &local->hw.conf; int count = 0; int timeout; @@ -1530,6 +1531,19 @@ void ieee80211_recalc_ps(struct ieee80211_local *local) local->ps_sdata = NULL; } + if (count == 1 && conf->dynamic_ps_rx_timeout > 0 && + (found->more_data_cnt > found->max_more_data_cnt)) { + found->more_data_cnt = 0; + local->ps_sdata = NULL; + ieee80211_change_ps(local); + local->ps_sdata = found; + sdata->rx_packet_count = 0; + /*Check for inactivity to go back to PS*/ + mod_timer(&local->dynamic_ps_rx_timer, jiffies + + msecs_to_jiffies(conf->dynamic_ps_rx_timeout)); + return; + } + ieee80211_change_ps(local); } @@ -1629,6 +1643,18 @@ void ieee80211_dynamic_ps_enable_work(struct work_struct *work) } } +void ieee80211_dynamic_ps_rx_recalc_ps_work(struct work_struct *work) +{ + struct ieee80211_local *local = + container_of(work, struct ieee80211_local, + dynamic_ps_rx_recalc_ps_work); + + mutex_lock(&local->iflist_mtx); + ieee80211_recalc_ps(local); + mutex_unlock(&local->iflist_mtx); +} + + void ieee80211_dynamic_ps_timer(unsigned long data) { struct ieee80211_local *local = (void *) data; @@ -1636,6 +1662,81 @@ void ieee80211_dynamic_ps_timer(unsigned long data) ieee80211_queue_work(&local->hw, &local->dynamic_ps_enable_work); } +void ieee80211_dynamic_ps_rx_timer(unsigned long data) +{ + struct ieee80211_local *local = (void *) data; + struct ieee80211_sub_if_data *sdata = NULL; + struct ieee80211_conf *conf = &local->hw.conf; + unsigned char count = 0; + + if (local->quiescing || local->suspended) + return; + + list_for_each_entry(sdata, &local->interfaces, list) { + if (!ieee80211_sdata_running(sdata)) + continue; + + if (sdata->vif.type == NL80211_IFTYPE_AP) { + /* If an AP vif is found, then disable PS + * by setting the count to zero thereby setting + * ps_sdata to NULL. + */ + count = 0; + break; + } + if (sdata->vif.type != NL80211_IFTYPE_STATION) + continue; + + count++; + } + + if (count == 1) { + if (sdata->rx_packet_count) { + /*No change, data is still flowing*/ + mod_timer(&local->dynamic_ps_rx_timer, jiffies + + msecs_to_jiffies(conf->dynamic_ps_rx_timeout)); + } else { + /*Go To powersave only if tx is also empty*/ + mod_timer(&local->dynamic_ps_timer, jiffies + + msecs_to_jiffies(conf->dynamic_ps_timeout)); + } + sdata->rx_packet_count = 0; + } +} + +void ieee80211_dynamic_ps_rx_traffic_timer(unsigned long data) +{ + struct ieee80211_local *local = (void *) data; + struct ieee80211_sub_if_data *sdata , *found = NULL; + unsigned char count = 0; + + if (local->quiescing || local->suspended) + return; + + list_for_each_entry(sdata, &local->interfaces, list) { + if (!ieee80211_sdata_running(sdata)) + continue; + if (sdata->vif.type == NL80211_IFTYPE_AP) { + /* If an AP vif is found, then disable PS + * by setting the count to zero thereby setting + * ps_sdata to NULL. + */ + count = 0; + break; + } + if (sdata->vif.type != NL80211_IFTYPE_STATION) + continue; + found = sdata; + count++; + } + if (count == 1) { + if (found->more_data_cnt < found->max_more_data_cnt) { + /*Traffic is slow, stay in ps*/ + found->more_data_cnt = 0; + } + } +} + void ieee80211_dfs_cac_timer_work(struct work_struct *work) { struct delayed_work *delayed_work = diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index 239ed6e92b89d4..3db520f9cf3396 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -1102,13 +1102,14 @@ minstrel_ht_update_caps(void *priv, struct ieee80211_supported_band *sband, struct minstrel_ht_sta_priv *msp = priv_sta; struct minstrel_ht_sta *mi = &msp->ht; struct ieee80211_mcs_info *mcs = &sta->ht_cap.mcs; - u16 sta_cap = sta->ht_cap.cap; + u16 ht_cap = sta->ht_cap.cap; struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; int use_vht; int n_supported = 0; int ack_dur; int stbc; int i; + bool ldpc; /* fall back to the old minstrel for legacy stations */ if (!sta->ht_cap.ht_supported) @@ -1146,16 +1147,24 @@ minstrel_ht_update_caps(void *priv, struct ieee80211_supported_band *sband, } mi->sample_tries = 4; - /* TODO tx_flags for vht - ATM the RC API is not fine-grained enough */ if (!use_vht) { - stbc = (sta_cap & IEEE80211_HT_CAP_RX_STBC) >> + stbc = (ht_cap & IEEE80211_HT_CAP_RX_STBC) >> IEEE80211_HT_CAP_RX_STBC_SHIFT; - mi->tx_flags |= stbc << IEEE80211_TX_CTL_STBC_SHIFT; - if (sta_cap & IEEE80211_HT_CAP_LDPC_CODING) - mi->tx_flags |= IEEE80211_TX_CTL_LDPC; + if (ht_cap & IEEE80211_HT_CAP_LDPC_CODING) + ldpc = true; + } else { + stbc = (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK) >> + IEEE80211_VHT_CAP_RXSTBC_SHIFT; + + if (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC) + ldpc = true; } + mi->tx_flags |= stbc << IEEE80211_TX_CTL_STBC_SHIFT; + if (ldpc) + mi->tx_flags |= IEEE80211_TX_CTL_LDPC; + for (i = 0; i < ARRAY_SIZE(mi->groups); i++) { u32 gflags = minstrel_mcs_groups[i].flags; int bw, nss; @@ -1168,10 +1177,10 @@ minstrel_ht_update_caps(void *priv, struct ieee80211_supported_band *sband, if (gflags & IEEE80211_TX_RC_SHORT_GI) { if (gflags & IEEE80211_TX_RC_40_MHZ_WIDTH) { - if (!(sta_cap & IEEE80211_HT_CAP_SGI_40)) + if (!(ht_cap & IEEE80211_HT_CAP_SGI_40)) continue; } else { - if (!(sta_cap & IEEE80211_HT_CAP_SGI_20)) + if (!(ht_cap & IEEE80211_HT_CAP_SGI_20)) continue; } } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 4cbf36cae806d2..a45597c6bda08c 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1190,11 +1190,28 @@ ieee80211_rx_h_check_more_data(struct ieee80211_rx_data *rx) struct ieee80211_local *local; struct ieee80211_hdr *hdr; struct sk_buff *skb; + struct ieee80211_sub_if_data *sdata = rx->sdata; + struct ieee80211_conf *conf; local = rx->local; + conf = &local->hw.conf; + skb = rx->skb; hdr = (struct ieee80211_hdr *) skb->data; + if (conf->dynamic_ps_rx_timeout > 0 && + ieee80211_powersave_allowed(sdata) && + (conf->flags & IEEE80211_CONF_PS) && + ieee80211_is_data(hdr->frame_control) && + ieee80211_has_moredata(hdr->frame_control)) { + sdata->more_data_cnt++; + /*For first packet start a traffic timer*/ + if (sdata->more_data_cnt == 1) + mod_timer(&local->dynamic_ps_rx_traffic_timer, jiffies + + msecs_to_jiffies(conf->dynamic_ps_rx_traffic_timeout)); + ieee80211_queue_work(&local->hw, &local->dynamic_ps_rx_recalc_ps_work); + } + if (!local->pspolling) return RX_CONTINUE; @@ -1212,7 +1229,7 @@ ieee80211_rx_h_check_more_data(struct ieee80211_rx_data *rx) } /* more data bit is set, let's request a new frame from the AP */ - ieee80211_send_pspoll(local, rx->sdata); + ieee80211_send_pspoll(local, sdata); return RX_CONTINUE; } @@ -3437,6 +3454,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, rx->skb = skb; } + sdata->rx_packet_count++; ieee80211_invoke_rx_handlers(rx); return true; } diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 308c9ecf73db18..228d6101404d3d 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -489,7 +489,8 @@ static void snd_pcm_xrun_injection_write(struct snd_info_entry *entry, snd_pcm_stream_lock_irq(substream); runtime = substream->runtime; - if (runtime && runtime->status->state == SNDRV_PCM_STATE_RUNNING) + if (runtime && (runtime->status->state == SNDRV_PCM_STATE_RUNNING || + runtime->status->state == SNDRV_PCM_STATE_STARTING)) snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); snd_pcm_stream_unlock_irq(substream); } @@ -746,6 +747,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) INIT_LIST_HEAD(&substream->self_group.substreams); list_add_tail(&substream->link_list, &substream->self_group.substreams); atomic_set(&substream->mmap_count, 0); + init_waitqueue_head(&substream->start_at_wait); prev = substream; } return 0; diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 1f64ab0c2a95a2..63451e3a6b1c00 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -316,6 +316,25 @@ static int snd_pcm_status_user_x32(struct snd_pcm_substream *substream, } #endif /* CONFIG_X86_X32 */ +struct snd_startat32 { + u32 clock_class; + u32 clock_type; + struct compat_timespec start_time; +} __packed; + +static int snd_pcm_start_at_compat(struct snd_pcm_substream *substream, + struct snd_startat32 __user *_start_at) +{ + struct snd_startat start_at; + + if (get_user(start_at.clock_class, &_start_at.clock_class) || + get_user(start_at.clock_type, &_start_at.clock_type) || + compat_get_timespec(&start_at.start_time, &_start_at.start_time)) + return -EFAULT; + + return snd_pcm_start_at(substream, &start_at); +} + /* both for HW_PARAMS and HW_REFINE */ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, int refine, @@ -653,6 +672,7 @@ enum { SNDRV_PCM_IOCTL_STATUS_EXT_X32 = _IOWR('A', 0x24, struct snd_pcm_status_x32), SNDRV_PCM_IOCTL_SYNC_PTR_X32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr_x32), #endif /* CONFIG_X86_X32 */ + SNDRV_PCM_IOCTL_START_AT32 = _IOWR('A', 0x62, struct snd_startat32) }; static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) @@ -734,6 +754,8 @@ static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned l case SNDRV_PCM_IOCTL_CHANNEL_INFO_X32: return snd_pcm_ioctl_channel_info_x32(substream, argp); #endif /* CONFIG_X86_X32 */ + case SNDRV_PCM_IOCTL_START_AT32: + return snd_pcm_start_at_compat(substream, argp); } return -ENOIOCTLCMD; diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index fba365a783909f..7956da101aa824 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -142,7 +142,7 @@ static void dmaengine_pcm_dma_complete(void *arg) snd_pcm_period_elapsed(substream); } -static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) +int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) { struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); struct dma_chan *chan = prtd->dma_chan; @@ -170,6 +170,7 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) return 0; } +EXPORT_SYMBOL_GPL(dmaengine_pcm_prepare_and_submit); /** * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 3a9b66c6e09c38..c4d88ee057a57b 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2025,6 +2025,7 @@ static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, snd_pcm_stream_lock_irq(substream); switch (runtime->status->state) { case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_STARTING: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: break; @@ -2251,6 +2252,7 @@ static snd_pcm_sframes_t snd_pcm_lib_read1(struct snd_pcm_substream *substream, break; case SNDRV_PCM_STATE_DRAINING: case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_STARTING: case SNDRV_PCM_STATE_PAUSED: break; case SNDRV_PCM_STATE_XRUN: diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 4ba64fd49759af..945fb80d96c3d4 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -36,6 +36,14 @@ #include #include +#if defined(CONFIG_HIGH_RES_TIMERS) +#include +#endif + +#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT) +#include +#endif + /* * Compatibility */ @@ -277,7 +285,7 @@ static const char * const snd_pcm_hw_param_names[] = { }; #endif -int snd_pcm_hw_refine(struct snd_pcm_substream *substream, +int snd_pcm_hw_refine(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { unsigned int k; @@ -1028,7 +1036,7 @@ static int snd_pcm_action_nonatomic(struct action_ops *ops, /* * start callbacks */ -static int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) +int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; if (runtime->status->state != SNDRV_PCM_STATE_PREPARED) @@ -1040,21 +1048,24 @@ static int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) runtime->trigger_master = substream; return 0; } +EXPORT_SYMBOL_GPL(snd_pcm_pre_start); -static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state) +int snd_pcm_do_start(struct snd_pcm_substream *substream, int state) { if (substream->runtime->trigger_master != substream) return 0; return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); } +EXPORT_SYMBOL_GPL(snd_pcm_do_start); -static void snd_pcm_undo_start(struct snd_pcm_substream *substream, int state) +void snd_pcm_undo_start(struct snd_pcm_substream *substream, int state) { if (substream->runtime->trigger_master == substream) substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); } +EXPORT_SYMBOL_GPL(snd_pcm_undo_start); -static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state) +void snd_pcm_post_start(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_trigger_tstamp(substream); @@ -1067,6 +1078,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state) snd_pcm_playback_silence(substream, ULONG_MAX); snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART); } +EXPORT_SYMBOL_GPL(snd_pcm_post_start); static struct action_ops snd_pcm_action_start = { .pre_action = snd_pcm_pre_start, @@ -1075,6 +1087,185 @@ static struct action_ops snd_pcm_action_start = { .post_action = snd_pcm_post_start }; +void snd_pcm_start_at_cleanup(struct snd_pcm_substream *substream) +{ + substream->start_at_pending = 0; + wake_up(&substream->start_at_wait); +} +EXPORT_SYMBOL_GPL(snd_pcm_start_at_cleanup); + +void snd_pcm_start_at_trigger(struct snd_pcm_substream *substream) +{ + snd_pcm_stream_lock(substream); + + if(substream->runtime->status->state == SNDRV_PCM_STATE_STARTING) + if(!snd_pcm_do_start(substream, SNDRV_PCM_STATE_RUNNING)) + snd_pcm_post_start(substream, SNDRV_PCM_STATE_RUNNING); + + snd_pcm_start_at_cleanup(substream); + + snd_pcm_stream_unlock(substream); +} +EXPORT_SYMBOL_GPL(snd_pcm_start_at_trigger); + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart snd_pcm_do_start_time(struct hrtimer *timer) +{ + struct snd_pcm_substream *substream; + + substream = container_of(timer, struct snd_pcm_substream, + start_at_timer); + + snd_pcm_start_at_trigger(substream); + + return HRTIMER_NORESTART; +} +#endif + +static int snd_pcm_startat_system(struct snd_pcm_substream *substream, + int clock_type, const struct timespec *start_time) +{ +#ifdef CONFIG_HIGH_RES_TIMERS + struct timespec now; + clockid_t clock; + + switch (clock_type) { + case SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY: + clock = CLOCK_REALTIME; + getnstimeofday(&now); + break; + case SNDRV_PCM_TSTAMP_TYPE_MONOTONIC: + clock = CLOCK_MONOTONIC; + ktime_get_ts(&now); + break; + default: /* unsupported clocks bounce off */ + return -ENOSYS; + } + + /* Check if start_time is in the past */ + if (timespec_compare(&now, start_time) >= 0) + return -ETIME; + + hrtimer_init(&substream->start_at_timer, clock, HRTIMER_MODE_ABS); + substream->start_at_timer.function = snd_pcm_do_start_time; + + hrtimer_start(&substream->start_at_timer, + timespec_to_ktime(*start_time), HRTIMER_MODE_ABS); + + return 0; +#else + return -ENOSYS; +#endif +} + +static void snd_pcm_startat_system_cancel(struct snd_pcm_substream *substream) +{ +#ifdef CONFIG_HIGH_RES_TIMERS + if(hrtimer_try_to_cancel(&substream->start_at_timer) == 1) + snd_pcm_start_at_cleanup(substream); +#endif +} + +static int snd_pcm_startat_audio(struct snd_pcm_substream *substream, + int clock_type, const struct timespec *start_time) +{ + if (substream->ops->start_at) + return substream->ops->start_at(substream, clock_type, + start_time); + else + return -ENOSYS; +} + +static void snd_pcm_startat_audio_cancel(struct snd_pcm_substream *substream) +{ + if (substream->ops->start_at_abort) + substream->ops->start_at_abort(substream); +} + +static int snd_pcm_start_at(struct snd_pcm_substream *substream, + struct snd_startat *start_at) +{ + int ret; + + if (!timespec_valid(&start_at->start_time)) + return -EINVAL; + + snd_pcm_stream_lock(substream); + + if(substream->start_at_pending) { + ret = -EBUSY; + goto end; + } + + ret = snd_pcm_pre_start(substream, SNDRV_PCM_STATE_PREPARED); + if (ret < 0) + goto end; + + switch (start_at->clock_class) { + case SNDRV_PCM_CLOCK_CLASS_SYSTEM: + ret = snd_pcm_startat_system(substream, + start_at->clock_type, + &start_at->start_time); + break; + case SNDRV_PCM_CLOCK_CLASS_AUDIO: + ret = snd_pcm_startat_audio(substream, + start_at->clock_type, + &start_at->start_time); + if(ret) + snd_pcm_startat_audio_cancel(substream); + break; + default: + ret = -EINVAL; + } + + if (!ret) { + substream->runtime->status->state = SNDRV_PCM_STATE_STARTING; + substream->start_at_pending = true; + substream->start_at_clock_class = start_at->clock_class; + } + +end: + snd_pcm_stream_unlock(substream); + return ret; +} + +static void snd_pcm_startat_cancel(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (runtime->status->state != SNDRV_PCM_STATE_STARTING) + return; + + switch (substream->start_at_clock_class) { + case SNDRV_PCM_CLOCK_CLASS_SYSTEM: + snd_pcm_startat_system_cancel(substream); + break; + case SNDRV_PCM_CLOCK_CLASS_AUDIO: + snd_pcm_startat_audio_cancel(substream); + break; + default: + return; + } + + runtime->status->state = SNDRV_PCM_STATE_SETUP; + + wait_event_cmd(substream->start_at_wait, + !substream->start_at_pending, + snd_pcm_stream_unlock(substream), + snd_pcm_stream_lock(substream)); +} + +int snd_pcm_start_at_user(struct snd_pcm_substream *substream, + struct snd_startat __user *_start_at_user) +{ + struct snd_startat start_at; + + if (copy_from_user(&start_at, _start_at_user, sizeof(start_at))) + return -EFAULT; + + return snd_pcm_start_at(substream, &start_at); +} + /** * snd_pcm_start - start all linked streams * @substream: the PCM substream instance @@ -1101,9 +1292,21 @@ static int snd_pcm_pre_stop(struct snd_pcm_substream *substream, int state) static int snd_pcm_do_stop(struct snd_pcm_substream *substream, int state) { - if (substream->runtime->trigger_master == substream && - snd_pcm_running(substream)) - substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); + if (substream->runtime->trigger_master == substream) { + switch(substream->runtime->status->state) { + case SNDRV_PCM_STATE_STARTING: + snd_pcm_startat_cancel(substream); + break; + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + substream->ops->trigger(substream, + SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } + } + return 0; /* unconditonally stop all substreams */ } @@ -1167,11 +1370,13 @@ int snd_pcm_drain_done(struct snd_pcm_substream *substream) */ int snd_pcm_stop_xrun(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; unsigned long flags; int ret = 0; snd_pcm_stream_lock_irqsave(substream, flags); - if (snd_pcm_running(substream)) + if (snd_pcm_running(substream) || + runtime->status->state == SNDRV_PCM_STATE_STARTING) ret = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); snd_pcm_stream_unlock_irqrestore(substream, flags); return ret; @@ -1257,7 +1462,8 @@ static int snd_pcm_pause(struct snd_pcm_substream *substream, int push) static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream, int state) { struct snd_pcm_runtime *runtime = substream->runtime; - if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) + if ((runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) || + (runtime->status->state == SNDRV_PCM_STATE_STARTING)) return -EBUSY; runtime->trigger_master = substream; return 0; @@ -1355,6 +1561,8 @@ static int snd_pcm_pre_resume(struct snd_pcm_substream *substream, int state) struct snd_pcm_runtime *runtime = substream->runtime; if (!(runtime->info & SNDRV_PCM_INFO_RESUME)) return -ENOSYS; + if (runtime->status->state == SNDRV_PCM_STATE_STARTING) + return -EBUSY; runtime->trigger_master = substream; return 0; } @@ -1439,6 +1647,7 @@ static int snd_pcm_xrun(struct snd_pcm_substream *substream) result = 0; /* already there */ break; case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_STARTING: result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); break; default: @@ -1458,6 +1667,7 @@ static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, int state) struct snd_pcm_runtime *runtime = substream->runtime; switch (runtime->status->state) { case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_STARTING: case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_PAUSED: case SNDRV_PCM_STATE_SUSPENDED: @@ -1512,7 +1722,8 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, if (runtime->status->state == SNDRV_PCM_STATE_OPEN || runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; - if (snd_pcm_running(substream)) + if (snd_pcm_running(substream) || + runtime->status->state == SNDRV_PCM_STATE_STARTING) return -EBUSY; substream->f_flags = f_flags; return 0; @@ -1578,6 +1789,7 @@ static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream, int state case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_DISCONNECTED: case SNDRV_PCM_STATE_SUSPENDED: + case SNDRV_PCM_STATE_STARTING: return -EBADFD; } runtime->trigger_master = substream; @@ -1782,6 +1994,7 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream) snd_pcm_pause(substream, 0); snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ snd_pcm_stream_unlock_irq(substream); @@ -2234,6 +2447,7 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream) return; snd_pcm_drop(substream); + if (substream->hw_opened) { if (substream->ops->hw_free && substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) @@ -2441,6 +2655,7 @@ static snd_pcm_sframes_t snd_pcm_playback_rewind(struct snd_pcm_substream *subst snd_pcm_stream_lock_irq(substream); switch (runtime->status->state) { case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_STARTING: break; case SNDRV_PCM_STATE_DRAINING: case SNDRV_PCM_STATE_RUNNING: @@ -2490,6 +2705,7 @@ static snd_pcm_sframes_t snd_pcm_capture_rewind(struct snd_pcm_substream *substr switch (runtime->status->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_STARTING: break; case SNDRV_PCM_STATE_RUNNING: if (snd_pcm_update_hw_ptr(substream) >= 0) @@ -2538,6 +2754,7 @@ static snd_pcm_sframes_t snd_pcm_playback_forward(struct snd_pcm_substream *subs switch (runtime->status->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_STARTING: break; case SNDRV_PCM_STATE_DRAINING: case SNDRV_PCM_STATE_RUNNING: @@ -2588,6 +2805,7 @@ static snd_pcm_sframes_t snd_pcm_capture_forward(struct snd_pcm_substream *subst case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_DRAINING: case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_STARTING: break; case SNDRV_PCM_STATE_RUNNING: if (snd_pcm_update_hw_ptr(substream) >= 0) @@ -2638,6 +2856,7 @@ static int snd_pcm_hwsync(struct snd_pcm_substream *substream) /* Fall through */ case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_SUSPENDED: + case SNDRV_PCM_STATE_STARTING: err = 0; break; case SNDRV_PCM_STATE_XRUN: @@ -2671,6 +2890,7 @@ static int snd_pcm_delay(struct snd_pcm_substream *substream, /* Fall through */ case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_SUSPENDED: + case SNDRV_PCM_STATE_STARTING: err = 0; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) n = snd_pcm_playback_hw_avail(runtime); @@ -2781,6 +3001,8 @@ static int snd_pcm_common_ioctl1(struct file *file, return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING); case SNDRV_PCM_IOCTL_LINK: return snd_pcm_link(substream, (int)(unsigned long) arg); + case SNDRV_PCM_IOCTL_START_AT: + return snd_pcm_start_at(substream, arg); case SNDRV_PCM_IOCTL_UNLINK: return snd_pcm_unlink(substream); case SNDRV_PCM_IOCTL_RESUME: @@ -3172,6 +3394,7 @@ static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait) case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_STARTING: if (avail >= runtime->control->avail_min) { mask = POLLOUT | POLLWRNORM; break; @@ -3209,6 +3432,7 @@ static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait) avail = snd_pcm_capture_avail(runtime); switch (runtime->status->state) { case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_STARTING: case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_PAUSED: if (avail >= runtime->control->avail_min) { @@ -3333,6 +3557,20 @@ static inline struct page * snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs) { void *vaddr = substream->runtime->dma_area + ofs; +#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT) + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) + return virt_to_page(CAC_ADDR(vaddr)); +#endif +#if defined(CONFIG_PPC32) && defined(CONFIG_NOT_COHERENT_CACHE) + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) { + dma_addr_t addr = substream->runtime->dma_addr + ofs; + addr -= get_dma_offset(substream->dma_buffer.dev.dev); + /* assume dma_handle set via pfn_to_phys() in + * mm/dma-noncoherent.c + */ + return pfn_to_page(addr >> PAGE_SHIFT); + } +#endif return virt_to_page(vaddr); } @@ -3377,6 +3615,13 @@ static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = { .fault = snd_pcm_mmap_data_fault, }; +#ifndef ARCH_HAS_DMA_MMAP_COHERENT +/* This should be defined / handled globally! */ +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) +#define ARCH_HAS_DMA_MMAP_COHERENT +#endif +#endif + /* * mmap the DMA buffer on RAM */ @@ -3401,7 +3646,8 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, area->vm_end - area->vm_start, area->vm_page_prot); } #endif /* CONFIG_GENERIC_ALLOCATOR */ -#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */ + +#ifdef ARCH_HAS_DMA_MMAP_COHERENT if (!substream->ops->page && substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) return dma_mmap_coherent(substream->dma_buffer.dev.dev, @@ -3409,7 +3655,11 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, substream->runtime->dma_area, substream->runtime->dma_addr, area->vm_end - area->vm_start); -#endif /* CONFIG_X86 */ +#elif defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT) + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV && + !plat_device_is_coherent(substream->dma_buffer.dev.dev)) + area->vm_page_prot = pgprot_noncached(area->vm_page_prot); +#endif /* ARCH_HAS_DMA_MMAP_COHERENT */ /* mmap with fault handler */ area->vm_ops = &snd_pcm_vm_ops_data_fault; return 0; diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 7ff7d88e46ddd6..a012b2655e8420 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -50,6 +50,7 @@ source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" +source "sound/soc/img/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mediatek/Kconfig" source "sound/soc/mxs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 8eb06db32fa0dc..78625fae78d648 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += img/ obj-$(CONFIG_SND_SOC) += intel/ obj-$(CONFIG_SND_SOC) += mediatek/ obj-$(CONFIG_SND_SOC) += mxs/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cfdafc4c11ea9a..c0b2e5f9ae5041 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -85,6 +85,10 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008 + select SND_SOC_PCM3060_I2C if I2C + select SND_SOC_PCM3060_SPI if SPI_MASTER + select SND_SOC_PCM3168A_I2C if I2C + select SND_SOC_PCM3168A_SPI if SPI_MASTER select SND_SOC_PCM512x_I2C if I2C select SND_SOC_PCM512x_SPI if SPI_MASTER select SND_SOC_RT286 if I2C @@ -506,6 +510,28 @@ config SND_SOC_PCM1792A config SND_SOC_PCM3008 tristate +config SND_SOC_PCM3060 + tristate + +config SND_SOC_PCM3060_SPI + tristate + select SND_SOC_PCM3060 + +config SND_SOC_PCM3060_I2C + tristate + select SND_SOC_PCM3060 + +config SND_SOC_PCM3168A + tristate + +config SND_SOC_PCM3168A_SPI + tristate + select SND_SOC_PCM3168A + +config SND_SOC_PCM3168A_I2C + tristate + select SND_SOC_PCM3168A + config SND_SOC_PCM512x tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f632fc42f59f08..1f944ec588a481 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -78,6 +78,12 @@ snd-soc-nau8825-objs := nau8825.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o +snd-soc-pcm3060-objs := pcm3060.o +snd-soc-pcm3060-i2c-objs := pcm3060-i2c.o +snd-soc-pcm3060-spi-objs := pcm3060-spi.o +snd-soc-pcm3168a-objs := pcm3168a.o +snd-soc-pcm3168a-i2c-objs := pcm3168a-i2c.o +snd-soc-pcm3168a-spi-objs := pcm3168a-spi.o snd-soc-pcm512x-objs := pcm512x.o snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o snd-soc-pcm512x-spi-objs := pcm512x-spi.o @@ -273,6 +279,12 @@ obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o +obj-$(CONFIG_SND_SOC_PCM3060) += snd-soc-pcm3060.o +obj-$(CONFIG_SND_SOC_PCM3060_I2C) += snd-soc-pcm3060-i2c.o +obj-$(CONFIG_SND_SOC_PCM3060_SPI) += snd-soc-pcm3060-spi.o +obj-$(CONFIG_SND_SOC_PCM3168A) += snd-soc-pcm3168a.o +obj-$(CONFIG_SND_SOC_PCM3168A_I2C) += snd-soc-pcm3168a-i2c.o +obj-$(CONFIG_SND_SOC_PCM3168A_SPI) += snd-soc-pcm3168a-spi.o obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o diff --git a/sound/soc/codecs/pcm3060-i2c.c b/sound/soc/codecs/pcm3060-i2c.c new file mode 100644 index 00000000000000..a1437036b817fe --- /dev/null +++ b/sound/soc/codecs/pcm3060-i2c.c @@ -0,0 +1,66 @@ +/* + * PCM3060 codec i2c driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include + +#include + +#include "pcm3060.h" + +static int pcm3060_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &pcm3060_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3060_probe(&i2c->dev, regmap); +} + +static int pcm3060_i2c_remove(struct i2c_client *i2c) +{ + pcm3060_remove(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id pcm3060_i2c_id[] = { + { "pcm3060", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm3060_i2c_id); + +static const struct of_device_id pcm3060_of_match[] = { + { .compatible = "ti,pcm3060", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3060_of_match); + +static struct i2c_driver pcm3060_i2c_driver = { + .probe = pcm3060_i2c_probe, + .remove = pcm3060_i2c_remove, + .id_table = pcm3060_i2c_id, + .driver = { + .name = "pcm3060", + .of_match_table = pcm3060_of_match, + .pm = &pcm3060_pm_ops, + }, +}; +module_i2c_driver(pcm3060_i2c_driver); + +MODULE_DESCRIPTION("PCM3060 I2C codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3060-spi.c b/sound/soc/codecs/pcm3060-spi.c new file mode 100644 index 00000000000000..c8831ae97b9a50 --- /dev/null +++ b/sound/soc/codecs/pcm3060-spi.c @@ -0,0 +1,65 @@ +/* + * PCM3060 codec spi driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include + +#include + +#include "pcm3060.h" + +static int pcm3060_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &pcm3060_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3060_probe(&spi->dev, regmap); +} + +static int pcm3060_spi_remove(struct spi_device *spi) +{ + pcm3060_remove(&spi->dev); + + return 0; +} + +static const struct spi_device_id pcm3060_spi_id[] = { + { "pcm3060", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, pcm3060_spi_id); + +static const struct of_device_id pcm3060_of_match[] = { + { .compatible = "ti,pcm3060", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3060_of_match); + +static struct spi_driver pcm3060_spi_driver = { + .probe = pcm3060_spi_probe, + .remove = pcm3060_spi_remove, + .id_table = pcm3060_spi_id, + .driver = { + .name = "pcm3060", + .of_match_table = pcm3060_of_match, + .pm = &pcm3060_pm_ops, + }, +}; +module_spi_driver(pcm3060_spi_driver); + +MODULE_DESCRIPTION("PCM3060 SPI codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c new file mode 100644 index 00000000000000..e92ac0aaa0d188 --- /dev/null +++ b/sound/soc/codecs/pcm3060.c @@ -0,0 +1,636 @@ +/* + * PCM3060 codec driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pcm3060.h" + +#define PCM3060_FORMATS (SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define PCM3060_FMT_I2S 0x0 +#define PCM3060_FMT_LEFT_J 0x1 +#define PCM3060_FMT_RIGHT_J 0x2 + +#define PCM3060_NUM_SUPPLIES 2 +static const char *const pcm3060_supply_names[PCM3060_NUM_SUPPLIES] = { + "VCC", + "VDD" +}; + +struct pcm3060_priv { + struct regulator_bulk_data supplies[PCM3060_NUM_SUPPLIES]; + struct regmap *regmap; + struct clk *scki_dac; + struct clk *scki_adc; + bool adc_slave_mode; + bool dac_slave_mode; + unsigned long sysclk_dac; + unsigned long sysclk_adc; + unsigned int adc_fmt; + unsigned int dac_fmt; +}; + +static const char *const pcm3060_over_mult[] = { "1", "2" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_over_mult, PCM3060_DAC_OV_PH_MUTE, + PCM3060_DAC_OVER_SHIFT, pcm3060_over_mult); + +static const char *const pcm3060_roll_off[] = { "Sharp", "Slow" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_roll_off, PCM3060_DAC_FLT_DEMP_Z, + PCM3060_DAC_FLT_SHIFT, pcm3060_roll_off); + +static const char *const pcm3060_demp[] = { "44.1khz", "48khz", "32khz" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_demp, PCM3060_DAC_FLT_DEMP_Z, + PCM3060_DAC_DMF_SHIFT, pcm3060_demp); + +static const char *const pcm3060_zf_func[] = { "Individual", "Joined" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_zf_func, PCM3060_DAC_FLT_DEMP_Z, + PCM3060_DAC_AZRO_SHIFT, pcm3060_zf_func); + +static const char *const pcm3060_pol[] = { "Active High", "Active Low" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_zf_pol, PCM3060_DAC_FLT_DEMP_Z, + PCM3060_DAC_ZREV_SHIFT, pcm3060_pol); + +static const char *const pcm3060_con[] = { "Differential", "Single-Ended" }; + +static SOC_ENUM_SINGLE_DECL(pcm3060_dac_con, PCM3060_RST_PWR_SE, + PCM3060_SE_SHIFT, pcm3060_con); + +/* -100db to 0db, register values 0-54 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3060_dac_tlv, -10050, 50, 1); + +/* -100db to 20db, register values 0-14 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3060_adc_tlv, -10050, 50, 1); + +static const struct snd_kcontrol_new pcm3060_snd_controls[] = { + SOC_ENUM("DAC Connection Type", pcm3060_dac_con), + SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", + PCM3060_DAC_VOL_L, PCM3060_DAC_VOL_R, + 0, 54, 255, 0, pcm3060_dac_tlv), + SOC_ENUM("DAC Oversampling Rate Multiplier", pcm3060_dac_over_mult), + SOC_SINGLE("DAC Invert Switch", PCM3060_DAC_OV_PH_MUTE, + PCM3060_DAC_DREV_SHIFT, 1, 0), + SOC_ENUM("DAC Digital Filter roll-off", pcm3060_dac_roll_off), + SOC_SINGLE("DAC De-Emphasis Switch", PCM3060_DAC_FLT_DEMP_Z, + PCM3060_DAC_DMC_SHIFT, 1, 0), + SOC_ENUM("DAC De-Emphasis Type", pcm3060_dac_demp), + SOC_ENUM("DAC Zero Flag Polarity", pcm3060_dac_zf_pol), + SOC_ENUM("DAC Zero Flag Function", pcm3060_dac_zf_func), + SOC_DOUBLE_R_RANGE_TLV("ADC Capture Volume", + PCM3060_ADC_VOL_L, PCM3060_ADC_VOL_R, + 0, 14, 255, 0, pcm3060_adc_tlv), + SOC_SINGLE("ADC Zero-Cross Detection Switch", PCM3060_ADC_OPT, + PCM3060_ADC_ZCDD_SHIFT, 1, 1), + SOC_SINGLE("ADC High-Pass Filter Switch", PCM3060_ADC_OPT, + PCM3060_ADC_BYP_SHIFT, 1, 1), + SOC_SINGLE("ADC Invert Switch", PCM3060_ADC_OPT, + PCM3060_ADC_DREV_SHIFT, 1, 0), + SOC_DOUBLE("ADC Mute Switch", PCM3060_ADC_OPT, + PCM3060_ADC_MUTE_L_SHIFT, PCM3060_ADC_MUTE_R_SHIFT, + 1, 0), +}; + +static const struct snd_soc_dapm_widget pcm3060_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", PCM3060_RST_PWR_SE, + PCM3060_DAPSV_SHIFT, 1), + + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + + SND_SOC_DAPM_ADC("ADC", "Capture", PCM3060_RST_PWR_SE, + PCM3060_ADPSV_SHIFT, 1), + + SND_SOC_DAPM_INPUT("AINL"), + SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route pcm3060_dapm_routes[] = { + /* Playback */ + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, + + /* Capture */ + { "ADC", NULL, "AINL" }, + { "ADC", NULL, "AINR" }, +}; + +static unsigned int pcm3060_scki_ratios[] = { + 768, + 512, + 384, + 256, + 192, + 128 +}; + +#define PCM3060_NUM_SCKI_RATIOS_DAC ARRAY_SIZE(pcm3060_scki_ratios) +#define PCM3060_NUM_SCKI_RATIOS_ADC (ARRAY_SIZE(pcm3060_scki_ratios) - 2) + +#define PCM1368A_MAX_SYSCLK 36864000 + +static int pcm3060_reset(struct pcm3060_priv *pcm3060) +{ + int ret; + u32 mask = PCM3060_MRST_MASK | PCM3060_SRST_MASK; + unsigned long sysclk = min(pcm3060->sysclk_dac, pcm3060->sysclk_adc); + + ret = regmap_update_bits(pcm3060->regmap, PCM3060_RST_PWR_SE, mask, 0); + if (ret) + return ret; + + /* Internal reset is de-asserted after 1024 cycles of both SCKIs */ + msleep(DIV_ROUND_UP(1024 * 1000, sysclk)); + + return regmap_update_bits(pcm3060->regmap, PCM3060_RST_PWR_SE, + mask, mask); +} + +static int pcm3060_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct pcm3060_priv *pcm3060 = snd_soc_codec_get_drvdata(dai->codec); + u32 mask = PCM3060_DAC_MUTE_R_MASK | PCM3060_DAC_MUTE_L_MASK; + + regmap_update_bits(pcm3060->regmap, PCM3060_DAC_OV_PH_MUTE, + mask, mute ? mask : 0); + + return 0; +} + +static int pcm3060_set_dai_sysclk_dac(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct pcm3060_priv *pcm3060 = snd_soc_codec_get_drvdata(dai->codec); + + if (freq > PCM1368A_MAX_SYSCLK) + return -EINVAL; + + pcm3060->sysclk_dac = freq; + + return 0; +} + +static int pcm3060_set_dai_sysclk_adc(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct pcm3060_priv *pcm3060 = snd_soc_codec_get_drvdata(dai->codec); + + if (freq > PCM1368A_MAX_SYSCLK) + return -EINVAL; + + pcm3060->sysclk_adc = freq; + + return 0; +} + +static int pcm3060_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format, bool dac) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm3060_priv *pcm3060 = snd_soc_codec_get_drvdata(codec); + u32 fmt, reg, mask, shift; + bool slave_mode; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + fmt = PCM3060_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_I2S: + fmt = PCM3060_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = PCM3060_FMT_RIGHT_J; + break; + default: + dev_err(codec->dev, "unsupported dai format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + slave_mode = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + slave_mode = false; + break; + default: + dev_err(codec->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(codec->dev, "LRCLK/BCLK inversion not supported\n"); + return -EINVAL; + } + + if (dac) { + reg = PCM3060_DAC_FMT; + mask = PCM3060_DAC_FMT_MASK; + shift = PCM3060_DAC_FMT_SHIFT; + pcm3060->dac_slave_mode = slave_mode; + pcm3060->dac_fmt = fmt; + } else { + reg = PCM3060_ADC_FMT; + mask = PCM3060_ADC_FMT_MASK; + shift = PCM3060_ADC_FMT_SHIFT; + pcm3060->adc_slave_mode = slave_mode; + pcm3060->adc_fmt = fmt; + } + + regmap_update_bits(pcm3060->regmap, reg, mask, fmt << shift); + + return 0; +} + +static int pcm3060_set_dai_fmt_dac(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3060_set_dai_fmt(dai, format, true); +} + +static int pcm3060_set_dai_fmt_adc(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3060_set_dai_fmt(dai, format, false); +} + +static int pcm3060_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm3060_priv *pcm3060 = snd_soc_codec_get_drvdata(codec); + u32 val, mask, shift, reg; + bool slave_mode, tx; + unsigned int fmt, rate, channels, max_ratio, ratio; + int i; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + max_ratio = PCM3060_NUM_SCKI_RATIOS_DAC; + reg = PCM3060_DAC_FMT; + mask = PCM3060_DAC_MS_MASK; + shift = PCM3060_DAC_MS_SHIFT; + ratio = pcm3060->sysclk_dac / rate; + slave_mode = pcm3060->dac_slave_mode; + fmt = pcm3060->dac_fmt; + } else { + max_ratio = PCM3060_NUM_SCKI_RATIOS_ADC; + reg = PCM3060_ADC_FMT; + mask = PCM3060_ADC_MS_MASK; + shift = PCM3060_ADC_MS_SHIFT; + ratio = pcm3060->sysclk_adc / rate; + slave_mode = pcm3060->adc_slave_mode; + fmt = pcm3060->adc_fmt; + } + + for (i = 0; i < max_ratio; i++) { + if (pcm3060_scki_ratios[i] == ratio) + break; + } + + if (i == max_ratio) { + dev_err(codec->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + if (!slave_mode && (format == SNDRV_PCM_FORMAT_S24_3LE)) { + dev_err(codec->dev, + "48-bit frames not supported in master mode\n"); + return -EINVAL; + } + + val = slave_mode ? 0 : ((i + 1) << shift); + + regmap_update_bits(pcm3060->regmap, reg, mask, val); + + if (tx) { + mask = PCM3060_DAC_FMT_MASK; + shift = PCM3060_DAC_FMT_SHIFT; + } else { + mask = PCM3060_ADC_FMT_MASK; + shift = PCM3060_ADC_FMT_SHIFT; + } + + if ((fmt == PCM3060_FMT_RIGHT_J) && (format == SNDRV_PCM_FORMAT_S32)) { + /* + * Justification has no effect here as the whole frame + * is filled with the samples, but the register field + * must be set to left justified for correct operation + */ + fmt = PCM3060_FMT_LEFT_J; + } + + regmap_update_bits(pcm3060->regmap, reg, mask, fmt << shift); + + return 0; +} + +static const struct snd_soc_dai_ops pcm3060_dac_dai_ops = { + .set_fmt = pcm3060_set_dai_fmt_dac, + .set_sysclk = pcm3060_set_dai_sysclk_dac, + .hw_params = pcm3060_hw_params, + .digital_mute = pcm3060_digital_mute, +}; + +static const struct snd_soc_dai_ops pcm3060_adc_dai_ops = { + .set_fmt = pcm3060_set_dai_fmt_adc, + .set_sysclk = pcm3060_set_dai_sysclk_adc, + .hw_params = pcm3060_hw_params, +}; + +static struct snd_soc_dai_driver pcm3060_dais[] = { + { + .name = "pcm3060-dac", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM3060_FORMATS, + }, + .ops = &pcm3060_dac_dai_ops, + }, + { + .name = "pcm3060-adc", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = PCM3060_FORMATS, + }, + .ops = &pcm3060_adc_dai_ops, + }, +}; + +static const struct reg_default pcm3060_reg_default[] = { + { PCM3060_RST_PWR_SE, PCM3060_MRST_MASK | PCM3060_SRST_MASK | + PCM3060_ADPSV_MASK | PCM3060_DAPSV_MASK }, + { PCM3060_DAC_VOL_L, 0xff }, + { PCM3060_DAC_VOL_R, 0xff }, + { PCM3060_DAC_FMT, 0x00 }, + { PCM3060_DAC_OV_PH_MUTE, 0x00 }, + { PCM3060_DAC_FLT_DEMP_Z, 0x00 }, + { PCM3060_ADC_VOL_L, 0xd7 }, + { PCM3060_ADC_VOL_R, 0xd7 }, + { PCM3060_ADC_FMT, 0x00 }, + { PCM3060_ADC_OPT, 0x00 }, +}; + +static bool pcm3060_volatile_register(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool pcm3060_readable_register(struct device *dev, unsigned int reg) +{ + if (reg >= PCM3060_RST_PWR_SE) + return true; + else + return false; +} + +static bool pcm3060_writeable_register(struct device *dev, unsigned int reg) +{ + if (reg >= PCM3060_RST_PWR_SE) + return true; + else + return false; +} + +const struct regmap_config pcm3060_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = PCM3060_ADC_OPT, + .reg_defaults = pcm3060_reg_default, + .num_reg_defaults = ARRAY_SIZE(pcm3060_reg_default), + .readable_reg = pcm3060_readable_register, + .volatile_reg = pcm3060_volatile_register, + .writeable_reg = pcm3060_writeable_register, + .cache_type = REGCACHE_FLAT, +}; +EXPORT_SYMBOL_GPL(pcm3060_regmap); + +static const struct snd_soc_codec_driver pcm3060_driver = { + .controls = pcm3060_snd_controls, + .num_controls = ARRAY_SIZE(pcm3060_snd_controls), + .dapm_widgets = pcm3060_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm3060_dapm_widgets), + .dapm_routes = pcm3060_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm3060_dapm_routes), +}; + +int pcm3060_probe(struct device *dev, struct regmap *regmap) +{ + struct pcm3060_priv *pcm3060; + int ret, i; + + pcm3060 = devm_kzalloc(dev, sizeof(*pcm3060), GFP_KERNEL); + if (pcm3060 == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, pcm3060); + + pcm3060->scki_dac = devm_clk_get(dev, "sckid"); + if (IS_ERR(pcm3060->scki_dac)) { + dev_err(dev, "failed to get the clock (dac): %ld\n", + PTR_ERR(pcm3060->scki_dac)); + return PTR_ERR(pcm3060->scki_dac); + } + + ret = clk_prepare_enable(pcm3060->scki_dac); + if (ret) { + dev_err(dev, "failed to enable mclk (dac): %d\n", ret); + return ret; + } + + pcm3060->sysclk_dac = clk_get_rate(pcm3060->scki_dac); + + pcm3060->scki_adc = devm_clk_get(dev, "sckia"); + if (IS_ERR(pcm3060->scki_adc)) { + dev_err(dev, "failed to get the clock (adc): %ld\n", + PTR_ERR(pcm3060->scki_adc)); + ret = PTR_ERR(pcm3060->scki_adc); + goto err_clk; + } + + ret = clk_prepare_enable(pcm3060->scki_adc); + if (ret) { + dev_err(dev, "failed to enable mclk (adc): %d\n", ret); + goto err_clk; + } + + pcm3060->sysclk_adc = clk_get_rate(pcm3060->scki_adc); + + for (i = 0; i < ARRAY_SIZE(pcm3060->supplies); i++) + pcm3060->supplies[i].supply = pcm3060_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(pcm3060->supplies), pcm3060->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + goto err_clks; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3060->supplies), + pcm3060->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clks; + } + + pcm3060->regmap = regmap; + if (IS_ERR(pcm3060->regmap)) { + ret = PTR_ERR(pcm3060->regmap); + dev_err(dev, "failed to allocate regmap: %d\n", ret); + goto err_regulator; + } + + ret = pcm3060_reset(pcm3060); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + ret = snd_soc_register_codec(dev, &pcm3060_driver, pcm3060_dais, + ARRAY_SIZE(pcm3060_dais)); + if (ret) { + dev_err(dev, "failed to register codec:%d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3060->supplies), + pcm3060->supplies); +err_clks: + clk_disable_unprepare(pcm3060->scki_adc); +err_clk: + clk_disable_unprepare(pcm3060->scki_dac); + + return ret; +} +EXPORT_SYMBOL_GPL(pcm3060_probe); + +void pcm3060_remove(struct device *dev) +{ + snd_soc_unregister_codec(dev); + pm_runtime_disable(dev); +} +EXPORT_SYMBOL_GPL(pcm3060_remove); + +#ifdef CONFIG_PM +static int pcm3060_rt_resume(struct device *dev) +{ + struct pcm3060_priv *pcm3060 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(pcm3060->scki_dac); + if (ret) { + dev_err(dev, "failed to enable mclk (dac): %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pcm3060->scki_adc); + if (ret) { + dev_err(dev, "failed to enable mclk (adc): %d\n", ret); + goto err_clk; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3060->supplies), + pcm3060->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clks; + } + + ret = pcm3060_reset(pcm3060); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + regcache_cache_only(pcm3060->regmap, false); + + regcache_mark_dirty(pcm3060->regmap); + + ret = regcache_sync(pcm3060->regmap); + if (ret) { + dev_err(dev, "failed to sync regmap: %d\n", ret); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3060->supplies), + pcm3060->supplies); +err_clks: + clk_disable_unprepare(pcm3060->scki_adc); +err_clk: + clk_disable_unprepare(pcm3060->scki_dac); + + return ret; +} + +static int pcm3060_rt_suspend(struct device *dev) +{ + struct pcm3060_priv *pcm3060 = dev_get_drvdata(dev); + + regcache_cache_only(pcm3060->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(pcm3060->supplies), + pcm3060->supplies); + + clk_disable_unprepare(pcm3060->scki_adc); + clk_disable_unprepare(pcm3060->scki_dac); + + return 0; +} +#endif + +const struct dev_pm_ops pcm3060_pm_ops = { + SET_RUNTIME_PM_OPS(pcm3060_rt_suspend, pcm3060_rt_resume, NULL) +}; +EXPORT_SYMBOL_GPL(pcm3060_pm_ops); + +MODULE_DESCRIPTION("PCM3060 codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3060.h b/sound/soc/codecs/pcm3060.h new file mode 100644 index 00000000000000..1de2e6ad6fa983 --- /dev/null +++ b/sound/soc/codecs/pcm3060.h @@ -0,0 +1,86 @@ +/* + * PCM3060 codec driver header + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#ifndef __PCM3060_H__ +#define __PCM3060_H__ + +extern const struct dev_pm_ops pcm3060_pm_ops; +extern const struct regmap_config pcm3060_regmap; + +extern int pcm3060_probe(struct device *dev, struct regmap *regmap); +extern void pcm3060_remove(struct device *dev); + +#define PCM3060_RST_PWR_SE 0x40 +#define PCM3060_MRST_MASK 0x80 +#define PCM3060_SRST_MASK 0x40 +#define PCM3060_ADPSV_SHIFT 5 +#define PCM3060_ADPSV_MASK 0x20 +#define PCM3060_DAPSV_SHIFT 4 +#define PCM3060_DAPSV_MASK 0x10 +#define PCM3060_SE_SHIFT 0 +#define PCM3060_SE_MASK 0x01 + +#define PCM3060_DAC_VOL_L 0x41 + +#define PCM3060_DAC_VOL_R 0x42 + +#define PCM3060_DAC_FMT 0x43 +#define PCM3060_DAC_CSEL_MASK 0x80 +#define PCM3060_DAC_MS_SHIFT 4 +#define PCM3060_DAC_MS_MASK 0x70 +#define PCM3060_DAC_FMT_SHIFT 0 +#define PCM3060_DAC_FMT_MASK 0x3 + +#define PCM3060_DAC_OV_PH_MUTE 0x44 +#define PCM3060_DAC_OVER_SHIFT 6 +#define PCM3060_DAC_OVER_MASK 0x40 +#define PCM3060_DAC_DREV_SHIFT 2 +#define PCM3060_DAC_DREV_MASK 0x4 +#define PCM3060_DAC_MUTE_R_MASK 0x2 +#define PCM3060_DAC_MUTE_L_MASK 0x1 + +#define PCM3060_DAC_FLT_DEMP_Z 0x45 +#define PCM3060_DAC_FLT_SHIFT 7 +#define PCM3060_DAC_FLT_MASK 0x80 +#define PCM3060_DAC_DMF_SHIFT 5 +#define PCM3060_DAC_DMF_MASK 0x60 +#define PCM3060_DAC_DMC_SHIFT 4 +#define PCM3060_DAC_DMC_MASK 0x10 +#define PCM3060_DAC_ZREV_SHIFT 1 +#define PCM3060_DAC_ZREV_MASK 0x2 +#define PCM3060_DAC_AZRO_SHIFT 0 +#define PCM3060_DAC_AZRO_MASK 0x1 + +#define PCM3060_ADC_VOL_L 0x46 + +#define PCM3060_ADC_VOL_R 0x47 + +#define PCM3060_ADC_FMT 0x48 +#define PCM3060_ADC_CSEL_MASK 0x80 +#define PCM3060_ADC_MS_SHIFT 4 +#define PCM3060_ADC_MS_MASK 0x70 +#define PCM3060_ADC_FMT_SHIFT 0 +#define PCM3060_ADC_FMT_MASK 0x3 + +#define PCM3060_ADC_OPT 0x49 +#define PCM3060_ADC_ZCDD_SHIFT 4 +#define PCM3060_ADC_ZCDD_MASK 0x10 +#define PCM3060_ADC_BYP_SHIFT 3 +#define PCM3060_ADC_BYP_MASK 0x8 +#define PCM3060_ADC_DREV_SHIFT 2 +#define PCM3060_ADC_DREV_MASK 0x4 +#define PCM3060_ADC_MUTE_R_SHIFT 1 +#define PCM3060_ADC_MUTE_R_MASK 0x2 +#define PCM3060_ADC_MUTE_L_SHIFT 0 +#define PCM3060_ADC_MUTE_L_MASK 0x1 + +#endif diff --git a/sound/soc/codecs/pcm3168a-i2c.c b/sound/soc/codecs/pcm3168a-i2c.c new file mode 100644 index 00000000000000..6feb0901dfeba0 --- /dev/null +++ b/sound/soc/codecs/pcm3168a-i2c.c @@ -0,0 +1,66 @@ +/* + * PCM3168A codec i2c driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include + +#include + +#include "pcm3168a.h" + +static int pcm3168a_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &pcm3168a_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3168a_probe(&i2c->dev, regmap); +} + +static int pcm3168a_i2c_remove(struct i2c_client *i2c) +{ + pcm3168a_remove(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id pcm3168a_i2c_id[] = { + { "pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm3168a_i2c_id); + +static const struct of_device_id pcm3168a_of_match[] = { + { .compatible = "ti,pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3168a_of_match); + +static struct i2c_driver pcm3168a_i2c_driver = { + .probe = pcm3168a_i2c_probe, + .remove = pcm3168a_i2c_remove, + .id_table = pcm3168a_i2c_id, + .driver = { + .name = "pcm3168a", + .of_match_table = pcm3168a_of_match, + .pm = &pcm3168a_pm_ops, + }, +}; +module_i2c_driver(pcm3168a_i2c_driver); + +MODULE_DESCRIPTION("PCM3168A I2C codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a-spi.c b/sound/soc/codecs/pcm3168a-spi.c new file mode 100644 index 00000000000000..03945a27ae405a --- /dev/null +++ b/sound/soc/codecs/pcm3168a-spi.c @@ -0,0 +1,65 @@ +/* + * PCM3168A codec spi driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include + +#include + +#include "pcm3168a.h" + +static int pcm3168a_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &pcm3168a_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3168a_probe(&spi->dev, regmap); +} + +static int pcm3168a_spi_remove(struct spi_device *spi) +{ + pcm3168a_remove(&spi->dev); + + return 0; +} + +static const struct spi_device_id pcm3168a_spi_id[] = { + { "pcm3168a", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, pcm3168a_spi_id); + +static const struct of_device_id pcm3168a_of_match[] = { + { .compatible = "ti,pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3168a_of_match); + +static struct spi_driver pcm3168a_spi_driver = { + .probe = pcm3168a_spi_probe, + .remove = pcm3168a_spi_remove, + .id_table = pcm3168a_spi_id, + .driver = { + .name = "pcm3168a", + .of_match_table = pcm3168a_of_match, + .pm = &pcm3168a_pm_ops, + }, +}; +module_spi_driver(pcm3168a_spi_driver); + +MODULE_DESCRIPTION("PCM3168A SPI codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c new file mode 100644 index 00000000000000..0c7248ab6a3744 --- /dev/null +++ b/sound/soc/codecs/pcm3168a.c @@ -0,0 +1,772 @@ +/* + * PCM3168A codec driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pcm3168a.h" + +#define PCM3168A_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define PCM3168A_FMT_I2S 0x0 +#define PCM3168A_FMT_LEFT_J 0x1 +#define PCM3168A_FMT_RIGHT_J 0x2 +#define PCM3168A_FMT_RIGHT_J_16 0x3 +#define PCM3168A_FMT_DSP_A 0x4 +#define PCM3168A_FMT_DSP_B 0x5 +#define PCM3168A_FMT_DSP_MASK 0x4 + +#define PCM3168A_NUM_SUPPLIES 6 +static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = { + "VDD1", + "VDD2", + "VCCAD1", + "VCCAD2", + "VCCDA1", + "VCCDA2" +}; + +struct pcm3168a_priv { + struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES]; + struct regmap *regmap; + struct clk *scki; + bool adc_master_mode; + bool dac_master_mode; + unsigned long sysclk; + unsigned int adc_fmt; + unsigned int dac_fmt; +}; + +static const char *const pcm3168a_roll_off[] = { "Sharp", "Slow" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_d1_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d2_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 1, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d3_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 2, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d4_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 3, pcm3168a_roll_off); + +static const char *const pcm3168a_volume_type[] = { + "Individual", "Master + Individual" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_volume_type, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATMDDA_SHIFT, pcm3168a_volume_type); + +static const char *const pcm3168a_att_speed_mult[] = { "2048", "4096" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_att_mult, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_att_speed_mult); + +static const char *const pcm3168a_demp[] = { + "Disabled", "48khz", "44.1khz", "32khz" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_demp, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_DEMP_SHIFT, pcm3168a_demp); + +static const char *const pcm3168a_zf_func[] = { + "DAC 1/2/3/4 AND", "DAC 1/2/3/4 OR", "DAC 1/2/3 AND", + "DAC 1/2/3 OR", "DAC 4 AND", "DAC 4 OR" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_AZRO_SHIFT, pcm3168a_zf_func); + +static const char *const pcm3168a_pol[] = { "Active High", "Active Low" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol); + +static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" }; + +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc1_con, PCM3168A_ADC_SEAD, + 0, 1, pcm3168a_con); +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc2_con, PCM3168A_ADC_SEAD, + 2, 3, pcm3168a_con); +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc3_con, PCM3168A_ADC_SEAD, + 4, 5, pcm3168a_con); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_volume_type, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_ATMDAD_SHIFT, pcm3168a_volume_type); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_att_mult, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_ATSPAD_SHIFT, pcm3168a_att_speed_mult); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_ov_pol, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_OVFP_SHIFT, pcm3168a_pol); + +/* -100db to 0db, register values 0-54 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3168a_dac_tlv, -10050, 50, 1); + +/* -100db to 20db, register values 0-14 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3168a_adc_tlv, -10050, 50, 1); + +static const struct snd_kcontrol_new pcm3168a_snd_controls[] = { + SOC_SINGLE("DAC Power-Save Switch", PCM3168A_DAC_PWR_MST_FMT, + PCM3168A_DAC_PSMDA_SHIFT, 1, 1), + SOC_ENUM("DAC1 Digital Filter roll-off", pcm3168a_d1_roll_off), + SOC_ENUM("DAC2 Digital Filter roll-off", pcm3168a_d2_roll_off), + SOC_ENUM("DAC3 Digital Filter roll-off", pcm3168a_d3_roll_off), + SOC_ENUM("DAC4 Digital Filter roll-off", pcm3168a_d4_roll_off), + SOC_DOUBLE("DAC1 Invert Switch", PCM3168A_DAC_INV, 0, 1, 1, 0), + SOC_DOUBLE("DAC2 Invert Switch", PCM3168A_DAC_INV, 2, 3, 1, 0), + SOC_DOUBLE("DAC3 Invert Switch", PCM3168A_DAC_INV, 4, 5, 1, 0), + SOC_DOUBLE("DAC4 Invert Switch", PCM3168A_DAC_INV, 6, 7, 1, 0), + SOC_DOUBLE_STS("DAC1 Zero Flag", PCM3168A_DAC_ZERO, 0, 1, 1, 0), + SOC_DOUBLE_STS("DAC2 Zero Flag", PCM3168A_DAC_ZERO, 2, 3, 1, 0), + SOC_DOUBLE_STS("DAC3 Zero Flag", PCM3168A_DAC_ZERO, 4, 5, 1, 0), + SOC_DOUBLE_STS("DAC4 Zero Flag", PCM3168A_DAC_ZERO, 6, 7, 1, 0), + SOC_ENUM("DAC Volume Control Type", pcm3168a_dac_volume_type), + SOC_ENUM("DAC Volume Rate Multiplier", pcm3168a_dac_att_mult), + SOC_ENUM("DAC De-Emphasis", pcm3168a_dac_demp), + SOC_ENUM("DAC Zero Flag Function", pcm3168a_dac_zf_func), + SOC_ENUM("DAC Zero Flag Polarity", pcm3168a_dac_zf_pol), + SOC_SINGLE_RANGE_TLV("Master Playback Volume", + PCM3168A_DAC_VOL_MASTER, 0, 54, 255, 0, + pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC1 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START, + PCM3168A_DAC_VOL_CHAN_START + 1, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC2 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 2, + PCM3168A_DAC_VOL_CHAN_START + 3, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC3 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 4, + PCM3168A_DAC_VOL_CHAN_START + 5, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC4 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 6, + PCM3168A_DAC_VOL_CHAN_START + 7, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_SINGLE("ADC1 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT, 1, 1), + SOC_SINGLE("ADC2 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT + 1, 1, 1), + SOC_SINGLE("ADC3 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT + 2, 1, 1), + SOC_ENUM("ADC1 Connection Type", pcm3168a_adc1_con), + SOC_ENUM("ADC2 Connection Type", pcm3168a_adc2_con), + SOC_ENUM("ADC3 Connection Type", pcm3168a_adc3_con), + SOC_DOUBLE("ADC1 Invert Switch", PCM3168A_ADC_INV, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Invert Switch", PCM3168A_ADC_INV, 2, 3, 1, 0), + SOC_DOUBLE("ADC3 Invert Switch", PCM3168A_ADC_INV, 4, 5, 1, 0), + SOC_DOUBLE("ADC1 Mute Switch", PCM3168A_ADC_MUTE, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Mute Switch", PCM3168A_ADC_MUTE, 2, 3, 1, 0), + SOC_DOUBLE("ADC3 Mute Switch", PCM3168A_ADC_MUTE, 4, 5, 1, 0), + SOC_DOUBLE_STS("ADC1 Overflow Flag", PCM3168A_ADC_OV, 0, 1, 1, 0), + SOC_DOUBLE_STS("ADC2 Overflow Flag", PCM3168A_ADC_OV, 2, 3, 1, 0), + SOC_DOUBLE_STS("ADC3 Overflow Flag", PCM3168A_ADC_OV, 4, 5, 1, 0), + SOC_ENUM("ADC Volume Control Type", pcm3168a_adc_volume_type), + SOC_ENUM("ADC Volume Rate Multiplier", pcm3168a_adc_att_mult), + SOC_ENUM("ADC Overflow Flag Polarity", pcm3168a_adc_ov_pol), + SOC_SINGLE_RANGE_TLV("Master Capture Volume", + PCM3168A_ADC_VOL_MASTER, 0, 14, 255, 0, + pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC1 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START, + PCM3168A_ADC_VOL_CHAN_START + 1, + 0, 14, 255, 0, pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC2 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START + 2, + PCM3168A_ADC_VOL_CHAN_START + 3, + 0, 14, 255, 0, pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC3 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START + 4, + PCM3168A_ADC_VOL_CHAN_START + 5, + 0, 14, 255, 0, pcm3168a_adc_tlv) +}; + +static const struct snd_soc_dapm_widget pcm3168a_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC1", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 1, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 2, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 3, 1), + + SND_SOC_DAPM_OUTPUT("AOUT1L"), + SND_SOC_DAPM_OUTPUT("AOUT1R"), + SND_SOC_DAPM_OUTPUT("AOUT2L"), + SND_SOC_DAPM_OUTPUT("AOUT2R"), + SND_SOC_DAPM_OUTPUT("AOUT3L"), + SND_SOC_DAPM_OUTPUT("AOUT3R"), + SND_SOC_DAPM_OUTPUT("AOUT4L"), + SND_SOC_DAPM_OUTPUT("AOUT4R"), + + SND_SOC_DAPM_ADC("ADC1", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT + 1, 1), + SND_SOC_DAPM_ADC("ADC3", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT + 2, 1), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R") +}; + +static const struct snd_soc_dapm_route pcm3168a_dapm_routes[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, + + { "ADC3", NULL, "AIN3L" }, + { "ADC3", NULL, "AIN3R" } +}; + +static unsigned int pcm3168a_scki_ratios[] = { + 768, + 512, + 384, + 256, + 192, + 128 +}; + +#define PCM3168A_NUM_SCKI_RATIOS_DAC ARRAY_SIZE(pcm3168a_scki_ratios) +#define PCM3168A_NUM_SCKI_RATIOS_ADC (ARRAY_SIZE(pcm3168a_scki_ratios) - 2) + +#define PCM1368A_MAX_SYSCLK 36864000 + +static int pcm3168a_reset(struct pcm3168a_priv *pcm3168a) +{ + int ret; + + ret = regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, 0); + if (ret) + return ret; + + /* Internal reset is de-asserted after 3846 SCKI cycles */ + msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk)); + + return regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, + PCM3168A_MRST_MASK | PCM3168A_SRST_MASK); +} + +static int pcm3168a_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); + + regmap_write(pcm3168a->regmap, PCM3168A_DAC_MUTE, mute ? 0xff : 0); + + return 0; +} + +static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(dai->codec); + int ret; + + if (freq > PCM1368A_MAX_SYSCLK) + return -EINVAL; + + ret = clk_set_rate(pcm3168a->scki, freq); + if (ret) + return ret; + + pcm3168a->sysclk = freq; + + return 0; +} + +static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format, bool dac) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); + u32 fmt, reg, mask, shift; + bool master_mode; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + fmt = PCM3168A_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_I2S: + fmt = PCM3168A_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = PCM3168A_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + fmt = PCM3168A_FMT_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + fmt = PCM3168A_FMT_DSP_B; + break; + default: + dev_err(codec->dev, "unsupported dai format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + master_mode = false; + break; + case SND_SOC_DAIFMT_CBM_CFM: + master_mode = true; + break; + default: + dev_err(codec->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + if (dac) { + reg = PCM3168A_DAC_PWR_MST_FMT; + mask = PCM3168A_DAC_FMT_MASK; + shift = PCM3168A_DAC_FMT_SHIFT; + pcm3168a->dac_master_mode = master_mode; + pcm3168a->dac_fmt = fmt; + } else { + reg = PCM3168A_ADC_MST_FMT; + mask = PCM3168A_ADC_FMTAD_MASK; + shift = PCM3168A_ADC_FMTAD_SHIFT; + pcm3168a->adc_master_mode = master_mode; + pcm3168a->adc_fmt = fmt; + } + + regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); + + return 0; +} + +static int pcm3168a_set_dai_fmt_dac(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3168a_set_dai_fmt(dai, format, true); +} + +static int pcm3168a_set_dai_fmt_adc(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3168a_set_dai_fmt(dai, format, false); +} + +static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct pcm3168a_priv *pcm3168a = snd_soc_codec_get_drvdata(codec); + bool tx, master_mode; + u32 val, mask, shift, reg; + unsigned int rate, channels, fmt, ratio, max_ratio; + int i, min_frame_size; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + + ratio = pcm3168a->sysclk / rate; + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_DAC; + reg = PCM3168A_DAC_PWR_MST_FMT; + mask = PCM3168A_DAC_MSDA_MASK; + shift = PCM3168A_DAC_MSDA_SHIFT; + master_mode = pcm3168a->dac_master_mode; + fmt = pcm3168a->dac_fmt; + } else { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_ADC; + reg = PCM3168A_ADC_MST_FMT; + mask = PCM3168A_ADC_MSAD_MASK; + shift = PCM3168A_ADC_MSAD_SHIFT; + master_mode = pcm3168a->adc_master_mode; + fmt = pcm3168a->adc_fmt; + } + + for (i = 0; i < max_ratio; i++) { + if (pcm3168a_scki_ratios[i] == ratio) + break; + } + + if (i == max_ratio) { + dev_err(codec->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + min_frame_size = params_width(params) * 2; + switch (min_frame_size) { + case 32: + if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { + dev_err(codec->dev, "32-bit frames are supported only for slave mode using right justified\n"); + return -EINVAL; + } + fmt = PCM3168A_FMT_RIGHT_J_16; + break; + case 48: + if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) { + dev_err(codec->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); + return -EINVAL; + } + break; + case 64: + break; + default: + dev_err(codec->dev, "unsupported frame size: %d\n", min_frame_size); + return -EINVAL; + } + + if (master_mode) + val = ((i + 1) << shift); + else + val = 0; + + regmap_update_bits(pcm3168a->regmap, reg, mask, val); + + if (tx) { + mask = PCM3168A_DAC_FMT_MASK; + shift = PCM3168A_DAC_FMT_SHIFT; + } else { + mask = PCM3168A_ADC_FMTAD_MASK; + shift = PCM3168A_ADC_FMTAD_SHIFT; + } + + regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); + + return 0; +} + +static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_dac, + .set_sysclk = pcm3168a_set_dai_sysclk, + .hw_params = pcm3168a_hw_params, + .digital_mute = pcm3168a_digital_mute +}; + +static const struct snd_soc_dai_ops pcm3168a_adc_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_adc, + .set_sysclk = pcm3168a_set_dai_sysclk, + .hw_params = pcm3168a_hw_params +}; + +static struct snd_soc_dai_driver pcm3168a_dais[] = { + { + .name = "pcm3168a-dac", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM3168A_FORMATS + }, + .ops = &pcm3168a_dac_dai_ops + }, + { + .name = "pcm3168a-adc", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = PCM3168A_FORMATS + }, + .ops = &pcm3168a_adc_dai_ops + }, +}; + +static const struct reg_default pcm3168a_reg_default[] = { + { PCM3168A_RST_SMODE, PCM3168A_MRST_MASK | PCM3168A_SRST_MASK }, + { PCM3168A_DAC_PWR_MST_FMT, 0x00 }, + { PCM3168A_DAC_OP_FLT, 0x00 }, + { PCM3168A_DAC_INV, 0x00 }, + { PCM3168A_DAC_MUTE, 0x00 }, + { PCM3168A_DAC_ZERO, 0x00 }, + { PCM3168A_DAC_ATT_DEMP_ZF, 0x00 }, + { PCM3168A_DAC_VOL_MASTER, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 1, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 2, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 3, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 4, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 5, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 6, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 7, 0xff }, + { PCM3168A_ADC_SMODE, 0x00 }, + { PCM3168A_ADC_MST_FMT, 0x00 }, + { PCM3168A_ADC_PWR_HPFB, 0x00 }, + { PCM3168A_ADC_SEAD, 0x00 }, + { PCM3168A_ADC_INV, 0x00 }, + { PCM3168A_ADC_MUTE, 0x00 }, + { PCM3168A_ADC_OV, 0x00 }, + { PCM3168A_ADC_ATT_OVF, 0x00 }, + { PCM3168A_ADC_VOL_MASTER, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 1, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 2, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 3, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 4, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 5, 0xd3 } +}; + +static bool pcm3168a_readable_register(struct device *dev, unsigned int reg) +{ + if (reg >= PCM3168A_RST_SMODE) + return true; + else + return false; +} + +static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM3168A_DAC_ZERO: + case PCM3168A_ADC_OV: + return true; + default: + return false; + } +} + +static bool pcm3168a_writeable_register(struct device *dev, unsigned int reg) +{ + if (reg < PCM3168A_RST_SMODE) + return false; + + switch (reg) { + case PCM3168A_DAC_ZERO: + case PCM3168A_ADC_OV: + return false; + default: + return true; + } +} + +const struct regmap_config pcm3168a_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = PCM3168A_ADC_VOL_CHAN_START + 5, + .reg_defaults = pcm3168a_reg_default, + .num_reg_defaults = ARRAY_SIZE(pcm3168a_reg_default), + .readable_reg = pcm3168a_readable_register, + .volatile_reg = pcm3168a_volatile_register, + .writeable_reg = pcm3168a_writeable_register, + .cache_type = REGCACHE_FLAT +}; +EXPORT_SYMBOL_GPL(pcm3168a_regmap); + +static const struct snd_soc_codec_driver pcm3168a_driver = { + .idle_bias_off = true, + .controls = pcm3168a_snd_controls, + .num_controls = ARRAY_SIZE(pcm3168a_snd_controls), + .dapm_widgets = pcm3168a_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm3168a_dapm_widgets), + .dapm_routes = pcm3168a_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm3168a_dapm_routes) +}; + +int pcm3168a_probe(struct device *dev, struct regmap *regmap) +{ + struct pcm3168a_priv *pcm3168a; + int ret, i; + + pcm3168a = devm_kzalloc(dev, sizeof(*pcm3168a), GFP_KERNEL); + if (pcm3168a == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, pcm3168a); + + pcm3168a->scki = devm_clk_get(dev, "scki"); + if (IS_ERR(pcm3168a->scki)) { + ret = PTR_ERR(pcm3168a->scki); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to acquire clock 'scki': %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pcm3168a->scki); + if (ret) { + dev_err(dev, "Failed to enable mclk: %d\n", ret); + return ret; + } + + pcm3168a->sysclk = clk_get_rate(pcm3168a->scki); + + for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++) + pcm3168a->supplies[i].supply = pcm3168a_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(pcm3168a->supplies), pcm3168a->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request supplies: %d\n", ret); + goto err_clk; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clk; + } + + pcm3168a->regmap = regmap; + if (IS_ERR(pcm3168a->regmap)) { + ret = PTR_ERR(pcm3168a->regmap); + dev_err(dev, "failed to allocate regmap: %d\n", ret); + goto err_regulator; + } + + ret = pcm3168a_reset(pcm3168a); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = snd_soc_register_codec(dev, &pcm3168a_driver, pcm3168a_dais, + ARRAY_SIZE(pcm3168a_dais)); + if (ret) { + dev_err(dev, "failed to register codec: %d\n", ret); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); +err_clk: + clk_disable_unprepare(pcm3168a->scki); + + return ret; +} +EXPORT_SYMBOL_GPL(pcm3168a_probe); + +void pcm3168a_remove(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + pm_runtime_disable(dev); + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + clk_disable_unprepare(pcm3168a->scki); +} +EXPORT_SYMBOL_GPL(pcm3168a_remove); + +#ifdef CONFIG_PM +static int pcm3168a_rt_resume(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(pcm3168a->scki); + if (ret) { + dev_err(dev, "Failed to enable mclk: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + goto err_clk; + } + + ret = pcm3168a_reset(pcm3168a); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + regcache_cache_only(pcm3168a->regmap, false); + + regcache_mark_dirty(pcm3168a->regmap); + + ret = regcache_sync(pcm3168a->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap: %d\n", ret); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); +err_clk: + clk_disable_unprepare(pcm3168a->scki); + + return ret; +} + +static int pcm3168a_rt_suspend(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + + regcache_cache_only(pcm3168a->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + + clk_disable_unprepare(pcm3168a->scki); + + return 0; +} +#endif + +const struct dev_pm_ops pcm3168a_pm_ops = { + SET_RUNTIME_PM_OPS(pcm3168a_rt_suspend, pcm3168a_rt_resume, NULL) +}; +EXPORT_SYMBOL_GPL(pcm3168a_pm_ops); + +MODULE_DESCRIPTION("PCM3168A codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.h b/sound/soc/codecs/pcm3168a.h new file mode 100644 index 00000000000000..56c8332d82fb7f --- /dev/null +++ b/sound/soc/codecs/pcm3168a.h @@ -0,0 +1,100 @@ +/* + * PCM3168A codec driver header + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#ifndef __PCM3168A_H__ +#define __PCM3168A_H__ + +extern const struct dev_pm_ops pcm3168a_pm_ops; +extern const struct regmap_config pcm3168a_regmap; + +extern int pcm3168a_probe(struct device *dev, struct regmap *regmap); +extern void pcm3168a_remove(struct device *dev); + +#define PCM3168A_RST_SMODE 0x40 +#define PCM3168A_MRST_MASK 0x80 +#define PCM3168A_SRST_MASK 0x40 +#define PCM3168A_DAC_SRDA_SHIFT 0 +#define PCM3168A_DAC_SRDA_MASK 0x3 + +#define PCM3168A_DAC_PWR_MST_FMT 0x41 +#define PCM3168A_DAC_PSMDA_SHIFT 7 +#define PCM3168A_DAC_PSMDA_MASK 0x80 +#define PCM3168A_DAC_MSDA_SHIFT 4 +#define PCM3168A_DAC_MSDA_MASK 0x70 +#define PCM3168A_DAC_FMT_SHIFT 0 +#define PCM3168A_DAC_FMT_MASK 0xf + +#define PCM3168A_DAC_OP_FLT 0x42 +#define PCM3168A_DAC_OPEDA_SHIFT 4 +#define PCM3168A_DAC_OPEDA_MASK 0xf0 +#define PCM3168A_DAC_FLT_SHIFT 0 +#define PCM3168A_DAC_FLT_MASK 0xf + +#define PCM3168A_DAC_INV 0x43 + +#define PCM3168A_DAC_MUTE 0x44 + +#define PCM3168A_DAC_ZERO 0x45 + +#define PCM3168A_DAC_ATT_DEMP_ZF 0x46 +#define PCM3168A_DAC_ATMDDA_MASK 0x80 +#define PCM3168A_DAC_ATMDDA_SHIFT 7 +#define PCM3168A_DAC_ATSPDA_MASK 0x40 +#define PCM3168A_DAC_ATSPDA_SHIFT 6 +#define PCM3168A_DAC_DEMP_SHIFT 4 +#define PCM3168A_DAC_DEMP_MASK 0x30 +#define PCM3168A_DAC_AZRO_SHIFT 1 +#define PCM3168A_DAC_AZRO_MASK 0xe +#define PCM3168A_DAC_ZREV_MASK 0x1 +#define PCM3168A_DAC_ZREV_SHIFT 0 + +#define PCM3168A_DAC_VOL_MASTER 0x47 + +#define PCM3168A_DAC_VOL_CHAN_START 0x48 + +#define PCM3168A_ADC_SMODE 0x50 +#define PCM3168A_ADC_SRAD_SHIFT 0 +#define PCM3168A_ADC_SRAD_MASK 0x3 + +#define PCM3168A_ADC_MST_FMT 0x51 +#define PCM3168A_ADC_MSAD_SHIFT 4 +#define PCM3168A_ADC_MSAD_MASK 0x70 +#define PCM3168A_ADC_FMTAD_SHIFT 0 +#define PCM3168A_ADC_FMTAD_MASK 0x7 + +#define PCM3168A_ADC_PWR_HPFB 0x52 +#define PCM3168A_ADC_PSVAD_SHIFT 4 +#define PCM3168A_ADC_PSVAD_MASK 0x70 +#define PCM3168A_ADC_BYP_SHIFT 0 +#define PCM3168A_ADC_BYP_MASK 0x7 + +#define PCM3168A_ADC_SEAD 0x53 + +#define PCM3168A_ADC_INV 0x54 + +#define PCM3168A_ADC_MUTE 0x55 + +#define PCM3168A_ADC_OV 0x56 + +#define PCM3168A_ADC_ATT_OVF 0x57 +#define PCM3168A_ADC_ATMDAD_MASK 0x80 +#define PCM3168A_ADC_ATMDAD_SHIFT 7 +#define PCM3168A_ADC_ATSPAD_MASK 0x40 +#define PCM3168A_ADC_ATSPAD_SHIFT 6 +#define PCM3168A_ADC_OVFP_MASK 0x1 +#define PCM3168A_ADC_OVFP_SHIFT 0 + +#define PCM3168A_ADC_VOL_MASTER 0x58 + +#define PCM3168A_ADC_VOL_CHAN_START 0x59 + +#endif diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 11d85c5c787add..cd21714c25cd21 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "tpa6130a2.h" @@ -314,6 +315,7 @@ static void tpa6130a2_channel_enable(u8 channel, int enable) /* Unmute channel */ val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE); val &= ~channel; + val |= 0x3F; /* max volume */ tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val); } else { /* Disable channel */ @@ -366,6 +368,51 @@ int tpa6130a2_add_controls(struct snd_soc_codec *codec) } EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); +static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), + + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), +}; + +static const struct snd_soc_dapm_route tpa6130a2_dapm_routes[] = { + { "OUTL", NULL, "INL" }, + { "OUTR", NULL, "INR" }, +}; + +static const struct snd_soc_codec_driver tpa6130a2_driver = { + .controls = tpa6130a2_controls, + .num_controls = ARRAY_SIZE(tpa6130a2_controls), + .dapm_widgets = tpa6130a2_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tpa6130a2_dapm_widgets), + .dapm_routes = tpa6130a2_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tpa6130a2_dapm_routes), +}; + +static const struct snd_soc_dai_ops tpa6130a2_dai_ops = { + +}; + +#define TPA6130A2_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver tpa6130a2_dais[] = { + { + .name = "tpa6130a2", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = TPA6130A2_FORMATS, + }, + .ops = &tpa6130a2_dai_ops, + }, +}; + static int tpa6130a2_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -439,7 +486,6 @@ static int tpa6130a2_probe(struct i2c_client *client, if (ret != 0) goto err_gpio; - /* Read version */ ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) & TPA6130A2_VERSION_MASK; @@ -451,6 +497,13 @@ static int tpa6130a2_probe(struct i2c_client *client, if (ret != 0) goto err_gpio; + ret = snd_soc_register_codec(dev, &tpa6130a2_driver, tpa6130a2_dais, + ARRAY_SIZE(tpa6130a2_dais)); + if (ret) { + dev_err(dev, "failed to register codec:%d\n", ret); + goto err_gpio; + } + return 0; err_gpio: diff --git a/sound/soc/img/Kconfig b/sound/soc/img/Kconfig new file mode 100644 index 00000000000000..35b11f0699e2e5 --- /dev/null +++ b/sound/soc/img/Kconfig @@ -0,0 +1,104 @@ +config SND_SOC_IMG + bool "Audio support for Imagination Technologies designs" + depends on MACH_PISTACHIO + help + Audio support for Imagination Technologies audio hardware + +config SND_SOC_IMG_I2S_IN + tristate "Imagination I2S Input Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S in driver for + Imagination Technologies I2S in device. + +config SND_SOC_IMG_I2S_OUT + tristate "Imagination I2S Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S out driver for + Imagination Technologies I2S out device. + +config SND_SOC_IMG_PARALLEL_OUT + tristate "Imagination Parallel Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for parallel out driver for + Imagination Technologies parallel out device. + +config SND_SOC_IMG_SPDIF_IN + tristate "Imagination SPDIF Input Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SPDIF input driver for + Imagination Technologies SPDIF input device. + +config SND_SOC_IMG_SPDIF_OUT + tristate "Imagination SPDIF Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SPDIF out driver for + Imagination Technologies SPDIF out device. + + +config SND_SOC_IMG_PISTACHIO_EVENT_TIMER + tristate "Support for Pistachio SoC Event Timer Driver" + depends on SND_SOC_IMG_PISTACHIO_EVENT_TIMER_ATU || SND_SOC_IMG_PISTACHIO_EVENT_TIMER_LOCAL + help + Say Y or M if you want to add support for Pistachio event timer + driver for Imagination Technologies Pistachio event timer device. + +choice + prompt "Pistachio Event Timer Time Units" + depends on SND_SOC_IMG + default SND_SOC_IMG_PISTACHIO_EVENT_TIMER_ATU if ATU + help + Selects the time units used by the Pistachio Event Timer + +config SND_SOC_IMG_PISTACHIO_EVENT_TIMER_ATU + bool "Pistachio SoC Event Timer uses ATU units" + depends on ATU + help + ATU units are used (atu time maintained by ATU driver) + +config SND_SOC_IMG_PISTACHIO_EVENT_TIMER_LOCAL + bool "Pistachio SoC Event Timer uses local units" + help + Local units are used (nanoseconds since event timer probe) + +endchoice + +config SND_SOC_IMG_PISTACHIO_INTERNAL_DAC + tristate "Support for Pistachio SoC Internal DAC Driver" + depends on SND_SOC_IMG + help + Say Y or M if you want to add support for Pistachio internal DAC + driver for Imagination Technologies Pistachio internal DAC device. + + +config SND_SOC_IMG_PISTACHIO + tristate "Audio support for Pistachio SoC" + depends on SND_SOC_IMG + select SND_SOC_IMG_I2S_IN + select SND_SOC_IMG_I2S_OUT + select SND_SOC_IMG_PARALLEL_OUT + select SND_SOC_IMG_SPDIF_IN + select SND_SOC_IMG_SPDIF_OUT + select SND_SOC_IMG_PISTACHIO_EVENT_TIMER + select SND_SOC_IMG_PISTACHIO_INTERNAL_DAC + +config SND_SOC_IMG_PISTACHIO_BUB + tristate "Audio support for Pistachio Bring-Up Board" + depends on SND_SOC_IMG + select SND_SOC_IMG_PISTACHIO + select SND_SOC_PCM3168A_I2C + select SND_SOC_PCM3168A_SPI + select SND_SOC_PCM3060_I2C + select SND_SOC_PCM3060_SPI + select SND_SOC_TPA6130A2 + help + Audio support for Imagination Technologies Pistachio Bring-Up Board diff --git a/sound/soc/img/Makefile b/sound/soc/img/Makefile new file mode 100644 index 00000000000000..f5c8bbc978eb02 --- /dev/null +++ b/sound/soc/img/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_SND_SOC_IMG_I2S_IN) += img-i2s-in.o +obj-$(CONFIG_SND_SOC_IMG_I2S_OUT) += img-i2s-out.o +obj-$(CONFIG_SND_SOC_IMG_PARALLEL_OUT) += img-parallel-out.o +obj-$(CONFIG_SND_SOC_IMG_SPDIF_IN) += img-spdif-in.o +obj-$(CONFIG_SND_SOC_IMG_SPDIF_OUT) += img-spdif-out.o + +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_EVENT_TIMER) += pistachio-event-timer.o +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_EVENT_TIMER_ATU) += pistachio-event-timer-atu.o +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_EVENT_TIMER_LOCAL) += pistachio-event-timer-local.o +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC) += pistachio-internal-dac.o + +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO) += pistachio.o diff --git a/sound/soc/img/img-i2s-in.c b/sound/soc/img/img-i2s-in.c new file mode 100644 index 00000000000000..b88b49cec46008 --- /dev/null +++ b/sound/soc/img/img-i2s-in.c @@ -0,0 +1,1141 @@ +/* + * IMG I2S input controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_I2S_IN_RX_FIFO 0x0 + +#define IMG_I2S_IN_CTL 0x4 +#define IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK 0xfffffffc +#define IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT 2 +#define IMG_I2S_IN_CTL_16PACK_MASK BIT(1) +#define IMG_I2S_IN_CTL_ME_MASK BIT(0) + +#define IMG_I2S_IN_CH_RX_FIFO(chan) ((chan) * 0x20) + +#define IMG_I2S_IN_CH_CTL 0x4 +#define IMG_I2S_IN_CH_CTL_CCDEL_MASK 0x38000 +#define IMG_I2S_IN_CH_CTL_CCDEL_SHIFT 15 +#define IMG_I2S_IN_CH_CTL_FEN_MASK BIT(14) +#define IMG_I2S_IN_CH_CTL_FMODE_MASK BIT(13) +#define IMG_I2S_IN_CH_CTL_16PACK_MASK BIT(12) +#define IMG_I2S_IN_CH_CTL_JUST_MASK BIT(10) +#define IMG_I2S_IN_CH_CTL_PACKH_MASK BIT(9) +#define IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK BIT(8) +#define IMG_I2S_IN_CH_CTL_BLKP_MASK BIT(7) +#define IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK BIT(6) +#define IMG_I2S_IN_CH_CTL_LRD_MASK BIT(3) +#define IMG_I2S_IN_CH_CTL_FW_MASK BIT(2) +#define IMG_I2S_IN_CH_CTL_SW_MASK BIT(1) +#define IMG_I2S_IN_CH_CTL_ME_MASK BIT(0) + +#define IMG_I2S_IN_CH_STRIDE 0x20 + +#define IMG_I2S_IN_CH_SCNT 0x10 + +#define PERIPH_I2S_IN_CLOCK_MASTER 0x280 + +#define PERIPH_SAMPLE_CAPTURE 0x80 +#define PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK 0x3f + +#define IMG_I2S_IN_CH_BIT(i2s, ch) (1UL << (((i2s)->max_i2s_chan - 1) - (ch))) +#define IMG_I2S_IN_CS_MASK(i2s, cs) (((1UL << (cs)->active_channels) - 1) << \ + (((i2s)->max_i2s_chan - \ + (cs)->active_channels) - (cs)->first_channel)) + +struct img_i2s_in_channel; + +struct img_i2s_in_channel_group { + u32 mask; + u32 channel_sets; + u32 active_channel_sets; + u32 master; + bool stopping; +}; + +struct img_i2s_in_channel_set { + struct img_i2s_in_channel_group *group; + char dma_name[5]; + char cpu_dai_name[18]; + char platform_name[23]; + struct snd_dmaengine_dai_dma_data dma_data; + u32 first_channel; + u32 last_channel; + u32 active_channels; + bool active; + bool shared_dma; +}; + +struct img_i2s_in_channel { + struct img_i2s_in_channel_set *set; +}; + +struct img_i2s_in { + spinlock_t lock; + void __iomem *base; + struct clk *clk_sys; + struct device *dev; + unsigned int max_i2s_chan; + void __iomem *channel_base; + u32 active_channel_sets; + bool core_me; + struct regmap *periph_regs; + u32 clock_masters; + u32 shared_dma_channels; + struct img_i2s_in_channel_set *channel_sets; + struct img_i2s_in_channel *channels; + struct img_i2s_in_channel_group *groups; + struct snd_soc_dai_driver *cpu_dais; + struct snd_soc_component_driver img_i2s_in_component; +}; + +static inline void img_i2s_in_writel(struct img_i2s_in *i2s, u32 val, u32 reg) +{ + writel(val, i2s->base + reg); +} + +static inline u32 img_i2s_in_readl(struct img_i2s_in *i2s, u32 reg) +{ + return readl(i2s->base + reg); +} + +static inline void img_i2s_in_ch_writel(struct img_i2s_in *i2s, u32 chan, + u32 val, u32 reg) +{ + writel(val, i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); +} + +static inline u32 img_i2s_in_ch_readl(struct img_i2s_in *i2s, u32 chan, + u32 reg) +{ + return readl(i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); +} + +static inline u32 img_i2s_in_ch_disable(struct img_i2s_in *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL); + + return reg; +} + +static inline void img_i2s_in_ch_enable(struct img_i2s_in *i2s, u32 chan, + u32 reg) +{ + reg |= IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL); +} + +static inline void img_i2s_in_flush(struct img_i2s_in *i2s, + struct img_i2s_in_channel_set *cs) +{ + int i; + u32 reg; + + for (i = cs->first_channel; i <= cs->last_channel; i++) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg |= IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } +} + +static void img_i2s_in_do_multistart(struct img_i2s_in *i2s, u32 mask) +{ + int i; + u32 reg; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (mask & IMG_I2S_IN_CH_BIT(i2s, i)) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg |= IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + } +} + +static void img_i2s_in_do_multistop(struct img_i2s_in *i2s, u32 mask) +{ + int i; + u32 reg; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (mask & IMG_I2S_IN_CH_BIT(i2s, i)) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + } +} + +static void img_i2s_in_multistart(struct img_i2s_in *i2s, u32 mask) +{ + int n = 0, i, first = -1, last; + u32 reg, regb, regc, regd; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (mask & IMG_I2S_IN_CH_BIT(i2s, i)) { + n++; + if (first == -1) + first = i; + last = i; + } + } + + if (n == 0) { + return; + } else if ((n == 1) || (!i2s->core_me)) { + img_i2s_in_do_multistart(i2s, mask); + } else { + while (1) { + regmap_update_bits(i2s->periph_regs, + PERIPH_SAMPLE_CAPTURE, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK); + regmap_update_bits(i2s->periph_regs, + PERIPH_SAMPLE_CAPTURE, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK, + 0); + reg = img_i2s_in_ch_readl(i2s, first, + IMG_I2S_IN_CH_SCNT); + regb = img_i2s_in_ch_readl(i2s, last, + IMG_I2S_IN_CH_SCNT); + img_i2s_in_do_multistart(i2s, mask); + regmap_update_bits(i2s->periph_regs, + PERIPH_SAMPLE_CAPTURE, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK); + regmap_update_bits(i2s->periph_regs, + PERIPH_SAMPLE_CAPTURE, + PERIPH_SAMPLE_CAPTURE_I2S_IN_MASK, + 0); + regc = img_i2s_in_ch_readl(i2s, first, + IMG_I2S_IN_CH_SCNT); + regd = img_i2s_in_ch_readl(i2s, last, + IMG_I2S_IN_CH_SCNT); + if ((regc - reg) == (regd - regb)) + break; + img_i2s_in_do_multistop(i2s, mask); + } + } +} + +static u32 img_i2s_in_get_mask_shared_dma_first_only(struct img_i2s_in *i2s, + u32 mask) +{ + if (i2s->shared_dma_channels && + (mask & (1UL << (i2s->max_i2s_chan - 1)))) { + mask &= ~(((1UL << i2s->shared_dma_channels) - 1) << + (i2s->max_i2s_chan - i2s->shared_dma_channels)); + mask |= 1UL << (i2s->max_i2s_chan - 1); + } + + return mask; +} + +static u32 img_i2s_in_get_mask_shared_dma_all(struct img_i2s_in *i2s, + u32 mask) +{ + if (i2s->shared_dma_channels && + (mask & (1UL << (i2s->max_i2s_chan - 1)))) { + mask |= ((1UL << i2s->shared_dma_channels) - 1) << + (i2s->max_i2s_chan - i2s->shared_dma_channels); + } + + return mask; +} + +static u32 img_i2s_in_get_mask_shared_dma_active_only(struct img_i2s_in *i2s, + u32 mask) +{ + u32 active_channels = i2s->channel_sets[0].active_channels; + + if (i2s->shared_dma_channels && + (mask & (1UL << (i2s->max_i2s_chan - 1)))) { + mask &= ~(((1UL << i2s->shared_dma_channels) - 1) << + (i2s->max_i2s_chan - i2s->shared_dma_channels)); + mask |= ((1UL << active_channels) - 1) << + (i2s->max_i2s_chan - active_channels); + } + + return mask; +} + +static int img_i2s_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg, mask; + unsigned long flags; + int ret = 0; + struct img_i2s_in_channel_set *cs; + struct img_i2s_in_channel_group *group; + struct img_i2s_in_channel *ch; + bool nostart = false; + + cs = &i2s->channel_sets[dai->id]; + ch = &i2s->channels[cs->first_channel]; + + spin_lock_irqsave(&i2s->lock, flags); + + group = cs->group; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + + if (cs->active) + break; + + if (!group || group->stopping) { + ret = -EINVAL; + break; + } + + group->active_channel_sets++; + + if (group->active_channel_sets == group->channel_sets) { + mask = group->mask; + mask = img_i2s_in_get_mask_shared_dma_active_only(i2s, + mask); + img_i2s_in_multistart(i2s, mask); + group->stopping = true; + } else { + nostart = true; + } + + if (!i2s->core_me && !nostart) { + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg |= IMG_I2S_IN_CTL_ME_MASK; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); + i2s->core_me = true; + } + + i2s->active_channel_sets++; + cs->active = true; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + + if (!cs->active) + break; + + if (!group) { + ret = -EINVAL; + break; + } + + mask = IMG_I2S_IN_CS_MASK(i2s, cs); + img_i2s_in_do_multistop(i2s, mask); + + i2s->active_channel_sets--; + cs->active = false; + group->active_channel_sets--; + if (!group->active_channel_sets) + group->stopping = false; + + if (!i2s->active_channel_sets) { + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg &= ~IMG_I2S_IN_CTL_ME_MASK; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); + i2s->core_me = false; + } + + img_i2s_in_flush(i2s, cs); + + break; + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&i2s->lock, flags); + + return ret; +} + +static int img_i2s_in_check_rate(struct img_i2s_in *i2s, + unsigned int sample_rate, unsigned int frame_size, + unsigned int *bclk_filter_enable, + unsigned int *bclk_filter_value) +{ + unsigned int bclk_freq, cur_freq; + + bclk_freq = sample_rate * frame_size; + + cur_freq = clk_get_rate(i2s->clk_sys); + + if (cur_freq >= bclk_freq * 8) { + *bclk_filter_enable = 1; + *bclk_filter_value = 0; + } else if (cur_freq >= bclk_freq * 7) { + *bclk_filter_enable = 1; + *bclk_filter_value = 1; + } else if (cur_freq >= bclk_freq * 6) { + *bclk_filter_enable = 0; + *bclk_filter_value = 0; + } else { + dev_err(i2s->dev, + "Sys clock rate %u insufficient for sample rate %u\n", + cur_freq, sample_rate); + return -EINVAL; + } + + return 0; +} + +static int img_i2s_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels, i2s_channels, frame_size; + unsigned int bclk_filter_enable, bclk_filter_value; + int i, ret = 0; + u32 reg, control_reg, control_mask, chan_control_mask; + u32 control_set = 0, chan_control_set = 0, max_chan; + u32 channel_mask, mask, new_control_reg, new_reg; + unsigned long flags; + struct img_i2s_in_channel_set *cs; + bool includes_shared = false; + bool control_reg_diff = false; + snd_pcm_format_t format; + + cs = &i2s->channel_sets[dai->id]; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + i2s_channels = channels / 2; + + switch (format) { + case SNDRV_PCM_FORMAT_S32_LE: + frame_size = 64; + chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_PACKH_MASK; + break; + case SNDRV_PCM_FORMAT_S24_LE: + frame_size = 64; + chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; + break; + case SNDRV_PCM_FORMAT_S16_LE: + frame_size = 32; + control_set |= IMG_I2S_IN_CTL_16PACK_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_16PACK_MASK; + break; + default: + return -EINVAL; + } + + max_chan = ((cs->last_channel - cs->first_channel) + 1) * 2; + if ((channels < 2) || (channels > max_chan) || (channels % 2)) + return -EINVAL; + + ret = img_i2s_in_check_rate(i2s, rate, frame_size, + &bclk_filter_enable, &bclk_filter_value); + if (ret < 0) + return ret; + + if (bclk_filter_enable) + chan_control_set |= IMG_I2S_IN_CH_CTL_FEN_MASK; + + if (bclk_filter_value) + chan_control_set |= IMG_I2S_IN_CH_CTL_FMODE_MASK; + + chan_control_mask = (u32)(~IMG_I2S_IN_CH_CTL_16PACK_MASK & + ~IMG_I2S_IN_CH_CTL_FEN_MASK & + ~IMG_I2S_IN_CH_CTL_FMODE_MASK & + ~IMG_I2S_IN_CH_CTL_SW_MASK & + ~IMG_I2S_IN_CH_CTL_FW_MASK & + ~IMG_I2S_IN_CH_CTL_PACKH_MASK); + + control_mask = ~IMG_I2S_IN_CTL_16PACK_MASK; + + channel_mask = i2s->clock_masters; + if (!(IMG_I2S_IN_CH_BIT(i2s, cs->first_channel) & channel_mask)) + channel_mask = ~channel_mask; + + mask = 1UL << (i2s->max_i2s_chan - 1); + + includes_shared = (i2s->shared_dma_channels && (channel_mask & mask)); + + spin_lock_irqsave(&i2s->lock, flags); + + /* See if the wrapper register needs to change */ + if (includes_shared) { + if (cs->shared_dma) { + control_set |= ((i2s_channels - 1) << + IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT); + control_mask &= ~IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK; + } + + control_reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + + new_control_reg = (control_reg & control_mask) | control_set; + + /* If it does, and there are any active channels, fail */ + if (new_control_reg != control_reg) { + if (i2s->active_channel_sets) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EBUSY; + } + control_reg_diff = true; + } + } + + /* + * Check that no individual registers need to change where the + * corresponding channel is active + */ + for (i = 0; i < i2s->max_i2s_chan; i++) { + if ((channel_mask & mask) && i2s->channels[i].set->active) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + new_reg = (reg & chan_control_mask) | chan_control_set; + if (new_reg != reg) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EBUSY; + } + } + mask >>= 1; + } + + mask = 1UL << (i2s->max_i2s_chan - 1); + + if (control_reg_diff) + img_i2s_in_writel(i2s, new_control_reg, IMG_I2S_IN_CTL); + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (channel_mask & mask) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + new_reg = (reg & chan_control_mask) | chan_control_set; + if (new_reg != reg) + img_i2s_in_ch_writel(i2s, i, new_reg, + IMG_I2S_IN_CH_CTL); + } + mask >>= 1; + } + + cs->active_channels = i2s_channels; + + spin_unlock_irqrestore(&i2s->lock, flags); + + return 0; +} + +static int img_i2s_in_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + int i; + u32 chan_control_mask, lrd_set = 0, blkp_set = 0, chan_control_set = 0; + u32 reg, channel_mask, mask; + struct img_i2s_in_channel_set *cs; + unsigned long flags; + + cs = &i2s->channel_sets[dai->id]; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_NF: + lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; + blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + chan_control_set |= IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + chan_control_mask = (u32)~IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; + + channel_mask = i2s->clock_masters; + if (!(IMG_I2S_IN_CH_BIT(i2s, cs->first_channel) & channel_mask)) + channel_mask = ~channel_mask; + + mask = 1UL << (i2s->max_i2s_chan - 1); + + spin_lock_irqsave(&i2s->lock, flags); + + if (i2s->active_channel_sets) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EBUSY; + } + + /* + * BLKP and LRD must be set during separate register writes + */ + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (mask & channel_mask) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg = (reg & chan_control_mask) | chan_control_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg = (reg & ~IMG_I2S_IN_CH_CTL_BLKP_MASK) | blkp_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg = (reg & ~IMG_I2S_IN_CH_CTL_LRD_MASK) | lrd_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + mask >>= 1; + } + + spin_unlock_irqrestore(&i2s->lock, flags); + + return 0; +} + +static const struct snd_soc_dai_ops img_i2s_in_dai_ops = { + .trigger = img_i2s_in_trigger, + .hw_params = img_i2s_in_hw_params, + .set_fmt = img_i2s_in_set_fmt +}; + +static int img_i2s_in_group_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = i2s->max_i2s_chan; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int img_i2s_in_group_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, int group) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int i; + u32 mask; + unsigned long flags; + + spin_lock_irqsave(&i2s->lock, flags); + + mask = i2s->groups[group].mask; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (mask & IMG_I2S_IN_CH_BIT(i2s, i)) + ucontrol->value.integer.value[i] = 1; + else + ucontrol->value.integer.value[i] = 0; + } + + spin_unlock_irqrestore(&i2s->lock, flags); + + return 0; +} + +static int img_i2s_in_group_get_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return img_i2s_in_group_get(kcontrol, ucontrol, 0); +} + +static int img_i2s_in_group_get_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return img_i2s_in_group_get(kcontrol, ucontrol, 1); +} + +static int img_i2s_in_group_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, int group) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int i; + u32 mask, new_mask; + unsigned long flags; + + new_mask = 0; + for (i = 0; i < i2s->max_i2s_chan; i++) + if (ucontrol->value.integer.value[i]) + new_mask |= IMG_I2S_IN_CH_BIT(i2s, i); + + /* Master must be present in group */ + if (!(new_mask & IMG_I2S_IN_CH_BIT(i2s, i2s->groups[group].master))) + return -EINVAL; + + spin_lock_irqsave(&i2s->lock, flags); + + /* Members of the group must have the specified bclk/lrclk master */ + if (group) { + if ((new_mask & i2s->clock_masters) != new_mask) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EINVAL; + } + } else { + if ((new_mask & ~i2s->clock_masters) != new_mask) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EINVAL; + } + } + + mask = i2s->groups[group].mask; + + /* Check none of the channels currently in the group are active */ + if (i2s->groups[group].active_channel_sets) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EINVAL; + } + + /* + * If one of the channels using the shared dma is present in the new + * group, all of the channels that use the shared dma must be present + */ + mask = img_i2s_in_get_mask_shared_dma_all(i2s, new_mask); + if ((mask & new_mask) != mask) { + spin_unlock_irqrestore(&i2s->lock, flags); + return -EINVAL; + } + + mask = i2s->groups[group].mask; + + for (i = 0; i < i2s->max_i2s_chan; i++) + if (new_mask & IMG_I2S_IN_CH_BIT(i2s, i)) + i2s->channels[i].set->group = &i2s->groups[group]; + else if (mask & IMG_I2S_IN_CH_BIT(i2s, i)) + i2s->channels[i].set->group = NULL; + + i2s->groups[group].mask = new_mask; + + i2s->groups[group].channel_sets = 0; + new_mask = img_i2s_in_get_mask_shared_dma_first_only(i2s, new_mask); + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (new_mask & IMG_I2S_IN_CH_BIT(i2s, i)) + i2s->groups[group].channel_sets++; + } + + spin_unlock_irqrestore(&i2s->lock, flags); + + return 0; +} + +static int img_i2s_in_group_set_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return img_i2s_in_group_set(kcontrol, ucontrol, 0); +} + +static int img_i2s_in_group_set_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return img_i2s_in_group_set(kcontrol, ucontrol, 1); +} + +static struct snd_kcontrol_new img_i2s_in_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "I2S In Group 1", + .info = img_i2s_in_group_info, + .get = img_i2s_in_group_get_a, + .put = img_i2s_in_group_set_a + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "I2S In Group 2", + .info = img_i2s_in_group_info, + .get = img_i2s_in_group_get_b, + .put = img_i2s_in_group_set_b + }, +}; + +static int img_i2s_in_dai_probe(struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + struct img_i2s_in_channel_set *cs; + + cs = &i2s->channel_sets[dai->id]; + + snd_soc_dai_init_dma_data(dai, NULL, &cs->dma_data); + + if (!dai->id) + snd_soc_add_dai_controls(dai, img_i2s_in_controls, 2); + + return 0; +} + +static int img_i2s_in_dma_prepare_slave_config(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params, struct dma_slave_config *sc) +{ + unsigned int i2s_channels = params_channels(params) / 2; + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st); + + ret = snd_hwparams_to_dma_slave_config(st, params, sc); + if (ret) + return ret; + + sc->src_addr = dma_data->addr; + sc->src_addr_width = dma_data->addr_width; + sc->src_maxburst = 4 * i2s_channels; + + return 0; +} + +static const struct snd_dmaengine_pcm_config img_i2s_in_dma_config = { + .prepare_slave_config = img_i2s_in_dma_prepare_slave_config +}; + +static int img_i2s_in_probe(struct platform_device *pdev) +{ + struct img_i2s_in *i2s; + struct img_i2s_in_channel_set *cs; + struct snd_soc_dai_driver *cd; + struct resource *res; + void __iomem *base; + int ret, i; + struct reset_control *rst; + u32 reg, temp, mask, num_channel_sets; + unsigned int max_i2s_chan_pow_2; + struct device *dev = &pdev->dev; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s); + + i2s->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s->base = base; + + if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels", + &i2s->max_i2s_chan)) { + dev_err(dev, "No img,i2s-channels property\n"); + return -EINVAL; + } + + if (!i2s->max_i2s_chan) + return -EINVAL; + + max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan); + + i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); + + i2s->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-periph"); + if (IS_ERR(i2s->periph_regs)) + return PTR_ERR(i2s->periph_regs); + + of_property_read_u32(pdev->dev.of_node, "img,clock-master", + &i2s->clock_masters); + + i2s->clock_masters <<= 2; + + if (of_property_read_u32(pdev->dev.of_node, "img,shared-dma", + &i2s->shared_dma_channels)) { + i2s->shared_dma_channels = i2s->max_i2s_chan; + } + + if (i2s->shared_dma_channels > i2s->max_i2s_chan) { + dev_err(dev, "img,shared-dma must be <= img,i2s-channels\n"); + return -EINVAL; + } + + mask = (1UL << i2s->shared_dma_channels) - 1; + temp = (i2s->clock_masters >> + (i2s->max_i2s_chan - i2s->shared_dma_channels)) & mask; + + if (temp && (temp != mask)) { + dev_err(dev, "img,shared-dma channels must have the same clock-master\n"); + return -EINVAL; + } + + if (i2s->clock_masters & ~0x3CUL) { + dev_err(dev, "channels 4/5 cannot use MFIO11/MFIO12 for BCLK/LRCLK\n"); + return -EINVAL; + } + + mask = 0x3F; + if (i2s->clock_masters) { + reg = 0x30; + reg |= (i2s->clock_masters & 0x20) ? 0x1 : 0x0; + reg |= (i2s->clock_masters & 0x10) ? 0x2 : 0x0; + reg |= (i2s->clock_masters & 0x8) ? 0x4 : 0x0; + reg |= (i2s->clock_masters & 0x4) ? 0x8 : 0x0; + + } else { + reg = 0; + } + regmap_update_bits(i2s->periph_regs, PERIPH_I2S_IN_CLOCK_MASTER, + mask, reg); + + i2s->clk_sys = devm_clk_get(dev, "sys"); + if (IS_ERR(i2s->clk_sys)) + return PTR_ERR(i2s->clk_sys); + + ret = clk_prepare_enable(i2s->clk_sys); + if (ret) + return ret; + + rst = devm_reset_control_get(dev, "rst"); + if (IS_ERR(rst)) { + dev_dbg(dev, "No top level reset found\n"); + + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg &= ~IMG_I2S_IN_CTL_ME_MASK; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_in_ch_disable(i2s, i); + reg |= IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + } else { + reset_control_assert(rst); + reset_control_deassert(rst); + } + + img_i2s_in_writel(i2s, 0, IMG_I2S_IN_CTL); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_in_ch_writel(i2s, i, + (4 << IMG_I2S_IN_CH_CTL_CCDEL_SHIFT) | + IMG_I2S_IN_CH_CTL_JUST_MASK | + IMG_I2S_IN_CH_CTL_FW_MASK, IMG_I2S_IN_CH_CTL); + + spin_lock_init(&i2s->lock); + + num_channel_sets = i2s->max_i2s_chan - i2s->shared_dma_channels; + if (i2s->shared_dma_channels) + num_channel_sets++; + + i2s->channel_sets = devm_kzalloc(dev, + sizeof(*i2s->channel_sets) * num_channel_sets, GFP_KERNEL); + if (!i2s->channel_sets) { + ret = -ENOMEM; + goto err_clk_disable; + } + + i2s->cpu_dais = devm_kzalloc(dev, + sizeof(*i2s->cpu_dais) * num_channel_sets, GFP_KERNEL); + if (!i2s->cpu_dais) { + ret = -ENOMEM; + goto err_clk_disable; + } + + i2s->channels = devm_kzalloc(dev, + sizeof(*i2s->channels) * i2s->max_i2s_chan, GFP_KERNEL); + if (!i2s->channels) { + ret = -ENOMEM; + goto err_clk_disable; + } + + i2s->groups = devm_kzalloc(dev, sizeof(*i2s->groups) * 2, GFP_KERNEL); + if (!i2s->groups) { + ret = -ENOMEM; + goto err_clk_disable; + } + + i2s->img_i2s_in_component.name = "img-i2s-in-component"; + + cs = &i2s->channel_sets[0]; + cd = &i2s->cpu_dais[0]; + + if (i2s->shared_dma_channels) { + cd->probe = img_i2s_in_dai_probe; + cd->capture.channels_min = 2; + cd->capture.channels_max = 2 * i2s->shared_dma_channels; + cd->capture.rates = SNDRV_PCM_RATE_8000_192000; + cd->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE; + cd->ops = &img_i2s_in_dai_ops; + strcpy(cs->cpu_dai_name, "img-i2s-in-shared"); + cd->name = cs->cpu_dai_name; + strcpy(cs->platform_name, "img-i2s-in-plat-shared"); + + cs->first_channel = 0; + cs->last_channel = i2s->shared_dma_channels - 1; + + cs->dma_data.addr = res->start + IMG_I2S_IN_RX_FIFO; + cs->dma_data.addr_width = 4; + + cs->shared_dma = true; + + ret = devm_snd_dmaengine_pcm_register_id_name(dev, + &img_i2s_in_dma_config, 0, 0, + cs->platform_name); + if (ret) + goto err_clk_disable; + + for (i = 0; i < i2s->shared_dma_channels; i++) + i2s->channels[i].set = cs; + + cs++; + cd++; + } + + for (i = i2s->shared_dma_channels; i < i2s->max_i2s_chan; i++) { + cd->probe = img_i2s_in_dai_probe; + cd->capture.channels_min = 2; + cd->capture.channels_max = 2; + cd->capture.rates = SNDRV_PCM_RATE_8000_192000; + cd->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE; + cd->ops = &img_i2s_in_dai_ops; + sprintf(cs->cpu_dai_name, "img-i2s-in-%d", i); + cd->name = cs->cpu_dai_name; + sprintf(cs->platform_name, "img-i2s-in-plat-%d", i); + + cs->first_channel = i; + cs->last_channel = i; + + sprintf(cs->dma_name, "rx%d", i); + cs->dma_data.chan_name = cs->dma_name; + cs->dma_data.addr = res->start + (max_i2s_chan_pow_2 * 0x20) + + IMG_I2S_IN_CH_RX_FIFO(i); + cs->dma_data.addr_width = 4; + cs->dma_data.maxburst = 4; + + ret = devm_snd_dmaengine_pcm_register_id_name(dev, NULL, + SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME, + (i - i2s->shared_dma_channels) + 1, + cs->platform_name); + if (ret) + goto err_clk_disable; + + i2s->channels[i].set = cs; + + cs++; + cd++; + } + + ret = devm_snd_soc_register_component(dev, &i2s->img_i2s_in_component, + i2s->cpu_dais, num_channel_sets); + if (ret) + goto err_clk_disable; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (~i2s->clock_masters & IMG_I2S_IN_CH_BIT(i2s, i)) + break; + } + + if (i != i2s->max_i2s_chan) { + i2s->groups[0].master = i; + mask = IMG_I2S_IN_CH_BIT(i2s, i); + mask = img_i2s_in_get_mask_shared_dma_all(i2s, mask); + i2s->groups[0].mask = mask; + i2s->groups[0].channel_sets = 1; + i2s->channels[i].set->group = &i2s->groups[0]; + } + + for (i = 0; i < i2s->max_i2s_chan; i++) { + if (i2s->clock_masters & IMG_I2S_IN_CH_BIT(i2s, i)) + break; + } + + if (i != i2s->max_i2s_chan) { + i2s->groups[1].master = i; + mask = IMG_I2S_IN_CH_BIT(i2s, i); + mask = img_i2s_in_get_mask_shared_dma_all(i2s, mask); + i2s->groups[1].mask = mask; + i2s->groups[1].channel_sets = 1; + i2s->channels[i].set->group = &i2s->groups[1]; + } + + + return 0; + +err_clk_disable: + clk_disable_unprepare(i2s->clk_sys); + + return ret; +} + +static int img_i2s_in_dev_remove(struct platform_device *pdev) +{ + struct img_i2s_in *i2s = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2s->clk_sys); + + return 0; +} + +static const struct of_device_id img_i2s_in_of_match[] = { + { .compatible = "img,i2s-in" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_i2s_in_of_match); + +static struct platform_driver img_i2s_in_driver = { + .driver = { + .name = "img-i2s-in", + .of_match_table = img_i2s_in_of_match + }, + .probe = img_i2s_in_probe, + .remove = img_i2s_in_dev_remove +}; +module_platform_driver(img_i2s_in_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG I2S Input Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-i2s-out.c b/sound/soc/img/img-i2s-out.c new file mode 100644 index 00000000000000..8940cc92e21301 --- /dev/null +++ b/sound/soc/img/img-i2s-out.c @@ -0,0 +1,598 @@ +/* + * IMG I2S output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_I2S_OUT_TX_FIFO 0x0 + +#define IMG_I2S_OUT_CTL 0x4 +#define IMG_I2S_OUT_CTL_EXT_EN_CLK_MASK BIT(25) +#define IMG_I2S_OUT_CTL_DATA_EN_MASK BIT(24) +#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK 0xffe000 +#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT 13 +#define IMG_I2S_OUT_CTL_FRM_SIZE_MASK BIT(8) +#define IMG_I2S_OUT_CTL_MASTER_MASK BIT(6) +#define IMG_I2S_OUT_CTL_CLK_MASK BIT(5) +#define IMG_I2S_OUT_CTL_CLK_EN_MASK BIT(4) +#define IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK BIT(3) +#define IMG_I2S_OUT_CTL_BCLK_POL_MASK BIT(2) +#define IMG_I2S_OUT_CTL_ME_MASK BIT(0) + +#define IMG_I2S_OUT_CH_CTL 0x4 +#define IMG_I2S_OUT_CHAN_CTL_CH_MASK BIT(11) +#define IMG_I2S_OUT_CHAN_CTL_LT_MASK BIT(10) +#define IMG_I2S_OUT_CHAN_CTL_FMT_MASK 0xf0 +#define IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT 4 +#define IMG_I2S_OUT_CHAN_CTL_JUST_MASK BIT(3) +#define IMG_I2S_OUT_CHAN_CTL_CLKT_MASK BIT(1) +#define IMG_I2S_OUT_CHAN_CTL_ME_MASK BIT(0) + +#define IMG_I2S_OUT_CH_STRIDE 0x20 + +struct img_i2s_out { + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + unsigned int max_i2s_chan; + void __iomem *channel_base; + bool force_clk_active; + unsigned int active_channels; + struct reset_control *rst; + struct snd_soc_dai_driver dai_driver; +}; + +static int img_i2s_out_suspend(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + + if (!i2s->force_clk_active) + clk_disable_unprepare(i2s->clk_ref); + + return 0; +} + +static int img_i2s_out_resume(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + int ret; + + if (!i2s->force_clk_active) { + ret = clk_prepare_enable(i2s->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static inline void img_i2s_out_writel(struct img_i2s_out *i2s, u32 val, + u32 reg) +{ + writel(val, i2s->base + reg); +} + +static inline u32 img_i2s_out_readl(struct img_i2s_out *i2s, u32 reg) +{ + return readl(i2s->base + reg); +} + +static inline void img_i2s_out_ch_writel(struct img_i2s_out *i2s, + u32 chan, u32 val, u32 reg) +{ + writel(val, i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); +} + +static inline u32 img_i2s_out_ch_readl(struct img_i2s_out *i2s, u32 chan, + u32 reg) +{ + return readl(i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); +} + +static inline void img_i2s_out_ch_disable(struct img_i2s_out *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); + reg &= ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; + img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL); +} + +static inline void img_i2s_out_ch_enable(struct img_i2s_out *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); + reg |= IMG_I2S_OUT_CHAN_CTL_ME_MASK; + img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL); +} + +static inline void img_i2s_out_disable(struct img_i2s_out *i2s) +{ + u32 reg; + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg &= ~IMG_I2S_OUT_CTL_ME_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); +} + +static inline void img_i2s_out_enable(struct img_i2s_out *i2s) +{ + u32 reg; + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg |= IMG_I2S_OUT_CTL_ME_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); +} + +static void img_i2s_out_reset(struct img_i2s_out *i2s) +{ + int i; + u32 core_ctl, chan_ctl; + + core_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL) & + ~IMG_I2S_OUT_CTL_ME_MASK & + ~IMG_I2S_OUT_CTL_DATA_EN_MASK; + + if (!i2s->force_clk_active) + core_ctl &= ~IMG_I2S_OUT_CTL_CLK_EN_MASK; + + chan_ctl = img_i2s_out_ch_readl(i2s, 0, IMG_I2S_OUT_CH_CTL) & + ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; + + reset_control_assert(i2s->rst); + reset_control_deassert(i2s->rst); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_writel(i2s, i, chan_ctl, IMG_I2S_OUT_CH_CTL); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + img_i2s_out_writel(i2s, core_ctl, IMG_I2S_OUT_CTL); + img_i2s_out_enable(i2s); +} + +static int img_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + if (!i2s->force_clk_active) + reg |= IMG_I2S_OUT_CTL_CLK_EN_MASK; + reg |= IMG_I2S_OUT_CTL_DATA_EN_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + img_i2s_out_reset(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_i2s_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int channels, i2s_channels; + long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; + int i; + u32 reg, control_mask, control_set = 0; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + i2s_channels = channels / 2; + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if ((channels < 2) || + (channels > (i2s->max_i2s_chan * 2)) || + (channels % 2)) + return -EINVAL; + + pre_div_a = clk_round_rate(i2s->clk_ref, rate * 256); + if (pre_div_a < 0) + return pre_div_a; + pre_div_b = clk_round_rate(i2s->clk_ref, rate * 384); + if (pre_div_b < 0) + return pre_div_b; + + diff_a = abs((pre_div_a / 256) - rate); + diff_b = abs((pre_div_b / 384) - rate); + + /* If diffs are equal, use lower clock rate */ + if (diff_a > diff_b) + clk_set_rate(i2s->clk_ref, pre_div_b); + else + clk_set_rate(i2s->clk_ref, pre_div_a); + + /* + * Another driver (eg alsa machine driver) may have rejected the above + * change. Get the current rate and set the register bit according to + * the new minimum diff + */ + clk_rate = clk_get_rate(i2s->clk_ref); + + diff_a = abs((clk_rate / 256) - rate); + diff_b = abs((clk_rate / 384) - rate); + + if (diff_a > diff_b) + control_set |= IMG_I2S_OUT_CTL_CLK_MASK; + + control_set |= ((i2s_channels - 1) << + IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT) & + IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; + + control_mask = IMG_I2S_OUT_CTL_CLK_MASK | + IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; + + img_i2s_out_disable(i2s); + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg = (reg & ~control_mask) | control_set; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + for (i = 0; i < i2s_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + for (; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_disable(i2s, i); + + img_i2s_out_enable(i2s); + + i2s->active_channels = i2s_channels; + + return 0; +} + +static int img_i2s_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + int i; + bool force_clk_active; + u32 chan_control_mask, control_mask, chan_control_set = 0; + u32 reg = 0, control_set = 0; + + force_clk_active = ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) == + SND_SOC_DAIFMT_CONT); + + if (force_clk_active) + control_set |= IMG_I2S_OUT_CTL_CLK_EN_MASK; + else + control_set |= IMG_I2S_OUT_CTL_EXT_EN_CLK_MASK; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + control_set |= IMG_I2S_OUT_CTL_MASTER_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; + break; + case SND_SOC_DAIFMT_NB_IF: + control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; + control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + chan_control_set |= IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + control_mask = IMG_I2S_OUT_CTL_CLK_EN_MASK | + IMG_I2S_OUT_CTL_MASTER_MASK | + IMG_I2S_OUT_CTL_BCLK_POL_MASK | + IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK | + IMG_I2S_OUT_CTL_EXT_EN_CLK_MASK; + + chan_control_mask = IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; + + img_i2s_out_disable(i2s); + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg = (reg & ~control_mask) | control_set; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_disable(i2s, i); + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_out_ch_readl(i2s, i, IMG_I2S_OUT_CH_CTL); + reg = (reg & ~chan_control_mask) | chan_control_set; + img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL); + } + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + img_i2s_out_enable(i2s); + + i2s->force_clk_active = force_clk_active; + + return 0; +} + +static int img_i2s_out_start_at(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai, int clock_type, + const struct timespec *ts) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + + if (!i2s->force_clk_active) { + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg |= IMG_I2S_OUT_CTL_DATA_EN_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + } + + return 0; +} + +static int img_i2s_out_start_at_abort(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + img_i2s_out_reset(i2s); + + return 0; +} + +static const struct snd_soc_dai_ops img_i2s_out_dai_ops = { + .trigger = img_i2s_out_trigger, + .hw_params = img_i2s_out_hw_params, + .set_fmt = img_i2s_out_set_fmt, + .start_at = img_i2s_out_start_at, + .start_at_abort = img_i2s_out_start_at_abort +}; + +static int img_i2s_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &i2s->dma_data, NULL); + + return 0; +} + +static const struct snd_soc_component_driver img_i2s_out_component = { + .name = "img-i2s-out" +}; + +static int img_i2s_out_dma_prepare_slave_config(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params, struct dma_slave_config *sc) +{ + unsigned int i2s_channels = params_channels(params) / 2; + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st); + + ret = snd_hwparams_to_dma_slave_config(st, params, sc); + if (ret) + return ret; + + sc->dst_addr = dma_data->addr; + sc->dst_addr_width = dma_data->addr_width; + sc->dst_maxburst = 4 * i2s_channels; + + return 0; +} + +static const struct snd_dmaengine_pcm_config img_i2s_out_dma_config = { + .prepare_slave_config = img_i2s_out_dma_prepare_slave_config +}; + +static int img_i2s_out_probe(struct platform_device *pdev) +{ + struct img_i2s_out *i2s; + struct resource *res; + void __iomem *base; + int i, ret; + unsigned int max_i2s_chan_pow_2; + u32 reg; + struct device *dev = &pdev->dev; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s); + + i2s->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s->base = base; + + if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels", + &i2s->max_i2s_chan)) { + dev_err(&pdev->dev, "No img,i2s-channels property\n"); + return -EINVAL; + } + + max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan); + + i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); + + i2s->rst = devm_reset_control_get(&pdev->dev, "rst"); + if (IS_ERR(i2s->rst)) { + if (PTR_ERR(i2s->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(i2s->rst); + } + + i2s->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(i2s->clk_sys)) { + if (PTR_ERR(i2s->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(i2s->clk_sys); + } + + i2s->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(i2s->clk_ref)) { + if (PTR_ERR(i2s->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(i2s->clk_ref); + } + + ret = clk_prepare_enable(i2s->clk_sys); + if (ret) + return ret; + + reg = IMG_I2S_OUT_CTL_FRM_SIZE_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + reg = IMG_I2S_OUT_CHAN_CTL_JUST_MASK | + IMG_I2S_OUT_CHAN_CTL_LT_MASK | + IMG_I2S_OUT_CHAN_CTL_CH_MASK | + (8 << IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL); + + img_i2s_out_reset(i2s); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_i2s_out_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + i2s->active_channels = 1; + i2s->dma_data.addr = res->start + IMG_I2S_OUT_TX_FIFO; + i2s->dma_data.addr_width = 4; + i2s->dma_data.maxburst = 4; + + i2s->dai_driver.probe = img_i2s_out_dai_probe; + i2s->dai_driver.playback.channels_min = 2; + i2s->dai_driver.playback.channels_max = i2s->max_i2s_chan * 2; + i2s->dai_driver.playback.rates = SNDRV_PCM_RATE_8000_192000; + i2s->dai_driver.playback.formats = SNDRV_PCM_FMTBIT_S32_LE; + i2s->dai_driver.ops = &img_i2s_out_dai_ops; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_i2s_out_component, &i2s->dai_driver, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &img_i2s_out_dma_config, + SND_DMAENGINE_PCM_FLAG_EARLY_START); + if (ret) + goto err_suspend; + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_i2s_out_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(i2s->clk_sys); + + return ret; +} + +static int img_i2s_out_dev_remove(struct platform_device *pdev) +{ + struct img_i2s_out *i2s = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_i2s_out_suspend(&pdev->dev); + + clk_disable_unprepare(i2s->clk_sys); + + return 0; +} + +static const struct of_device_id img_i2s_out_of_match[] = { + { .compatible = "img,i2s-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_i2s_out_of_match); + +static const struct dev_pm_ops img_i2s_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_i2s_out_suspend, + img_i2s_out_resume, NULL) +}; + +static struct platform_driver img_i2s_out_driver = { + .driver = { + .name = "img-i2s-out", + .of_match_table = img_i2s_out_of_match, + .pm = &img_i2s_out_pm_ops + }, + .probe = img_i2s_out_probe, + .remove = img_i2s_out_dev_remove +}; +module_platform_driver(img_i2s_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG I2S Output Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-parallel-out.c b/sound/soc/img/img-parallel-out.c new file mode 100644 index 00000000000000..3414db2d2bcbb1 --- /dev/null +++ b/sound/soc/img/img-parallel-out.c @@ -0,0 +1,339 @@ +/* + * IMG parallel output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_PRL_OUT_TX_FIFO 0 + +#define IMG_PRL_OUT_CTL 0x4 +#define IMG_PRL_OUT_CTL_CH_MASK BIT(4) +#define IMG_PRL_OUT_CTL_PACKH_MASK BIT(3) +#define IMG_PRL_OUT_CTL_EDGE_MASK BIT(2) +#define IMG_PRL_OUT_CTL_ME_MASK BIT(1) +#define IMG_PRL_OUT_CTL_SRST_MASK BIT(0) + +struct img_prl_out { + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + struct reset_control *rst; +}; + +static int img_prl_out_suspend(struct device *dev) +{ + struct img_prl_out *prl = dev_get_drvdata(dev); + + clk_disable_unprepare(prl->clk_ref); + + return 0; +} + +static int img_prl_out_resume(struct device *dev) +{ + struct img_prl_out *prl = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(prl->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static inline void img_prl_out_writel(struct img_prl_out *prl, + u32 val, u32 reg) +{ + writel(val, prl->base + reg); +} + +static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg) +{ + return readl(prl->base + reg); +} + +static void img_prl_out_reset(struct img_prl_out *prl) +{ + u32 ctl; + + ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) & + ~IMG_PRL_OUT_CTL_ME_MASK; + + reset_control_assert(prl->rst); + reset_control_deassert(prl->rst); + + img_prl_out_writel(prl, ctl, IMG_PRL_OUT_CTL); +} + +static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + u32 reg; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg |= IMG_PRL_OUT_CTL_ME_MASK; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + img_prl_out_reset(prl); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_prl_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels; + u32 reg, control_set = 0; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + control_set |= IMG_PRL_OUT_CTL_PACKH_MASK; + break; + case SNDRV_PCM_FORMAT_S24_LE: + break; + default: + return -EINVAL; + } + + if (channels != 2) + return -EINVAL; + + clk_set_rate(prl->clk_ref, rate * 256); + + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + + return 0; +} + +static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + u32 reg, control_set = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + control_set |= IMG_PRL_OUT_CTL_EDGE_MASK; + break; + default: + return -EINVAL; + } + + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + + return 0; +} + +static int img_prl_out_start_at_abort(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(cpu_dai); + + img_prl_out_reset(prl); + + return 0; +} + +static const struct snd_soc_dai_ops img_prl_out_dai_ops = { + .trigger = img_prl_out_trigger, + .hw_params = img_prl_out_hw_params, + .set_fmt = img_prl_out_set_fmt, + .start_at_abort = img_prl_out_start_at_abort +}; + +static int img_prl_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &prl->dma_data, NULL); + + return 0; +} + +static struct snd_soc_dai_driver img_prl_out_dai = { + .probe = img_prl_out_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &img_prl_out_dai_ops +}; + +static const struct snd_soc_component_driver img_prl_out_component = { + .name = "img-prl-out" +}; + +static int img_prl_out_probe(struct platform_device *pdev) +{ + struct img_prl_out *prl; + struct resource *res; + void __iomem *base; + int ret; + struct device *dev = &pdev->dev; + + prl = devm_kzalloc(&pdev->dev, sizeof(*prl), GFP_KERNEL); + if (!prl) + return -ENOMEM; + + platform_set_drvdata(pdev, prl); + + prl->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + prl->base = base; + + prl->rst = devm_reset_control_get(&pdev->dev, "rst"); + if (IS_ERR(prl->rst)) { + if (PTR_ERR(prl->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(prl->rst); + } + + prl->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(prl->clk_sys)) { + if (PTR_ERR(prl->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(prl->clk_sys); + } + + prl->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(prl->clk_ref)) { + if (PTR_ERR(prl->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(prl->clk_ref); + } + + ret = clk_prepare_enable(prl->clk_sys); + if (ret) + return ret; + + img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL); + img_prl_out_reset(prl); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_prl_out_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO; + prl->dma_data.addr_width = 4; + prl->dma_data.maxburst = 4; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_prl_out_component, + &img_prl_out_dai, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + SND_DMAENGINE_PCM_FLAG_EARLY_START); + if (ret) + goto err_suspend; + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_prl_out_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(prl->clk_sys); + + return ret; +} + +static int img_prl_out_dev_remove(struct platform_device *pdev) +{ + struct img_prl_out *prl = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_prl_out_suspend(&pdev->dev); + + clk_disable_unprepare(prl->clk_sys); + + return 0; +} + +static const struct of_device_id img_prl_out_of_match[] = { + { .compatible = "img,parallel-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_prl_out_of_match); + +static const struct dev_pm_ops img_prl_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_prl_out_suspend, + img_prl_out_resume, NULL) +}; + +static struct platform_driver img_prl_out_driver = { + .driver = { + .name = "img-parallel-out", + .of_match_table = img_prl_out_of_match, + .pm = &img_prl_out_pm_ops + }, + .probe = img_prl_out_probe, + .remove = img_prl_out_dev_remove +}; +module_platform_driver(img_prl_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG Parallel Output Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-spdif-in.c b/sound/soc/img/img-spdif-in.c new file mode 100644 index 00000000000000..4d9953d318af44 --- /dev/null +++ b/sound/soc/img/img-spdif-in.c @@ -0,0 +1,806 @@ +/* + * IMG SPDIF input controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_SPDIF_IN_RX_FIFO_OFFSET 0 + +#define IMG_SPDIF_IN_CTL 0x4 +#define IMG_SPDIF_IN_CTL_LOCKLO_MASK 0xff +#define IMG_SPDIF_IN_CTL_LOCKLO_SHIFT 0 +#define IMG_SPDIF_IN_CTL_LOCKHI_MASK 0xff00 +#define IMG_SPDIF_IN_CTL_LOCKHI_SHIFT 8 +#define IMG_SPDIF_IN_CTL_TRK_MASK 0xff0000 +#define IMG_SPDIF_IN_CTL_TRK_SHIFT 16 +#define IMG_SPDIF_IN_CTL_SRD_MASK 0x70000000 +#define IMG_SPDIF_IN_CTL_SRD_SHIFT 28 +#define IMG_SPDIF_IN_CTL_SRT_MASK BIT(31) + +#define IMG_SPDIF_IN_STATUS 0x8 +#define IMG_SPDIF_IN_STATUS_SAM_MASK 0x7000 +#define IMG_SPDIF_IN_STATUS_SAM_SHIFT 12 +#define IMG_SPDIF_IN_STATUS_LOCK_MASK BIT(15) +#define IMG_SPDIF_IN_STATUS_LOCK_SHIFT 15 + +#define IMG_SPDIF_IN_CLKGEN 0x1c +#define IMG_SPDIF_IN_CLKGEN_NOM_MASK 0x3ff +#define IMG_SPDIF_IN_CLKGEN_NOM_SHIFT 0 +#define IMG_SPDIF_IN_CLKGEN_HLD_MASK 0x3ff0000 +#define IMG_SPDIF_IN_CLKGEN_HLD_SHIFT 16 + +#define IMG_SPDIF_IN_CSL 0x20 + +#define IMG_SPDIF_IN_CSH 0x24 +#define IMG_SPDIF_IN_CSH_MASK 0xff +#define IMG_SPDIF_IN_CSH_SHIFT 0 + +#define IMG_SPDIF_IN_SOFT_RESET 0x28 +#define IMG_SPDIF_IN_SOFT_RESET_MASK BIT(0) + +#define IMG_SPDIF_IN_ACLKGEN_START 0x2c +#define IMG_SPDIF_IN_ACLKGEN_NOM_MASK 0x3ff +#define IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT 0 +#define IMG_SPDIF_IN_ACLKGEN_HLD_MASK 0xffc00 +#define IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT 10 +#define IMG_SPDIF_IN_ACLKGEN_TRK_MASK 0xff00000 +#define IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT 20 + +#define IMG_SPDIF_IN_NUM_ACLKGEN 4 + +struct img_spdif_in { + spinlock_t lock; + void __iomem *base; + struct clk *clk_sys; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + unsigned int trk; + bool multi_freq; + int lock_acquire; + int lock_release; + unsigned int single_freq; + unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN]; + bool active; + + /* Write-only registers */ + unsigned int aclkgen_regs[IMG_SPDIF_IN_NUM_ACLKGEN]; +}; + +static inline void img_spdif_in_writel(struct img_spdif_in *spdif, + u32 val, u32 reg) +{ + writel(val, spdif->base + reg); +} + +static inline u32 img_spdif_in_readl(struct img_spdif_in *spdif, u32 reg) +{ + return readl(spdif->base + reg); +} + +static inline void img_spdif_in_aclkgen_writel(struct img_spdif_in *spdif, + u32 index) +{ + img_spdif_in_writel(spdif, spdif->aclkgen_regs[index], + IMG_SPDIF_IN_ACLKGEN_START + (index * 0x4)); +} + +static int img_spdif_in_check_max_rate(struct img_spdif_in *spdif, + unsigned int sample_rate, unsigned long *actual_freq) +{ + unsigned long min_freq, freq_t; + + /* Clock rate must be at least 24x the bit rate */ + min_freq = sample_rate * 2 * 32 * 24; + + freq_t = clk_get_rate(spdif->clk_sys); + + if (freq_t < min_freq) + return -EINVAL; + + *actual_freq = freq_t; + + return 0; +} + +static int img_spdif_in_do_clkgen_calc(unsigned int rate, unsigned int *pnom, + unsigned int *phld, unsigned long clk_rate) +{ + unsigned int ori, nom, hld; + + /* + * Calculate oversampling ratio, nominal phase increment and hold + * increment for the given rate / frequency + */ + + if (!rate) + return -EINVAL; + + ori = clk_rate / (rate * 64); + + if (!ori) + return -EINVAL; + + nom = (4096 / ori) + 1; + do + hld = 4096 - (--nom * (ori - 1)); + while (hld < 120); + + *pnom = nom; + *phld = hld; + + return 0; +} + +static int img_spdif_in_do_clkgen_single(struct img_spdif_in *spdif, + unsigned int rate) +{ + unsigned int nom, hld; + unsigned long flags, clk_rate; + int ret = 0; + u32 reg; + + ret = img_spdif_in_check_max_rate(spdif, rate, &clk_rate); + if (ret) + return ret; + + ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate); + if (ret) + return ret; + + reg = (nom << IMG_SPDIF_IN_CLKGEN_NOM_SHIFT) & + IMG_SPDIF_IN_CLKGEN_NOM_MASK; + reg |= (hld << IMG_SPDIF_IN_CLKGEN_HLD_SHIFT) & + IMG_SPDIF_IN_CLKGEN_HLD_MASK; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CLKGEN); + + spdif->single_freq = rate; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_do_clkgen_multi(struct img_spdif_in *spdif, + unsigned int multi_freqs[]) +{ + unsigned int nom, hld, rate, max_rate = 0; + unsigned long flags, clk_rate; + int i, ret = 0; + u32 reg, trk_reg, temp_regs[IMG_SPDIF_IN_NUM_ACLKGEN]; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) + if (multi_freqs[i] > max_rate) + max_rate = multi_freqs[i]; + + ret = img_spdif_in_check_max_rate(spdif, max_rate, &clk_rate); + if (ret) + return ret; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + rate = multi_freqs[i]; + + ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate); + if (ret) + return ret; + + reg = (nom << IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT) & + IMG_SPDIF_IN_ACLKGEN_NOM_MASK; + reg |= (hld << IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT) & + IMG_SPDIF_IN_ACLKGEN_HLD_MASK; + temp_regs[i] = reg; + } + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + trk_reg = spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + spdif->aclkgen_regs[i] = temp_regs[i] | trk_reg; + img_spdif_in_aclkgen_writel(spdif, i); + } + + spdif->multi_freq = true; + spdif->multi_freqs[0] = multi_freqs[0]; + spdif->multi_freqs[1] = multi_freqs[1]; + spdif->multi_freqs[2] = multi_freqs[2]; + spdif->multi_freqs[3] = multi_freqs[3]; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int img_spdif_in_get_status_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + ucontrol->value.iec958.status[4] = 0xff; + + return 0; +} + +static int img_spdif_in_get_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSL); + ucontrol->value.iec958.status[0] = reg & 0xff; + ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff; + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSH); + ucontrol->value.iec958.status[4] = (reg & IMG_SPDIF_IN_CSH_MASK) + >> IMG_SPDIF_IN_CSH_SHIFT; + + return 0; +} + +static int img_spdif_in_info_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = IMG_SPDIF_IN_NUM_ACLKGEN; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LONG_MAX; + + return 0; +} + +static int img_spdif_in_get_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + if (spdif->multi_freq) { + ucontrol->value.integer.value[0] = spdif->multi_freqs[0]; + ucontrol->value.integer.value[1] = spdif->multi_freqs[1]; + ucontrol->value.integer.value[2] = spdif->multi_freqs[2]; + ucontrol->value.integer.value[3] = spdif->multi_freqs[3]; + } else { + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 0; + ucontrol->value.integer.value[3] = 0; + } + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_set_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN]; + bool multi_freq; + unsigned long flags; + + if ((ucontrol->value.integer.value[0] == 0) && + (ucontrol->value.integer.value[1] == 0) && + (ucontrol->value.integer.value[2] == 0) && + (ucontrol->value.integer.value[3] == 0)) { + multi_freq = false; + } else { + multi_freqs[0] = ucontrol->value.integer.value[0]; + multi_freqs[1] = ucontrol->value.integer.value[1]; + multi_freqs[2] = ucontrol->value.integer.value[2]; + multi_freqs[3] = ucontrol->value.integer.value[3]; + multi_freq = true; + } + + if (multi_freq) + return img_spdif_in_do_clkgen_multi(spdif, multi_freqs); + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->multi_freq = false; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_lock_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LONG_MAX; + + return 0; +} + +static int img_spdif_in_get_lock_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uc) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + int i; + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_STATUS); + if (reg & IMG_SPDIF_IN_STATUS_LOCK_MASK) { + if (spdif->multi_freq) { + i = ((reg & IMG_SPDIF_IN_STATUS_SAM_MASK) >> + IMG_SPDIF_IN_STATUS_SAM_SHIFT) - 1; + uc->value.integer.value[0] = spdif->multi_freqs[i]; + } else { + uc->value.integer.value[0] = spdif->single_freq; + } + } else { + uc->value.integer.value[0] = 0; + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_trk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + + return 0; +} + +static int img_spdif_in_get_trk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->trk; + + return 0; +} + +static int img_spdif_in_set_trk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + int i; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->trk = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_TRK_MASK; + reg |= spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + spdif->aclkgen_regs[i] = (spdif->aclkgen_regs[i] & + ~IMG_SPDIF_IN_ACLKGEN_TRK_MASK) | + (spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT); + + img_spdif_in_aclkgen_writel(spdif, i); + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_lock(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -128; + uinfo->value.integer.max = 127; + + return 0; +} + +static int img_spdif_in_get_lock_acquire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->lock_acquire; + + return 0; +} + +static int img_spdif_in_set_lock_acquire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->lock_acquire = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_LOCKHI_MASK; + reg |= (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKHI_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_get_lock_release(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->lock_release; + + return 0; +} + +static int img_spdif_in_set_lock_release(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->lock_release = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_LOCKLO_MASK; + reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKLO_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static struct snd_kcontrol_new img_spdif_in_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .info = img_spdif_in_iec958_info, + .get = img_spdif_in_get_status_mask + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = img_spdif_in_iec958_info, + .get = img_spdif_in_get_status + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Multi Frequency Acquire", + .info = img_spdif_in_info_multi_freq, + .get = img_spdif_in_get_multi_freq, + .put = img_spdif_in_set_multi_freq + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Frequency", + .info = img_spdif_in_info_lock_freq, + .get = img_spdif_in_get_lock_freq + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock TRK", + .info = img_spdif_in_info_trk, + .get = img_spdif_in_get_trk, + .put = img_spdif_in_set_trk + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Acquire Threshold", + .info = img_spdif_in_info_lock, + .get = img_spdif_in_get_lock_acquire, + .put = img_spdif_in_set_lock_acquire + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Release Threshold", + .info = img_spdif_in_info_lock, + .get = img_spdif_in_get_lock_release, + .put = img_spdif_in_set_lock_release + } +}; + +static int img_spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + unsigned long flags; + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + int ret = 0; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + if (spdif->multi_freq) + reg &= ~IMG_SPDIF_IN_CTL_SRD_MASK; + else + reg |= (1UL << IMG_SPDIF_IN_CTL_SRD_SHIFT); + reg |= IMG_SPDIF_IN_CTL_SRT_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + spdif->active = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_SRT_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + spdif->active = false; + break; + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return ret; +} + +static int img_spdif_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels; + snd_pcm_format_t format; + + rate = params_rate(params); + channels = params_channels(params); + format = params_format(params); + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if (channels != 2) + return -EINVAL; + + return img_spdif_in_do_clkgen_single(spdif, rate); +} + +static const struct snd_soc_dai_ops img_spdif_in_dai_ops = { + .trigger = img_spdif_in_trigger, + .hw_params = img_spdif_in_hw_params +}; + +static int img_spdif_in_dai_probe(struct snd_soc_dai *dai) +{ + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, NULL, &spdif->dma_data); + + snd_soc_add_dai_controls(dai, img_spdif_in_controls, + ARRAY_SIZE(img_spdif_in_controls)); + + return 0; +} + +static struct snd_soc_dai_driver img_spdif_in_dai = { + .probe = img_spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &img_spdif_in_dai_ops +}; + +static const struct snd_soc_component_driver img_spdif_in_component = { + .name = "img-spdif-in" +}; + +static int img_spdif_in_probe(struct platform_device *pdev) +{ + struct img_spdif_in *spdif; + struct resource *res; + void __iomem *base; + int ret; + struct reset_control *rst; + u32 reg; + struct device *dev = &pdev->dev; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + spdif->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->base = base; + + spdif->clk_sys = devm_clk_get(dev, "sys"); + if (IS_ERR(spdif->clk_sys)) { + if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(spdif->clk_sys); + } + + ret = clk_prepare_enable(spdif->clk_sys); + if (ret) + return ret; + + rst = devm_reset_control_get(&pdev->dev, "rst"); + if (IS_ERR(rst)) { + if (PTR_ERR(rst) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_clk_disable; + } + dev_dbg(dev, "No top level reset found\n"); + img_spdif_in_writel(spdif, IMG_SPDIF_IN_SOFT_RESET_MASK, + IMG_SPDIF_IN_SOFT_RESET); + img_spdif_in_writel(spdif, 0, IMG_SPDIF_IN_SOFT_RESET); + } else { + reset_control_assert(rst); + reset_control_deassert(rst); + } + + spin_lock_init(&spdif->lock); + + spdif->dma_data.addr = res->start + IMG_SPDIF_IN_RX_FIFO_OFFSET; + spdif->dma_data.addr_width = 4; + spdif->dma_data.maxburst = 4; + spdif->trk = 0x80; + spdif->lock_acquire = 4; + spdif->lock_release = -128; + + reg = (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKHI_MASK; + reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKLO_MASK; + reg |= (spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT) & + IMG_SPDIF_IN_CTL_TRK_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_spdif_in_component, &img_spdif_in_dai, 1); + if (ret) + goto err_clk_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + clk_disable_unprepare(spdif->clk_sys); + + return ret; +} + +static int img_spdif_in_dev_remove(struct platform_device *pdev) +{ + struct img_spdif_in *spdif = platform_get_drvdata(pdev); + + clk_disable_unprepare(spdif->clk_sys); + + return 0; +} + +static const struct of_device_id img_spdif_in_of_match[] = { + { .compatible = "img,spdif-in" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_spdif_in_of_match); + +static struct platform_driver img_spdif_in_driver = { + .driver = { + .name = "img-spdif-in", + .of_match_table = img_spdif_in_of_match + }, + .probe = img_spdif_in_probe, + .remove = img_spdif_in_dev_remove +}; +module_platform_driver(img_spdif_in_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG SPDIF Input driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-spdif-out.c b/sound/soc/img/img-spdif-out.c new file mode 100644 index 00000000000000..7629a81e1cb9f8 --- /dev/null +++ b/sound/soc/img/img-spdif-out.c @@ -0,0 +1,457 @@ +/* + * IMG SPDIF output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_SPDIF_OUT_TX_FIFO 0x0 + +#define IMG_SPDIF_OUT_CTL 0x4 +#define IMG_SPDIF_OUT_CTL_FS_MASK BIT(4) +#define IMG_SPDIF_OUT_CTL_CLK_MASK BIT(2) +#define IMG_SPDIF_OUT_CTL_SRT_MASK BIT(0) + +#define IMG_SPDIF_OUT_CSL 0x14 + +#define IMG_SPDIF_OUT_CSH_UV 0x18 +#define IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT 0 +#define IMG_SPDIF_OUT_CSH_UV_CSH_MASK 0xff + +struct img_spdif_out { + spinlock_t lock; + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + struct reset_control *rst; +}; + +static int img_spdif_out_suspend(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + + clk_disable_unprepare(spdif->clk_ref); + + return 0; +} + +static int img_spdif_out_resume(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spdif->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static inline void img_spdif_out_writel(struct img_spdif_out *spdif, u32 val, + u32 reg) +{ + writel(val, spdif->base + reg); +} + +static inline u32 img_spdif_out_readl(struct img_spdif_out *spdif, u32 reg) +{ + return readl(spdif->base + reg); +} + +static void img_spdif_out_reset(struct img_spdif_out *spdif) +{ + u32 ctl, status_low, status_high; + + ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL) & + ~IMG_SPDIF_OUT_CTL_SRT_MASK; + status_low = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); + status_high = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + + reset_control_assert(spdif->rst); + reset_control_deassert(spdif->rst); + + img_spdif_out_writel(spdif, ctl, IMG_SPDIF_OUT_CTL); + img_spdif_out_writel(spdif, status_low, IMG_SPDIF_OUT_CSL); + img_spdif_out_writel(spdif, status_high, IMG_SPDIF_OUT_CSH_UV); +} + +static int img_spdif_out_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int img_spdif_out_get_status_mask(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + ucontrol->value.iec958.status[4] = 0xff; + + return 0; +} + +static int img_spdif_out_get_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); + ucontrol->value.iec958.status[0] = reg & 0xff; + ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff; + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + ucontrol->value.iec958.status[4] = + (reg & IMG_SPDIF_OUT_CSH_UV_CSH_MASK) >> + IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_out_set_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + unsigned long flags; + + reg = ((u32)ucontrol->value.iec958.status[3] << 24); + reg |= ((u32)ucontrol->value.iec958.status[2] << 16); + reg |= ((u32)ucontrol->value.iec958.status[1] << 8); + reg |= (u32)ucontrol->value.iec958.status[0]; + + spin_lock_irqsave(&spdif->lock, flags); + + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSL); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + reg &= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK; + reg |= (u32)ucontrol->value.iec958.status[4] << + IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSH_UV); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static struct snd_kcontrol_new img_spdif_out_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = img_spdif_out_info, + .get = img_spdif_out_get_status_mask + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = img_spdif_out_info, + .get = img_spdif_out_get_status, + .put = img_spdif_out_set_status + } +}; + +static int img_spdif_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + u32 reg; + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); + reg |= IMG_SPDIF_OUT_CTL_SRT_MASK; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&spdif->lock, flags); + img_spdif_out_reset(spdif); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_spdif_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int channels; + long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; + u32 reg; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + + dev_dbg(spdif->dev, "hw_params rate %ld channels %u format %u\n", + rate, channels, format); + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if (channels != 2) + return -EINVAL; + + pre_div_a = clk_round_rate(spdif->clk_ref, rate * 256); + if (pre_div_a < 0) + return pre_div_a; + pre_div_b = clk_round_rate(spdif->clk_ref, rate * 384); + if (pre_div_b < 0) + return pre_div_b; + + diff_a = abs((pre_div_a / 256) - rate); + diff_b = abs((pre_div_b / 384) - rate); + + /* If diffs are equal, use lower clock rate */ + if (diff_a > diff_b) + clk_set_rate(spdif->clk_ref, pre_div_b); + else + clk_set_rate(spdif->clk_ref, pre_div_a); + + /* + * Another driver (eg machine driver) may have rejected the above + * change. Get the current rate and set the register bit according to + * the new min diff + */ + clk_rate = clk_get_rate(spdif->clk_ref); + + diff_a = abs((clk_rate / 256) - rate); + diff_b = abs((clk_rate / 384) - rate); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); + if (diff_a <= diff_b) + reg &= ~IMG_SPDIF_OUT_CTL_CLK_MASK; + else + reg |= IMG_SPDIF_OUT_CTL_CLK_MASK; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); + + return 0; +} + +static int img_spdif_out_start_at_abort(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + img_spdif_out_reset(spdif); + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + + +static const struct snd_soc_dai_ops img_spdif_out_dai_ops = { + .trigger = img_spdif_out_trigger, + .hw_params = img_spdif_out_hw_params, + .start_at_abort = img_spdif_out_start_at_abort +}; + +static int img_spdif_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL); + + snd_soc_add_dai_controls(dai, img_spdif_out_controls, + ARRAY_SIZE(img_spdif_out_controls)); + + return 0; +} + +static struct snd_soc_dai_driver img_spdif_out_dai = { + .probe = img_spdif_out_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &img_spdif_out_dai_ops +}; + +static const struct snd_soc_component_driver img_spdif_out_component = { + .name = "img-spdif-out" +}; + +static int img_spdif_out_probe(struct platform_device *pdev) +{ + struct img_spdif_out *spdif; + struct resource *res; + void __iomem *base; + int ret; + struct device *dev = &pdev->dev; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + spdif->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->base = base; + + spdif->rst = devm_reset_control_get(&pdev->dev, "rst"); + if (IS_ERR(spdif->rst)) { + if (PTR_ERR(spdif->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(spdif->rst); + } + + spdif->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(spdif->clk_sys)) { + if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(spdif->clk_sys); + } + + spdif->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(spdif->clk_ref)) { + if (PTR_ERR(spdif->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(spdif->clk_ref); + } + + ret = clk_prepare_enable(spdif->clk_sys); + if (ret) + return ret; + + img_spdif_out_writel(spdif, IMG_SPDIF_OUT_CTL_FS_MASK, + IMG_SPDIF_OUT_CTL); + + img_spdif_out_reset(spdif); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_spdif_out_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + spin_lock_init(&spdif->lock); + + spdif->dma_data.addr = res->start + IMG_SPDIF_OUT_TX_FIFO; + spdif->dma_data.addr_width = 4; + spdif->dma_data.maxburst = 4; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_spdif_out_component, + &img_spdif_out_dai, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + SND_DMAENGINE_PCM_FLAG_EARLY_START); + if (ret) + goto err_suspend; + + dev_dbg(&pdev->dev, "Probe successful\n"); + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_spdif_out_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(spdif->clk_sys); + + return ret; +} + +static int img_spdif_out_dev_remove(struct platform_device *pdev) +{ + struct img_spdif_out *spdif = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_spdif_out_suspend(&pdev->dev); + + clk_disable_unprepare(spdif->clk_sys); + + return 0; +} + +static const struct of_device_id img_spdif_out_of_match[] = { + { .compatible = "img,spdif-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_spdif_out_of_match); + +static const struct dev_pm_ops img_spdif_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_spdif_out_suspend, + img_spdif_out_resume, NULL) +}; + +static struct platform_driver img_spdif_out_driver = { + .driver = { + .name = "img-spdif-out", + .of_match_table = img_spdif_out_of_match, + .pm = &img_spdif_out_pm_ops + }, + .probe = img_spdif_out_probe, + .remove = img_spdif_out_dev_remove +}; +module_platform_driver(img_spdif_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG SPDIF Output driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/pistachio-event-timer-atu.c b/sound/soc/img/pistachio-event-timer-atu.c new file mode 100644 index 00000000000000..698b6ec9f438c0 --- /dev/null +++ b/sound/soc/img/pistachio-event-timer-atu.c @@ -0,0 +1,68 @@ +/* + * Pistachio event timer ATU time units + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pistachio-event-timer.h" +#include "pistachio-event-timer-internal.h" + + +u64 _pistachio_evt_get_time(struct pistachio_evt *evt) +{ + return atu_get_current_time(); +} +EXPORT_SYMBOL_GPL(_pistachio_evt_get_time); + +u64 pistachio_evt_get_time(struct pistachio_evt *evt) +{ + return atu_get_current_time(); +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_time); + +int pistachio_evt_time_to_reg(struct pistachio_evt *evt, u64 time, u32 *reg, + u64 min_time_delta) +{ + return atu_to_frc(time, reg, min_time_delta); +} +EXPORT_SYMBOL_GPL(pistachio_evt_time_to_reg); + +int pistachio_evt_init(struct pistachio_evt *evt) +{ + return atu_cyclecounter_register(&evt->cc, evt->audio_pll); +} +EXPORT_SYMBOL_GPL(pistachio_evt_init); + +void pistachio_evt_deinit(struct pistachio_evt *evt) +{ + atu_cyclecounter_unregister(&evt->cc); +} +EXPORT_SYMBOL_GPL(pistachio_evt_deinit); diff --git a/sound/soc/img/pistachio-event-timer-internal.h b/sound/soc/img/pistachio-event-timer-internal.h new file mode 100644 index 00000000000000..afc69287b5e76e --- /dev/null +++ b/sound/soc/img/pistachio-event-timer-internal.h @@ -0,0 +1,70 @@ +/* + * Imagination Technologies Pistachio Event Timer Internal Header + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#ifndef __IMG_PISTACHIO_EVT_INTERNAL_H__ +#define __IMG_PISTACHIO_EVT_INTERNAL_H__ + +enum pistachio_evt_state { + PISTACHIO_EVT_STATE_IDLE = 0, + PISTACHIO_EVT_STATE_ACTIVE_FIRST, + PISTACHIO_EVT_STATE_ACTIVE_SECOND, + PISTACHIO_EVT_STATE_ACTIVE_THIRD, + PISTACHIO_EVT_STATE_COMPLETE +}; + +struct pistachio_evt_callback { + u64 trigger_time; + u32 cyc; + void (*callback)(struct pistachio_evt *, void *); + void *context; +}; + +struct pistachio_evt_measurement { + enum pistachio_evt_state state; + void (*callback)(void *); + void *context; +}; + +struct pistachio_evt { + struct list_head list; + spinlock_t lock; + struct device *dev; + struct device_node *np; + void __iomem *base; + struct clk *audio_pll; + struct clk *clk_sys; + struct clk *clk_ref_a; + struct clk *clk_ref_b; + const char *ref_names[2]; + struct clk *clk_ref_internal; + struct cyclecounter cc; + struct timecounter tc; + struct notifier_block evt_clk_notifier; + struct hrtimer poll_timer; + ktime_t quarter_rollover; + unsigned long sys_rate; + struct pistachio_evt_callback trigger_cbs[PISTACHIO_EVT_NUM_ENABLES]; + struct pistachio_evt_measurement sample_rates[PISTACHIO_EVT_MAX_SOURCES]; + struct pistachio_evt_measurement phase_difference; +}; + +/* Call with lock held */ +u64 _pistachio_evt_get_time(struct pistachio_evt *evt); +/* Call without lock held */ +u64 pistachio_evt_get_time(struct pistachio_evt *evt); +int pistachio_evt_time_to_reg(struct pistachio_evt *evt, u64 time, + u32 *reg, u64 min_time_delta); +int pistachio_evt_init(struct pistachio_evt *evt); +void pistachio_evt_deinit(struct pistachio_evt *evt); + +void pistachio_evt_clk_rate_change(struct pistachio_evt *evt); +#endif diff --git a/sound/soc/img/pistachio-event-timer-local.c b/sound/soc/img/pistachio-event-timer-local.c new file mode 100644 index 00000000000000..e775a7a548602e --- /dev/null +++ b/sound/soc/img/pistachio-event-timer-local.c @@ -0,0 +1,186 @@ +/* + * Pistachio event timer local time units + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pistachio-event-timer.h" +#include "pistachio-event-timer-internal.h" + +static u64 _pistachio_evt_get_time_cyc(struct pistachio_evt *evt, u32 *cyc) +{ + u64 ret; + + ret = timecounter_read(&evt->tc); + if (cyc) + *cyc = evt->tc.cycle_last; + + return ret; +} + +static u64 pistachio_evt_get_time_cyc(struct pistachio_evt *evt, u32 *cyc) +{ + unsigned long flags; + u64 ret; + + spin_lock_irqsave(&evt->lock, flags); + ret = _pistachio_evt_get_time_cyc(evt, cyc); + spin_unlock_irqrestore(&evt->lock, flags); + + return ret; +} + +u64 _pistachio_evt_get_time(struct pistachio_evt *evt) +{ + return _pistachio_evt_get_time_cyc(evt, NULL); +} +EXPORT_SYMBOL_GPL(_pistachio_evt_get_time); + +u64 pistachio_evt_get_time(struct pistachio_evt *evt) +{ + return pistachio_evt_get_time_cyc(evt, NULL); +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_time); + +int pistachio_evt_time_to_reg(struct pistachio_evt *evt, u64 time, u32 *reg, + u64 min_time_delta) +{ + u64 tmp; + u32 cyc; + + tmp = _pistachio_evt_get_time_cyc(evt, &cyc); + + /* Trigger in the past or too close to current time? */ + if (time < (tmp + min_time_delta)) + return -ETIME; + + /* + * Convert ns difference between current time and trigger time + * to event timer cycles + */ + tmp = (time - tmp) << evt->cc.shift; + do_div(tmp, evt->cc.mult); + + /* Trigger too far into the future (cyc value would be ambiguous)? */ + if (tmp > evt->cc.mask) + return -ETIME; + + /* Calculate cycle value for trigger */ + cyc = (cyc + tmp) & evt->cc.mask; + + /* Final time check before fast write operations */ + tmp = _pistachio_evt_get_time_cyc(evt, &cyc); + if (time < (tmp + min_time_delta)) + return -ETIME; + + return 0; +} +EXPORT_SYMBOL_GPL(pistachio_evt_time_to_reg); + +static enum hrtimer_restart pistachio_evt_poll(struct hrtimer *tmr) +{ + struct pistachio_evt *evt; + u64 tmp; + + evt = container_of(tmr, struct pistachio_evt, poll_timer); + + tmp = pistachio_evt_get_time(evt); + + hrtimer_forward(&evt->poll_timer, + hrtimer_get_expires(&evt->poll_timer), + evt->quarter_rollover); + + return HRTIMER_RESTART; +} + +static void pistachio_evt_start_poll_timer(struct pistachio_evt *evt) +{ + ktime_t ks; + + ks = ktime_get(); + ks = ktime_add(ks, evt->quarter_rollover); + + hrtimer_start(&evt->poll_timer, ks, HRTIMER_MODE_ABS); +} + +static int pistachio_evt_clk_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct pistachio_evt *evt; + + evt = container_of(nb, struct pistachio_evt, evt_clk_notifier); + + switch (event) { + case PRE_RATE_CHANGE: + pistachio_evt_get_time_cyc(evt, NULL); + return NOTIFY_OK; + case POST_RATE_CHANGE: + hrtimer_cancel(&evt->poll_timer); + pistachio_evt_clk_rate_change(evt); + pistachio_evt_get_time_cyc(evt, NULL); + pistachio_evt_start_poll_timer(evt); + return NOTIFY_OK; + case ABORT_RATE_CHANGE: + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +int pistachio_evt_init(struct pistachio_evt *evt) +{ + int ret; + + timecounter_init(&evt->tc, (const struct cyclecounter *)&evt->cc, 0); + + hrtimer_init(&evt->poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + evt->poll_timer.function = pistachio_evt_poll; + + pistachio_evt_start_poll_timer(evt); + + evt->evt_clk_notifier.notifier_call = pistachio_evt_clk_notifier_cb; + + ret = clk_notifier_register(evt->clk_ref_internal, + &evt->evt_clk_notifier); + + if (ret) + hrtimer_cancel(&evt->poll_timer); + + return ret; +} +EXPORT_SYMBOL_GPL(pistachio_evt_init); + +void pistachio_evt_deinit(struct pistachio_evt *evt) +{ + clk_notifier_unregister(evt->clk_ref_internal, &evt->evt_clk_notifier); + hrtimer_cancel(&evt->poll_timer); +} +EXPORT_SYMBOL_GPL(pistachio_evt_deinit); diff --git a/sound/soc/img/pistachio-event-timer.c b/sound/soc/img/pistachio-event-timer.c new file mode 100644 index 00000000000000..4f2c75092ce852 --- /dev/null +++ b/sound/soc/img/pistachio-event-timer.c @@ -0,0 +1,995 @@ +/* + * Pistachio event timer driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pistachio-event-timer.h" +#include "pistachio-event-timer-internal.h" + +#define PISTACHIO_EVT_FIFO_DEPTH 16 + +#define PISTACHIO_EVT_COUNTER 0x0 +#define PISTACHIO_EVT_COUNTER_MASK 0x3fffffff +#define PISTACHIO_EVT_COUNTER_ENABLE_MASK 0x80000000 + +#define PISTACHIO_EVT_TIMESTAMP_STS 0x4 + +#define PISTACHIO_EVT_TIMESTAMP_CLR 0x8 + +#define PISTACHIO_EVT_CLKSRC_SELECT 0xc +#define PISTACHIO_EVT_CLKSRC_SELECT_SHIFT 0 +#define PISTACHIO_EVT_CLKSRC_SELECT_WIDTH 1 + +#define PISTACHIO_EVT_SOURCE_INTERNAL_START 0x10 +#define PISTACHIO_EVT_SOURCE_INTERNAL_MASK 0xf + +#define PISTACHIO_EVT_TIMESTAMP_START 0x40 +#define PISTACHIO_EVT_TIMESTAMP_MASK PISTACHIO_EVT_COUNTER_MASK + +#define PISTACHIO_EVT_TIMER_ENABLE 0x100 +#define PISTACHIO_EVT_TIMER_ENABLE_MASK 0x1 + +#define PISTACHIO_EVT_SOURCES 0x108 +#define PISTACHIO_EVT_SOURCES_SHIFT 16 +#define PISTACHIO_EVT_SOURCES_MASK_LSB 0xffffUL + +#define PISTACHIO_EVT_PHASE_FIFO 0x110 + +#define PISTACHIO_EVT_SAMPLE_FIFO(id) (0x114 + ((id) * 0x4)) + +#define PISTACHIO_EVT_EVENT_CTL 0x120 +#define PISTACHIO_EVT_EVENT_CTL_MASK 0x3 +#define PISTACHIO_EVT_EVENT_CTL_WIDTH 2 + +#define PISTACHIO_EVT_TIME_REG(en) (0x130 + ((en) * 0x4)) + +#define PISTACHIO_EVT_INT_STATUS 0x170 + +#define PISTACHIO_EVT_INT_ENABLE 0x174 + +#define PISTACHIO_EVT_INT_CLEAR 0x178 + +#define PISTACHIO_EVT_INT_SAMPLE_0_FNE_MASK BIT(5) +#define PISTACHIO_EVT_INT_SAMPLE_1_FNE_MASK BIT(9) +#define PISTACHIO_EVT_INT_PHASE_FNE_MASK BIT(1) + +#define PISTACHIO_EVT_EXT_SRC_REG 0x158 +#define PISTACHIO_EVT_EXT_SRC_MASK 0xf +#define PISTACHIO_EVT_EXT_SRC_NUM_BANKS 7 + +#define PISTACHIO_EVT_MIN_EVENT_DELTA_NS 10000 + +static LIST_HEAD(pistachio_evt_list); +static DEFINE_SPINLOCK(pistachio_evt_list_spinlock); + +static inline u32 pistachio_evt_readl(struct pistachio_evt *evt, u32 reg) +{ + return readl(evt->base + reg); +} + +static inline void pistachio_evt_writel(struct pistachio_evt *evt, + u32 val, u32 reg) +{ + writel(val, evt->base + reg); +} + +static inline void pistachio_evt_stop_count(struct pistachio_evt *evt) +{ + u32 reg = pistachio_evt_readl(evt, PISTACHIO_EVT_COUNTER); + + reg &= ~PISTACHIO_EVT_COUNTER_ENABLE_MASK; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_COUNTER); +} + +static inline void pistachio_evt_start_count(struct pistachio_evt *evt) +{ + u32 reg = pistachio_evt_readl(evt, PISTACHIO_EVT_COUNTER); + + reg |= PISTACHIO_EVT_COUNTER_ENABLE_MASK; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_COUNTER); +} + +static inline u32 pistachio_evt_get_count(struct pistachio_evt *evt) +{ + u32 reg = pistachio_evt_readl(evt, PISTACHIO_EVT_COUNTER); + + return reg & PISTACHIO_EVT_COUNTER_MASK; +} + +static cycle_t pistachio_evt_cc_read(const struct cyclecounter *cc) +{ + struct pistachio_evt *evt; + + evt = container_of(cc, struct pistachio_evt, cc); + + return (cycle_t)pistachio_evt_get_count(evt); +} + +void pistachio_evt_get_time_ts(struct pistachio_evt *evt, + struct timespec *ts) +{ + u64 tmp; + + tmp = pistachio_evt_get_time(evt); + ts->tv_nsec = do_div(tmp, NSEC_PER_SEC); + ts->tv_sec = tmp; +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_time_ts); + +static inline bool pistachio_evt_bad_event(enum pistachio_evt_enable event) +{ + switch (event) { + case PISTACHIO_EVT_ENABLE_PARALLEL_OUT: + case PISTACHIO_EVT_ENABLE_I2S_OUT: + case PISTACHIO_EVT_ENABLE_SPDIF_OUT: + case PISTACHIO_EVT_ENABLE_EXTERNAL: + return false; + default: + return true; + } +} + +static struct pistachio_evt_callback *pistachio_evt_get_next_trigger( + struct pistachio_evt *evt, u64 *p_next_trigger) +{ + u64 next_trigger, tmp; + int i; + struct pistachio_evt_callback *cbr = NULL, *cb; + + cb = &evt->trigger_cbs[0]; + next_trigger = ULLONG_MAX; + + for (i = 0; i < PISTACHIO_EVT_NUM_ENABLES; i++, cb++) { + if (!pistachio_evt_bad_event(i)) { + tmp = cb->trigger_time; + if (tmp && (tmp < next_trigger)) { + next_trigger = tmp; + cbr = cb; + } + } + } + + *p_next_trigger = next_trigger; + + return cbr; +} + +struct pistachio_evt *pistachio_evt_get(struct device_node *np) +{ + struct pistachio_evt *evt, *ret = ERR_PTR(-EPROBE_DEFER); + + spin_lock(&pistachio_evt_list_spinlock); + list_for_each_entry(evt, &pistachio_evt_list, list) { + if (evt->np == np) { + ret = evt; + break; + } + } + spin_unlock(&pistachio_evt_list_spinlock); + + return ret; +} +EXPORT_SYMBOL_GPL(pistachio_evt_get); + +void _pistachio_evt_disable_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event) +{ + u32 reg; + + dev_dbg(evt->dev, "Disable event %u\n", (unsigned int)event); + + if (pistachio_evt_bad_event(event)) { + dev_err(evt->dev, "Disable event %u failed (bad event %u)\n", + (unsigned int)event, (unsigned int)event); + return; + } + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_EVENT_CTL); + + reg &= ~(PISTACHIO_EVT_EVENT_CTL_MASK << + (PISTACHIO_EVT_EVENT_CTL_WIDTH * event)); + + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_EVENT_CTL); + + evt->trigger_cbs[event].trigger_time = 0; +} +EXPORT_SYMBOL_GPL(_pistachio_evt_disable_event); + +void pistachio_evt_disable_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event) +{ + unsigned long flags; + + spin_lock_irqsave(&evt->lock, flags); + _pistachio_evt_disable_event(evt, event); + spin_unlock_irqrestore(&evt->lock, flags); +} +EXPORT_SYMBOL_GPL(pistachio_evt_disable_event); + +int pistachio_evt_set_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event, enum pistachio_evt_type type, + struct timespec *ts, + void (*event_trigger_callback)(struct pistachio_evt *, void *), + void *context) +{ + u32 reg, cyc, event_reg_addr, irq_reg_addr; + u64 trigger_time, next_trigger; + unsigned long flags; + struct pistachio_evt_callback *cb; + int ret; + + dev_dbg(evt->dev, "Set event %u type %u time %u,%ld\n", + (unsigned int)event, (unsigned int)type, + (unsigned int)ts->tv_sec, ts->tv_nsec); + + if (pistachio_evt_bad_event(event)) { + dev_err(evt->dev, "Set event %u failed (bad event %u)\n", + (unsigned int)event, (unsigned int)event); + return -EINVAL; + } + + switch (type) { + case PISTACHIO_EVT_TYPE_LEVEL: + case PISTACHIO_EVT_TYPE_PULSE: + break; + default: + dev_err(evt->dev, "Set event %u failed (bad event type %u)\n", + (unsigned int)event, (unsigned int)type); + return -EINVAL; + } + + if (!ts) { + dev_err(evt->dev, "Set event %u failed (ts == NULL)\n", + (unsigned int)event); + return -EINVAL; + } + + event_reg_addr = PISTACHIO_EVT_TIME_REG(event); + irq_reg_addr = PISTACHIO_EVT_TIME_REG(PISTACHIO_EVT_ENABLE_IRQ_0); + + trigger_time = (u64)ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; + + spin_lock_irqsave(&evt->lock, flags); + + /* Trigger already pending for this event? */ + if (evt->trigger_cbs[event].trigger_time) { + spin_unlock_irqrestore(&evt->lock, flags); + dev_err(evt->dev, "Set event %u failed (trigger already pending at %lluns)\n", + (unsigned int)event, + evt->trigger_cbs[event].trigger_time); + return -EINVAL; + } + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_EVENT_CTL); + + /* + * This event may have triggered previously. The control bits need to + * be cleared before programming a new trigger + */ + reg &= ~(PISTACHIO_EVT_EVENT_CTL_MASK << + (PISTACHIO_EVT_EVENT_CTL_WIDTH * event)); + + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_EVENT_CTL); + + reg |= (type << (PISTACHIO_EVT_EVENT_CTL_WIDTH * event)); + + ret = pistachio_evt_time_to_reg(evt, trigger_time, &cyc, + PISTACHIO_EVT_MIN_EVENT_DELTA_NS); + if (ret) { + spin_unlock_irqrestore(&evt->lock, flags); + dev_err(evt->dev, "Set event %u failed (%d)\n", + (unsigned int)event, ret); + return ret; + } + + pistachio_evt_writel(evt, cyc, event_reg_addr); + + cb = pistachio_evt_get_next_trigger(evt, &next_trigger); + + /* + * No irq trigger currently set or the new trigger time is + * earlier than the next trigger time? + */ + if (!cb || (next_trigger > trigger_time)) { + pistachio_evt_writel(evt, cyc, irq_reg_addr); + reg |= PISTACHIO_EVT_TYPE_LEVEL << + (PISTACHIO_EVT_EVENT_CTL_WIDTH * + PISTACHIO_EVT_ENABLE_IRQ_0); + } + + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_EVENT_CTL); + + evt->trigger_cbs[event].callback = event_trigger_callback; + evt->trigger_cbs[event].trigger_time = trigger_time; + evt->trigger_cbs[event].cyc = cyc; + evt->trigger_cbs[event].context = context; + + spin_unlock_irqrestore(&evt->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pistachio_evt_set_event); + +static bool pistachio_evt_retrigger(struct pistachio_evt *evt, + struct pistachio_evt_callback *cb) +{ + u32 reg, trig_reg_addr; + u64 cur_time; + + trig_reg_addr = PISTACHIO_EVT_TIME_REG(PISTACHIO_EVT_ENABLE_IRQ_0); + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_EVENT_CTL); + + reg |= (PISTACHIO_EVT_TYPE_LEVEL << + (PISTACHIO_EVT_EVENT_CTL_WIDTH * PISTACHIO_EVT_ENABLE_IRQ_0)); + + pistachio_evt_writel(evt, cb->cyc, trig_reg_addr); + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_EVENT_CTL); + + cur_time = _pistachio_evt_get_time(evt); + + /* Trigger passed while writing? */ + if (cb->trigger_time < cur_time) + return false; + + return true; +} + +static irqreturn_t pistachio_evt_trigger_0_irq(int irq, void *dev_id) +{ + struct pistachio_evt *evt = (struct pistachio_evt *)dev_id; + u64 next_trigger, cur_time; + struct pistachio_evt_callback *cb; + unsigned long flags; + u32 reg; + + dev_dbg(evt->dev, "Trigger IRQ\n"); + + spin_lock_irqsave(&evt->lock, flags); + + while (1) { + cb = pistachio_evt_get_next_trigger(evt, &next_trigger); + + /* Disable the irq trigger */ + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_EVENT_CTL); + reg &= ~(PISTACHIO_EVT_EVENT_CTL_MASK << + (PISTACHIO_EVT_EVENT_CTL_WIDTH * + PISTACHIO_EVT_ENABLE_IRQ_0)); + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_EVENT_CTL); + + if (!cb) + break; + + cur_time = _pistachio_evt_get_time(evt); + + if (cur_time >= next_trigger) { + if (cb->callback) + cb->callback(evt, cb->context); + cb->trigger_time = 0; + } else if (pistachio_evt_retrigger(evt, cb)) { + break; + } else { + if (cb->callback) + cb->callback(evt, cb->context); + cb->trigger_time = 0; + } + } + + spin_unlock_irqrestore(&evt->lock, flags); + + return IRQ_HANDLED; +} + +int pistachio_evt_set_source(struct pistachio_evt *evt, + int id, enum pistachio_evt_source source) +{ + unsigned long flags; + u32 reg; + + if ((id >= PISTACHIO_EVT_MAX_SOURCES) || + (source >= PISTACHIO_EVT_NUM_SOURCES)) + return -EINVAL; + + spin_lock_irqsave(&evt->lock, flags); + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_SOURCES); + reg &= ~(PISTACHIO_EVT_SOURCES_MASK_LSB << + (id * PISTACHIO_EVT_SOURCES_SHIFT)); + reg |= source << (id * PISTACHIO_EVT_SOURCES_SHIFT); + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_SOURCES); + + /* + * Changing one of the sources invalidates the active sample rate + * measurement for the source in question, and the active phase + * difference measurement, so reset these states and mask the + * interrupts + */ + evt->sample_rates[id].state = PISTACHIO_EVT_STATE_IDLE; + evt->phase_difference.state = PISTACHIO_EVT_STATE_IDLE; + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + if (id == 0) + reg &= ~PISTACHIO_EVT_INT_SAMPLE_0_FNE_MASK; + else + reg &= ~PISTACHIO_EVT_INT_SAMPLE_1_FNE_MASK; + reg &= ~PISTACHIO_EVT_INT_PHASE_FNE_MASK; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_INT_ENABLE); + + spin_unlock_irqrestore(&evt->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(pistachio_evt_set_source); + +int pistachio_evt_get_source(struct pistachio_evt *evt, + int id, enum pistachio_evt_source *source) +{ + u32 reg; + + if (id >= PISTACHIO_EVT_MAX_SOURCES) + return -EINVAL; + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_SOURCES); + + *source = (reg >> (id * PISTACHIO_EVT_SOURCES_SHIFT)) & + PISTACHIO_EVT_SOURCES_MASK_LSB; + + return 0; +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_source); + +static void pistachio_evt_clear_fifo(struct pistachio_evt *evt, + u32 fifo_offset, u32 mask, bool enable_int) +{ + u32 reg; + + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + if (enable_int) + reg |= mask; + else + reg &= ~mask; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_INT_ENABLE); + + while (1) { + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_STATUS); + if (!(reg & mask)) + break; + reg = pistachio_evt_readl(evt, fifo_offset); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + } +} + +static void pistachio_evt_new_sr(struct pistachio_evt *evt, int id, u32 mask) +{ + u32 reg; + enum pistachio_evt_state new_state; + struct pistachio_evt_measurement *sr = &evt->sample_rates[id]; + + switch (sr->state) { + case PISTACHIO_EVT_STATE_ACTIVE_FIRST: + /* First sample rate measurement is always invalid */ + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_SAMPLE_FIFO(id)); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_STATUS); + if (reg & mask) + new_state = PISTACHIO_EVT_STATE_COMPLETE; + else + new_state = PISTACHIO_EVT_STATE_ACTIVE_SECOND; + break; + + case PISTACHIO_EVT_STATE_ACTIVE_SECOND: + new_state = PISTACHIO_EVT_STATE_COMPLETE; + break; + + default: + dev_err(evt->dev, "pistachio_evt_new_sr bad state (%d)\n", + (int)sr->state); + return; + } + + if (new_state == PISTACHIO_EVT_STATE_COMPLETE) { + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + reg &= ~mask; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_INT_ENABLE); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + if (sr->callback) + sr->callback(sr->context); + } + + sr->state = new_state; +} + +static void pistachio_evt_new_pd(struct pistachio_evt *evt) +{ + u32 reg; + enum pistachio_evt_state new_state; + u32 mask = PISTACHIO_EVT_INT_PHASE_FNE_MASK; + struct pistachio_evt_measurement *pd = &evt->phase_difference; + + switch (pd->state) { + case PISTACHIO_EVT_STATE_ACTIVE_FIRST: + /* First two phase measurements are always invalid */ + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_PHASE_FIFO); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_STATUS); + if (!(reg & mask)) { + new_state = PISTACHIO_EVT_STATE_ACTIVE_SECOND; + break; + } + /* Fall through */ + case PISTACHIO_EVT_STATE_ACTIVE_SECOND: + /* First two phase measurements are always invalid */ + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_PHASE_FIFO); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_STATUS); + if (reg & mask) + new_state = PISTACHIO_EVT_STATE_COMPLETE; + else + new_state = PISTACHIO_EVT_STATE_ACTIVE_THIRD; + break; + + case PISTACHIO_EVT_STATE_ACTIVE_THIRD: + new_state = PISTACHIO_EVT_STATE_COMPLETE; + break; + + default: + dev_err(evt->dev, "pistachio_evt_new_pd bad state (%d)\n", + (int)pd->state); + return; + } + + if (new_state == PISTACHIO_EVT_STATE_COMPLETE) { + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + reg &= ~mask; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_INT_ENABLE); + pistachio_evt_writel(evt, mask, PISTACHIO_EVT_INT_CLEAR); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_INT_CLEAR); + if (pd->callback) + pd->callback(pd->context); + } + + pd->state = new_state; +} + +static irqreturn_t pistachio_evt_general_irq(int irq, void *dev_id) +{ + struct pistachio_evt *evt = (struct pistachio_evt *)dev_id; + unsigned long flags; + u32 mask, i, isr, ier; + + spin_lock_irqsave(&evt->lock, flags); + + while (1) { + isr = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_STATUS); + ier = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + isr &= ier; + + if (!isr) + break; + + for (i = 0; i < PISTACHIO_EVT_MAX_SOURCES; i++) { + if (i == 0) + mask = PISTACHIO_EVT_INT_SAMPLE_0_FNE_MASK; + else + mask = PISTACHIO_EVT_INT_SAMPLE_1_FNE_MASK; + + if (isr & mask) + pistachio_evt_new_sr(evt, i, mask); + } + + if (isr & PISTACHIO_EVT_INT_PHASE_FNE_MASK) + pistachio_evt_new_pd(evt); + } + + spin_unlock_irqrestore(&evt->lock, flags); + + return IRQ_HANDLED; +} + +int pistachio_evt_get_sample_rate(struct pistachio_evt *evt, int id, + u32 *val, u32 *sys_freq, + void (*callback)(void *context), void *context) +{ + unsigned long flags; + u32 mask; + int ret; + + if (id >= PISTACHIO_EVT_MAX_SOURCES) + return -EINVAL; + + spin_lock_irqsave(&evt->lock, flags); + + switch (evt->sample_rates[id].state) { + case PISTACHIO_EVT_STATE_IDLE: + if (id == 0) + mask = PISTACHIO_EVT_INT_SAMPLE_0_FNE_MASK; + else + mask = PISTACHIO_EVT_INT_SAMPLE_1_FNE_MASK; + + pistachio_evt_clear_fifo(evt, PISTACHIO_EVT_SAMPLE_FIFO(id), + mask, true); + + ret = -EBUSY; + evt->sample_rates[id].state = PISTACHIO_EVT_STATE_ACTIVE_FIRST; + evt->sample_rates[id].callback = callback; + evt->sample_rates[id].context = context; + break; + + case PISTACHIO_EVT_STATE_COMPLETE: + *val = pistachio_evt_readl(evt, PISTACHIO_EVT_SAMPLE_FIFO(id)); + *sys_freq = evt->sys_rate; + evt->sample_rates[id].state = PISTACHIO_EVT_STATE_IDLE; + ret = 0; + break; + + default: + ret = -EBUSY; + break; + } + + spin_unlock_irqrestore(&evt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_sample_rate); + +extern int pistachio_evt_get_phase_difference(struct pistachio_evt *evt, + u32 *val, u32 *sys_freq, + void (*callback)(void *context), void *context) +{ + unsigned long flags; + u32 mask; + int ret = 0; + + spin_lock_irqsave(&evt->lock, flags); + + switch (evt->phase_difference.state) { + case PISTACHIO_EVT_STATE_IDLE: + mask = PISTACHIO_EVT_INT_PHASE_FNE_MASK; + + pistachio_evt_clear_fifo(evt, PISTACHIO_EVT_PHASE_FIFO, + mask, true); + + ret = -EBUSY; + evt->phase_difference.state = PISTACHIO_EVT_STATE_ACTIVE_FIRST; + evt->phase_difference.callback = callback; + evt->phase_difference.context = context; + break; + + case PISTACHIO_EVT_STATE_COMPLETE: + *val = pistachio_evt_readl(evt, PISTACHIO_EVT_PHASE_FIFO); + *sys_freq = evt->sys_rate; + evt->phase_difference.state = PISTACHIO_EVT_STATE_IDLE; + break; + + default: + ret = -EBUSY; + break; + } + + spin_unlock_irqrestore(&evt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(pistachio_evt_get_phase_difference); + +void pistachio_evt_abort_measurements(struct pistachio_evt *evt) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&evt->lock, flags); + evt->sample_rates[0].state = PISTACHIO_EVT_STATE_IDLE; + evt->sample_rates[1].state = PISTACHIO_EVT_STATE_IDLE; + evt->phase_difference.state = PISTACHIO_EVT_STATE_IDLE; + reg = pistachio_evt_readl(evt, PISTACHIO_EVT_INT_ENABLE); + reg &= ~PISTACHIO_EVT_INT_SAMPLE_0_FNE_MASK; + reg &= ~PISTACHIO_EVT_INT_SAMPLE_1_FNE_MASK; + reg &= ~PISTACHIO_EVT_INT_PHASE_FNE_MASK; + pistachio_evt_writel(evt, reg, PISTACHIO_EVT_INT_ENABLE); + spin_unlock_irqrestore(&evt->lock, flags); +} +EXPORT_SYMBOL_GPL(pistachio_evt_abort_measurements); + +void pistachio_evt_clk_rate_change(struct pistachio_evt *evt) +{ + u64 tmp; + unsigned long flags; + unsigned long rate; + ktime_t quarter_rollover; + u32 mult, shift, mask; + + dev_dbg(evt->dev, "pistachio_evt_clk_rate_change()\n"); + + mask = PISTACHIO_EVT_COUNTER_MASK; + + rate = clk_get_rate(evt->clk_ref_internal); + + tmp = ((u64)mask + 1) * NSEC_PER_SEC; + do_div(tmp, rate); + tmp >>= 2; + quarter_rollover.tv64 = tmp; + + clocks_calc_mult_shift(&mult, &shift, rate, + NSEC_PER_SEC, DIV_ROUND_UP(mask, rate)); + + spin_lock_irqsave(&evt->lock, flags); + evt->quarter_rollover = quarter_rollover; + evt->cc.mult = mult; + evt->cc.shift = shift; + spin_unlock_irqrestore(&evt->lock, flags); + + dev_dbg(evt->dev, "rate %ld cc mult %u shift %u\n", rate, evt->cc.mult, + evt->cc.shift); +} + +static int pistachio_evt_driver_probe(struct platform_device *pdev) +{ + struct pistachio_evt *evt; + int ret, irq; + struct device_node *np = pdev->dev.of_node; + u32 clk_select, rate, ext_src_bank; + struct resource iomem; + struct device *dev = &pdev->dev; + struct regmap *periph_regs; + + evt = devm_kzalloc(&pdev->dev, sizeof(*evt), GFP_KERNEL); + if (!evt) + return -ENOMEM; + platform_set_drvdata(pdev, evt); + + evt->dev = dev; + evt->np = np; + + spin_lock_init(&evt->lock); + + ret = of_address_to_resource(np, 0, &iomem); + if (ret) { + dev_err(dev, "Could not get IO memory\n"); + return ret; + } + + evt->base = devm_ioremap_resource(dev, &iomem); + if (IS_ERR(evt->base)) + return PTR_ERR(evt->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "can't get general irq\n"); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, pistachio_evt_general_irq, + 0, pdev->name, evt); + if (ret) { + dev_err(&pdev->dev, "can't request irq %d\n", irq); + return ret; + } + + irq = platform_get_irq(pdev, 3); + if (irq < 0) { + dev_err(&pdev->dev, "can't get trigger 0 irq\n"); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, pistachio_evt_trigger_0_irq, + 0, pdev->name, evt); + if (ret) { + dev_err(&pdev->dev, "can't request irq %d\n", irq); + return ret; + } + + periph_regs = syscon_regmap_lookup_by_phandle(np, "img,cr-periph"); + if (IS_ERR(periph_regs)) + return PTR_ERR(periph_regs); + + if (of_property_read_u32(np, "img,ext-src-bank", &ext_src_bank)) { + dev_err(&pdev->dev, "No img,ext-src-bank property\n"); + return -EINVAL; + } + + if (ext_src_bank >= PISTACHIO_EVT_EXT_SRC_NUM_BANKS) + return -EINVAL; + + regmap_update_bits(periph_regs, PISTACHIO_EVT_EXT_SRC_REG, + PISTACHIO_EVT_EXT_SRC_MASK, ext_src_bank); + + if (of_property_read_u32(np, "img,clk-select", &clk_select)) { + dev_err(&pdev->dev, "No img,clk-select property\n"); + return -EINVAL; + } + + if (clk_select > 1) + return -EINVAL; + + if (of_property_read_u32(np, "img,clk-rate", &rate)) + rate = 0; + + evt->audio_pll = devm_clk_get(&pdev->dev, "pll"); + if (IS_ERR(evt->audio_pll)) + return PTR_ERR(evt->audio_pll); + + ret = clk_prepare_enable(evt->audio_pll); + if (ret) + return ret; + + evt->clk_ref_a = devm_clk_get(&pdev->dev, "ref0"); + if (IS_ERR(evt->clk_ref_a)) { + ret = PTR_ERR(evt->audio_pll); + goto err_pll; + } + + ret = clk_prepare_enable(evt->clk_ref_a); + if (ret) + goto err_pll; + + evt->clk_ref_b = devm_clk_get(&pdev->dev, "ref1"); + if (IS_ERR(evt->clk_ref_b)) { + ret = PTR_ERR(evt->clk_ref_b); + goto err_ref_a; + } + + ret = clk_prepare_enable(evt->clk_ref_b); + if (ret) + goto err_ref_a; + + evt->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(evt->clk_sys)) { + ret = PTR_ERR(evt->clk_sys); + goto err_ref_b; + } + + ret = clk_prepare_enable(evt->clk_sys); + if (ret) + goto err_ref_b; + + evt->sys_rate = clk_get_rate(evt->clk_sys); + + evt->ref_names[0] = __clk_get_name(evt->clk_ref_a); + evt->ref_names[1] = __clk_get_name(evt->clk_ref_b); + + evt->clk_ref_internal = clk_register_mux(NULL, "event_timer_internal", + evt->ref_names, 2, CLK_SET_RATE_PARENT | + CLK_SET_RATE_NO_REPARENT, + evt->base + PISTACHIO_EVT_CLKSRC_SELECT, + PISTACHIO_EVT_CLKSRC_SELECT_SHIFT, + PISTACHIO_EVT_CLKSRC_SELECT_WIDTH, + 0, NULL); + + if (IS_ERR(evt->clk_ref_internal)) { + ret = PTR_ERR(evt->clk_ref_internal); + goto err_sys; + } + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + evt->clk_ref_internal); + if (ret) + goto err_mux; + + if (clk_select == 0) + ret = clk_set_parent(evt->clk_ref_internal, evt->clk_ref_a); + else + ret = clk_set_parent(evt->clk_ref_internal, evt->clk_ref_b); + + if (ret) + goto err_clkp; + + if (rate) { + ret = clk_set_rate(evt->clk_ref_internal, rate); + if (ret) + goto err_clkp; + } + + evt->cc.mask = PISTACHIO_EVT_COUNTER_MASK; + evt->cc.read = pistachio_evt_cc_read; + + pistachio_evt_writel(evt, PISTACHIO_EVT_TIMER_ENABLE_MASK, + PISTACHIO_EVT_TIMER_ENABLE); + + pistachio_evt_start_count(evt); + + pistachio_evt_clk_rate_change(evt); + + ret = pistachio_evt_init(evt); + if (ret) + goto err_count; + + spin_lock(&pistachio_evt_list_spinlock); + list_add(&evt->list, &pistachio_evt_list); + spin_unlock(&pistachio_evt_list_spinlock); + + return 0; + +err_count: + pistachio_evt_stop_count(evt); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_TIMER_ENABLE); +err_clkp: + of_clk_del_provider(np); +err_mux: + clk_unregister(evt->clk_ref_internal); +err_sys: + clk_disable_unprepare(evt->clk_sys); +err_ref_b: + clk_disable_unprepare(evt->clk_ref_b); +err_ref_a: + clk_disable_unprepare(evt->clk_ref_a); +err_pll: + clk_disable_unprepare(evt->audio_pll); + + return ret; +} + +static const struct of_device_id pistachio_evt_of_match[] = { + { .compatible = "img,pistachio-event-timer" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pistachio_evt_of_match); + +static int pistachio_evt_driver_remove(struct platform_device *pdev) +{ + struct pistachio_evt *evt = platform_get_drvdata(pdev); + + spin_lock(&pistachio_evt_list_spinlock); + list_del(&evt->list); + spin_unlock(&pistachio_evt_list_spinlock); + pistachio_evt_deinit(evt); + pistachio_evt_stop_count(evt); + pistachio_evt_writel(evt, 0, PISTACHIO_EVT_TIMER_ENABLE); + of_clk_del_provider(evt->dev->of_node); + clk_unregister(evt->clk_ref_internal); + clk_disable_unprepare(evt->clk_sys); + clk_disable_unprepare(evt->clk_ref_b); + clk_disable_unprepare(evt->clk_ref_a); + clk_disable_unprepare(evt->audio_pll); + + return 0; +} + +static struct platform_driver pistachio_evt_driver = { + .driver = { + .name = "pistachio-event-timer", + .of_match_table = pistachio_evt_of_match, + }, + .probe = pistachio_evt_driver_probe, + .remove = pistachio_evt_driver_remove, +}; +module_platform_driver(pistachio_evt_driver); + +MODULE_DESCRIPTION("Event Timer driver"); +MODULE_AUTHOR("Damien Horsley"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/pistachio-event-timer.h b/sound/soc/img/pistachio-event-timer.h new file mode 100644 index 00000000000000..0f0d4b6b5abb8b --- /dev/null +++ b/sound/soc/img/pistachio-event-timer.h @@ -0,0 +1,82 @@ +/* + * Imagination Technologies Pistachio Event Timer Header + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#ifndef __IMG_PISTACHIO_EVT_H__ +#define __IMG_PISTACHIO_EVT_H__ + +struct pistachio_evt; + +enum pistachio_evt_enable { + PISTACHIO_EVT_ENABLE_PARALLEL_OUT = 0, + PISTACHIO_EVT_ENABLE_I2S_OUT, + PISTACHIO_EVT_ENABLE_SPDIF_OUT, + PISTACHIO_EVT_ENABLE_IRQ_0, + PISTACHIO_EVT_ENABLE_IRQ_1, + PISTACHIO_EVT_ENABLE_EXTERNAL, + PISTACHIO_EVT_NUM_ENABLES +}; + +enum pistachio_evt_type { + PISTACHIO_EVT_TYPE_PULSE = 1, + PISTACHIO_EVT_TYPE_LEVEL +}; + +enum pistachio_evt_source { + PISTACHIO_EVT_SOURCE_EXTERNAL = 0, + PISTACHIO_EVT_SOURCE_SPDIF_IN, + PISTACHIO_EVT_SOURCE_SPDIF_OUT, + PISTACHIO_EVT_SOURCE_I2S_IN, + PISTACHIO_EVT_SOURCE_I2S_OUT, + PISTACHIO_EVT_SOURCE_PARALLEL_OUT, + PISTACHIO_EVT_NUM_SOURCES +}; + +#define PISTACHIO_EVT_NUM_TIMESTAMP_MODULES 12 + +#define PISTACHIO_EVT_MAX_SOURCES 2 + +extern struct pistachio_evt *pistachio_evt_get(struct device_node *np); + +extern void pistachio_evt_get_time_ts(struct pistachio_evt *evt, + struct timespec *ts); + +/* Call this outside of an event callback */ +extern void pistachio_evt_disable_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event); + +/* Call this inside of an event callback */ +extern void _pistachio_evt_disable_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event); + +extern int pistachio_evt_set_event(struct pistachio_evt *evt, + enum pistachio_evt_enable event, enum pistachio_evt_type type, + struct timespec *ts, + void (*event_trigger_callback)(struct pistachio_evt *, void *), + void *context); + +extern int pistachio_evt_set_source(struct pistachio_evt *evt, int id, + enum pistachio_evt_source source); + +extern int pistachio_evt_get_source(struct pistachio_evt *evt, + int id, enum pistachio_evt_source *source); + +extern int pistachio_evt_get_sample_rate(struct pistachio_evt *evt, int id, + u32 *val, u32 *sys_freq, + void (*callback)(void *), void *context); + +extern int pistachio_evt_get_phase_difference(struct pistachio_evt *evt, + u32 *val, u32 *sys_freq, + void (*callback)(void *), void *context); + +extern void pistachio_evt_abort_measurements(struct pistachio_evt *evt); + +#endif diff --git a/sound/soc/img/pistachio-internal-dac.c b/sound/soc/img/pistachio-internal-dac.c new file mode 100644 index 00000000000000..162a0fd68c7b1d --- /dev/null +++ b/sound/soc/img/pistachio-internal-dac.c @@ -0,0 +1,287 @@ +/* + * Pistachio internal dac driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PISTACHIO_INTERNAL_DAC_CTRL 0x40 +#define PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK 0x2 +#define PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_SRST 0x44 +#define PISTACHIO_INTERNAL_DAC_SRST_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL 0x48 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT 0 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK 0xFFF +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK 0x1000 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT 13 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK 0x1FE000 + +#define PISTACHIO_INTERNAL_DAC_PWR 0x1 +#define PISTACHIO_INTERNAL_DAC_PWR_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +/* codec private data */ +struct pistachio_internal_dac { + struct regmap *regmap; + struct regulator *supply; + bool mute; +}; + +static const struct snd_kcontrol_new pistachio_internal_dac_snd_controls[] = { + SOC_SINGLE("Playback Switch", PISTACHIO_INTERNAL_DAC_CTRL, 2, 1, 1) +}; + +static const struct snd_soc_dapm_widget pistachio_internal_dac_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route pistachio_internal_dac_routes[] = { + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +static void pistachio_internal_dac_reg_writel(struct regmap *top_regs, + u32 val, u32 reg) +{ + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK, + reg << PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK, + val << PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, 0); +} + +static void pistachio_internal_dac_pwr_off(struct pistachio_internal_dac *dac) +{ + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK); + + pistachio_internal_dac_reg_writel(dac->regmap, 0, + PISTACHIO_INTERNAL_DAC_PWR); +} + +static void pistachio_internal_dac_pwr_on(struct pistachio_internal_dac *dac) +{ + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST, + PISTACHIO_INTERNAL_DAC_SRST_MASK, + PISTACHIO_INTERNAL_DAC_SRST_MASK); + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST, + PISTACHIO_INTERNAL_DAC_SRST_MASK, 0); + + pistachio_internal_dac_reg_writel(dac->regmap, + PISTACHIO_INTERNAL_DAC_PWR_MASK, + PISTACHIO_INTERNAL_DAC_PWR); + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, 0); +} + +static struct snd_soc_dai_driver pistachio_internal_dac_dais[] = { + { + .name = "pistachio_internal_dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = PISTACHIO_INTERNAL_DAC_FORMATS, + } + }, +}; + +static int pistachio_internal_dac_codec_probe(struct snd_soc_codec *codec) +{ + struct pistachio_internal_dac *dac = snd_soc_codec_get_drvdata(codec); + + snd_soc_codec_init_regmap(codec, dac->regmap); + + return 0; +} + +static const struct snd_soc_codec_driver pistachio_internal_dac_driver = { + .probe = pistachio_internal_dac_codec_probe, + .idle_bias_off = true, + .controls = pistachio_internal_dac_snd_controls, + .num_controls = ARRAY_SIZE(pistachio_internal_dac_snd_controls), + .dapm_widgets = pistachio_internal_dac_widgets, + .num_dapm_widgets = ARRAY_SIZE(pistachio_internal_dac_widgets), + .dapm_routes = pistachio_internal_dac_routes, + .num_dapm_routes = ARRAY_SIZE(pistachio_internal_dac_routes), +}; + +static int pistachio_internal_dac_probe(struct platform_device *pdev) +{ + struct pistachio_internal_dac *dac; + int ret, voltage; + struct device *dev = &pdev->dev; + u32 reg; + + dac = devm_kzalloc(dev, sizeof(*dac), GFP_KERNEL); + + if (!dac) + return -ENOMEM; + + platform_set_drvdata(pdev, dac); + + dac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-top"); + if (IS_ERR(dac->regmap)) + return PTR_ERR(dac->regmap); + + dac->supply = devm_regulator_get(dev, "VDD"); + if (IS_ERR(dac->supply)) { + ret = PTR_ERR(dac->supply); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to acquire supply 'VDD-supply': %d\n", ret); + return ret; + } + + ret = regulator_enable(dac->supply); + if (ret) { + dev_err(dev, "failed to enable supply: %d\n", ret); + return ret; + } + + voltage = regulator_get_voltage(dac->supply); + + switch (voltage) { + case 1800000: + reg = 0; + break; + case 3300000: + reg = PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK; + break; + default: + dev_err(dev, "invalid voltage: %d\n", voltage); + ret = -EINVAL; + goto err_regulator; + } + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK, reg); + + pistachio_internal_dac_pwr_off(dac); + pistachio_internal_dac_pwr_on(dac); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = snd_soc_register_codec(dev, &pistachio_internal_dac_driver, + pistachio_internal_dac_dais, + ARRAY_SIZE(pistachio_internal_dac_dais)); + if (ret) { + dev_err(dev, "failed to register codec: %d\n", ret); + goto err_pwr; + } + + return 0; + +err_pwr: + pm_runtime_disable(&pdev->dev); + pistachio_internal_dac_pwr_off(dac); +err_regulator: + regulator_disable(dac->supply); + + return ret; +} + +static int pistachio_internal_dac_remove(struct platform_device *pdev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pistachio_internal_dac_pwr_off(dac); + regulator_disable(dac->supply); + + return 0; +} + +#ifdef CONFIG_PM +static int pistachio_internal_dac_rt_resume(struct device *dev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(dac->supply); + if (ret) { + dev_err(dev, "failed to enable supply: %d\n", ret); + return ret; + } + + pistachio_internal_dac_pwr_on(dac); + + return 0; +} + +static int pistachio_internal_dac_rt_suspend(struct device *dev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(dev); + + pistachio_internal_dac_pwr_off(dac); + + regulator_disable(dac->supply); + + return 0; +} +#endif + +static const struct dev_pm_ops pistachio_internal_dac_pm_ops = { + SET_RUNTIME_PM_OPS(pistachio_internal_dac_rt_suspend, + pistachio_internal_dac_rt_resume, NULL) +}; + +static const struct of_device_id pistachio_internal_dac_of_match[] = { + { .compatible = "img,pistachio-internal-dac" }, + {} +}; +MODULE_DEVICE_TABLE(of, pistachio_internal_dac_of_match); + +static struct platform_driver pistachio_internal_dac_plat_driver = { + .driver = { + .name = "img-pistachio-internal-dac", + .of_match_table = pistachio_internal_dac_of_match, + .pm = &pistachio_internal_dac_pm_ops + }, + .probe = pistachio_internal_dac_probe, + .remove = pistachio_internal_dac_remove +}; +module_platform_driver(pistachio_internal_dac_plat_driver); + +MODULE_DESCRIPTION("Pistachio Internal DAC driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/pistachio.c b/sound/soc/img/pistachio.c new file mode 100644 index 00000000000000..a861b8ce886047 --- /dev/null +++ b/sound/soc/img/pistachio.c @@ -0,0 +1,2889 @@ +/* + * Pistachio audio card driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "../codecs/tpa6130a2.h" + +#include + +#include "pistachio-event-timer.h" + +#define PISTACHIO_PLL_RATE_A 147456000 +#define PISTACHIO_PLL_RATE_B 135475200 +#define PISTACHIO_MAX_DIV 256 +#define PISTACHIO_MIN_MCLK_FREQ (135475200 / 256) + +#define PISTACHIO_CLOCK_MASTER_EXT -1 +#define PISTACHIO_CLOCK_MASTER_LOOPBACK -2 + +#define PISTACHIO_MAX_I2S_CODECS 18 + +#define PISTACHIO_MAX_FS_RATES 20 + +#define PISTACHIO_I2S_MCLK_MAX_FREQ 200000000 +#define PISTACHIO_DAC_MCLK_MAX_FREQ 200000000 + +#define PISTACHIO_INTERNAL_DAC_PREFIX "internal-dac" + +#define PISTACHIO_TPA6130A2_PREFIX "tpa" + +#define PISTACHIO_I2S_LOOPBACK_REG 0x88 +#define PISTACHIO_I2S_LOOPBACK_CLK_MASK 0x3 +#define PISTACHIO_I2S_LOOPBACK_CLK_SHIFT 0 + +#define PISTACHIO_I2S_LOOPBACK_CLK_NONE 0 +#define PISTACHIO_I2S_LOOPBACK_CLK_MFIO 1 +#define PISTACHIO_I2S_LOOPBACK_CLK_LOCAL 2 + +struct pistachio_start_at { + enum pistachio_evt_enable enable; + spinlock_t lock; + struct snd_pcm_substream *substream; + unsigned int dummy_frames; +}; + +struct pistachio_output { + struct pistachio_start_at start_at; + unsigned int active_rate; +}; + +struct pistachio_parallel_out { + struct pistachio_output output; + struct snd_soc_dai_link_component components[2]; +}; + +struct pistachio_mclk { + struct clk *mclk; + unsigned int cur_rate; + unsigned int min_rate; + unsigned int max_rate; +}; + +struct pistachio_i2s_mclk { + struct pistachio_mclk *mclk; + unsigned int *fs_rates; + unsigned int num_fs_rates; +}; + +struct pistachio_codec_i2s { + struct pistachio_mclk *mclk; + struct snd_soc_dai *dai; + unsigned int mclk_index; +}; + +struct pistachio_i2s { + struct pistachio_i2s_mclk mclk_a; + struct pistachio_i2s_mclk mclk_b; + struct pistachio_codec_i2s *codecs; + struct snd_soc_dai_link_component *components; + unsigned int num_codecs; +}; + +struct pistachio_i2s_out { + struct pistachio_i2s i2s; + struct pistachio_output output; + struct device *cpu_dev; +}; + +struct pistachio_i2s_in { + struct pistachio_i2s i2s; + unsigned int active_rate; + unsigned int fmt; + int frame_master; + int bitclock_master; + struct device *cpu_dev; +}; + +struct pistachio_i2s_codec_info_s { + const char *prefix; + const char *dai_name; + struct device_node *np; + struct pistachio_mclk *mclk; + unsigned int mclk_index; +}; + +struct pistachio_i2s_codec_info { + unsigned int total_codecs; + unsigned int unique_codecs; + int bitclock_master_idx; + int frame_master_idx; + struct pistachio_i2s_codec_info_s codecs[PISTACHIO_MAX_I2S_CODECS]; +}; + +struct pistachio_i2s_mclk_fs_info { + unsigned int fs_rates[PISTACHIO_MAX_FS_RATES]; + unsigned int num_fs_rates; +}; + +struct pistachio_card { + struct pistachio_output *spdif_out; + struct pistachio_parallel_out *parallel_out; + struct pistachio_i2s_out *i2s_out; + struct pistachio_i2s_in *i2s_in; + struct pistachio_i2s_in *i2s_in_alt; + bool spdif_in; + struct device_node *event_timer_np; + struct pistachio_evt *event_timer; + struct snd_soc_card card; + struct snd_soc_jack hp_jack; + struct snd_soc_jack_pin hp_jack_pin; + struct snd_soc_jack_gpio hp_jack_gpio; + unsigned int mute_gpio; + bool mute_gpio_inverted; + struct mutex rate_mutex; + struct clk *audio_pll; + unsigned int audio_pll_rate; + struct pistachio_mclk i2s_mclk; + struct pistachio_mclk dac_mclk; + struct regmap *periph_regs; + struct notifier_block i2s_clk_notifier; + struct snd_ctl_elem_id *sample_rate_ids[PISTACHIO_EVT_MAX_SOURCES]; + struct snd_ctl_elem_id *phase_difference_id; + struct snd_soc_dai_link_component *tpa6130a2; + struct snd_soc_dai_link *i2s_in_start; + struct snd_soc_dai_link *i2s_in_alt_start; +}; + +static void pistachio_card_set_mclk_codecs(struct pistachio_i2s *i2s, + struct pistachio_mclk *mclk, unsigned int rate) +{ + int i; + struct pistachio_codec_i2s *codec; + + for (i = 0; i < i2s->num_codecs; i++) { + codec = &i2s->codecs[i]; + if (codec->mclk == mclk) { + snd_soc_dai_set_sysclk(codec->dai, codec->mclk_index, + rate, SND_SOC_CLOCK_IN); + } + } +} + +static int pistachio_card_set_mclk(struct pistachio_card *pbc, + struct pistachio_mclk *mclk, unsigned int rate) +{ + int ret; + unsigned int old_rate = mclk->cur_rate; + + if (mclk->cur_rate != rate) { + /* + * Set cur_rate before the clk_set_rate call to stop the i2s + * mclk rate change callback rejecting the change + */ + mclk->cur_rate = rate; + ret = clk_set_rate(mclk->mclk, rate); + if (ret) { + mclk->cur_rate = old_rate; + return ret; + } + } + + if (pbc->i2s_out) + pistachio_card_set_mclk_codecs(&pbc->i2s_out->i2s, mclk, rate); + + if (pbc->i2s_in) + pistachio_card_set_mclk_codecs(&pbc->i2s_in->i2s, mclk, rate); + + if (pbc->i2s_in_alt) + pistachio_card_set_mclk_codecs(&pbc->i2s_in_alt->i2s, mclk, + rate); + + return 0; +} + +static int pistachio_card_set_pll_rate(struct pistachio_card *pbc, + unsigned int rate) +{ + int ret; + unsigned int old_i2s_rate; + + /* + * If any active streams are currently using a clock derived + * from the audio pll, a pll rate change cannot take place + */ + if ((pbc->spdif_out && pbc->spdif_out->active_rate) || + (pbc->parallel_out && pbc->parallel_out->output.active_rate) || + (pbc->i2s_out && pbc->i2s_out->output.active_rate) || + (pbc->i2s_in && pbc->i2s_in->active_rate && + pbc->i2s_in->i2s.mclk_a.mclk) || + (pbc->i2s_in_alt && pbc->i2s_in_alt->active_rate && + pbc->i2s_in_alt->i2s.mclk_a.mclk)) + return -EBUSY; + + /* + * Set cur_rate before the clk_set_rate call to stop the i2s + * mclk rate change callback rejecting the change + */ + old_i2s_rate = pbc->i2s_mclk.cur_rate; + pbc->i2s_mclk.cur_rate = rate / (pbc->audio_pll_rate / old_i2s_rate); + + ret = clk_set_rate(pbc->audio_pll, rate); + + if (ret) { + pbc->i2s_mclk.cur_rate = old_i2s_rate; + } else { + pbc->audio_pll_rate = rate; + pbc->dac_mclk.cur_rate = rate / (pbc->audio_pll_rate / + pbc->dac_mclk.cur_rate); + pistachio_card_set_mclk(pbc, &pbc->i2s_mclk, + pbc->i2s_mclk.cur_rate); + pistachio_card_set_mclk(pbc, &pbc->dac_mclk, + pbc->dac_mclk.cur_rate); + } + + return ret; +} + +static void pistachio_card_rate_err(struct pistachio_card *pbc, + struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b, + struct pistachio_i2s_mclk *mclk_c, unsigned int rate_a, + unsigned int rate_b, unsigned int rate_c) +{ + char *mclk_name, *dir_a, *dir_b, *dir_c; + + if (mclk_a->mclk == &pbc->i2s_mclk) + mclk_name = "i2s"; + else + mclk_name = "dac"; + + if (pbc->i2s_out && ((mclk_a == &pbc->i2s_out->i2s.mclk_a) || + (mclk_a == &pbc->i2s_out->i2s.mclk_b))) { + dir_a = "I2S out"; + } else if (pbc->i2s_in && ((mclk_a == &pbc->i2s_in->i2s.mclk_a) || + (mclk_a == &pbc->i2s_in->i2s.mclk_b))) { + dir_a = "I2S in"; + } else { + dir_a = "I2S in (alt)"; + } + + if (pbc->i2s_out && ((mclk_b == &pbc->i2s_out->i2s.mclk_a) || + (mclk_b == &pbc->i2s_out->i2s.mclk_b))) { + dir_b = "I2S out"; + } else if (pbc->i2s_in && ((mclk_b == &pbc->i2s_in->i2s.mclk_a) || + (mclk_b == &pbc->i2s_in->i2s.mclk_b))) { + dir_b = "I2S in"; + } else { + dir_b = "I2S in (alt)"; + } + + if (pbc->i2s_out && ((mclk_c == &pbc->i2s_out->i2s.mclk_a) || + (mclk_c == &pbc->i2s_out->i2s.mclk_b))) { + dir_c = "I2S out"; + } else if (pbc->i2s_in && ((mclk_c == &pbc->i2s_in->i2s.mclk_a) || + (mclk_c == &pbc->i2s_in->i2s.mclk_b))) { + dir_c = "I2S in"; + } else { + dir_c = "I2S in (alt)"; + } + + if (!mclk_b) { + dev_err(pbc->card.dev, + "No valid rate for mclk %s (%s sample rate %u)\n", + mclk_name, dir_a, rate_a); + } else if (!mclk_c) { + dev_err(pbc->card.dev, + "No valid rate for mclk %s (%s sample rate %u, %s sample rate %u)\n", + mclk_name, dir_a, rate_a, dir_b, rate_b); + } else { + dev_err(pbc->card.dev, + "No valid rate for mclk %s (%s sample rate %u, %s sample rate %u, %s sample rate %u)\n", + mclk_name, dir_a, rate_a, dir_b, rate_b, dir_c, + rate_c); + } +} + +static bool pistachio_card_mclk_ok(struct pistachio_i2s_mclk *mclk, + unsigned int rate) +{ + int i; + + if (!mclk) + return true; + + for (i = 0; i < mclk->num_fs_rates; i++) + if ((mclk->mclk->cur_rate / mclk->fs_rates[i]) == rate) + return true; + + return false; +} + +static int pistachio_card_get_optimal_mclk_rate(struct pistachio_card *pbc, + struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b, + struct pistachio_i2s_mclk *mclk_c, unsigned int rate_a, + unsigned int rate_b, unsigned int rate_c, unsigned int *p_mclk_rate) +{ + int i, j, k; + unsigned int div, total_div, mclk_rate; + + /* + * If the current system clock rate has zero difference, do not + * change the rate. This ensures a rate set using the "I2S Rates" + * control will not be erroneously overridden by a hw_params call + */ + if (pistachio_card_mclk_ok(mclk_a, rate_a) && + pistachio_card_mclk_ok(mclk_b, rate_b) && + pistachio_card_mclk_ok(mclk_c, rate_c)) { + *p_mclk_rate = mclk_a->mclk->cur_rate; + return 0; + } + + total_div = pbc->audio_pll_rate / rate_a; + + for (i = 0; i < mclk_a->num_fs_rates; i++) { + div = total_div / mclk_a->fs_rates[i]; + if (div > PISTACHIO_MAX_DIV) + continue; + mclk_rate = pbc->audio_pll_rate / div; + if ((mclk_rate < mclk_a->mclk->min_rate) || + (mclk_rate > mclk_a->mclk->max_rate)) + continue; + if ((rate_a * mclk_a->fs_rates[i] * div) != pbc->audio_pll_rate) + continue; + + if (!mclk_b) + break; + + for (j = 0; j < mclk_b->num_fs_rates; j++) { + if ((rate_b * mclk_b->fs_rates[j] * div) != + pbc->audio_pll_rate) + continue; + + if (!mclk_c) + break; + + for (k = 0; k < mclk_c->num_fs_rates; k++) { + if ((rate_c * mclk_c->fs_rates[k] * div) == + pbc->audio_pll_rate) + break; + } + if (k != mclk_c->num_fs_rates) + break; + } + if (j != mclk_b->num_fs_rates) + break; + } + + if (i == mclk_a->num_fs_rates) { + pistachio_card_rate_err(pbc, mclk_a, mclk_b, mclk_c, + rate_a, rate_b, rate_c); + return -EINVAL; + } + + *p_mclk_rate = mclk_rate; + + return 0; +} + +static bool pistachio_card_mclk_active(struct pistachio_card *pbc, + struct pistachio_mclk *mclk) +{ + if (pbc->i2s_out && pbc->i2s_out->output.active_rate) { + if (pbc->i2s_out->i2s.mclk_a.mclk == mclk) + return true; + if (pbc->i2s_out->i2s.mclk_b.mclk == mclk) + return true; + } + + if (pbc->i2s_in && pbc->i2s_in->active_rate) { + if (pbc->i2s_in->i2s.mclk_a.mclk == mclk) + return true; + if (pbc->i2s_in->i2s.mclk_b.mclk == mclk) + return true; + } + + if (pbc->i2s_in_alt && pbc->i2s_in_alt->active_rate) { + if (pbc->i2s_in_alt->i2s.mclk_a.mclk == mclk) + return true; + if (pbc->i2s_in_alt->i2s.mclk_b.mclk == mclk) + return true; + } + + return false; +} + +static int pistachio_card_update_mclk(struct pistachio_card *pbc, + struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b, + struct pistachio_i2s_mclk *mclk_c, unsigned int rate_a, + unsigned int rate_b, unsigned int rate_c) +{ + unsigned int mclk_rate; + int ret; + + ret = pistachio_card_get_optimal_mclk_rate(pbc, mclk_a, mclk_b, mclk_c, + rate_a, rate_b, rate_c, &mclk_rate); + if (ret) + return ret; + + if (mclk_a->mclk->cur_rate != mclk_rate) { + if (pistachio_card_mclk_active(pbc, mclk_a->mclk)) + return -EBUSY; + return pistachio_card_set_mclk(pbc, mclk_a->mclk, mclk_rate); + } + + return 0; +} + +static int pistachio_card_update_mclk_single(struct pistachio_card *pbc, + struct pistachio_i2s_mclk *mclk, unsigned int rate) +{ + return pistachio_card_update_mclk(pbc, mclk, NULL, NULL, rate, 0, 0); +} + +static inline int pistachio_card_get_pll_rate(unsigned int rate) +{ + switch (rate) { + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + case 192000: + return PISTACHIO_PLL_RATE_A; + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + return PISTACHIO_PLL_RATE_B; + default: + return -EINVAL; + } +} + +static int _pistachio_card_change_rate(struct pistachio_card *pbc, + unsigned int rate, struct pistachio_i2s *i2s) +{ + int ret = 0; + unsigned int pll_rate; + + ret = pistachio_card_get_pll_rate(rate); + if (ret < 0) + return ret; + + pll_rate = ret; + + if (pbc->audio_pll_rate != pll_rate) { + ret = pistachio_card_set_pll_rate(pbc, pll_rate); + if (ret) + return ret; + } + + /* + * Nothing more to do if an mclk is not used. The individual + * cpu-dai drivers will make the required clock changes + */ + if (!i2s) + return 0; + + ret = pistachio_card_update_mclk_single(pbc, &i2s->mclk_a, rate); + if (ret) + return ret; + + if (!i2s->mclk_b.mclk) + return 0; + + return pistachio_card_update_mclk_single(pbc, &i2s->mclk_b, rate); +} + +static int pistachio_card_change_rate(struct pistachio_card *pbc, + unsigned int rate, struct pistachio_i2s *i2s, + unsigned int *active_rate) +{ + int ret; + + mutex_lock(&pbc->rate_mutex); + *active_rate = 0; + ret = _pistachio_card_change_rate(pbc, rate, i2s); + if (!ret) + *active_rate = rate; + mutex_unlock(&pbc->rate_mutex); + + return ret; +} + +static void pistachio_card_start_at_cb(struct pistachio_evt *evt, + void *context) +{ + struct pistachio_start_at *sa = context; + unsigned long flags; + + spin_lock_irqsave(&sa->lock, flags); + + if (!sa->substream) { + spin_unlock_irqrestore(&sa->lock, flags); + return; + } + + snd_pcm_start_at_trigger(sa->substream); + + _pistachio_evt_disable_event(evt, sa->enable); + + sa->substream = NULL; + + spin_unlock_irqrestore(&sa->lock, flags); +} + +static int pistachio_card_start_at(struct pistachio_output *output, + struct pistachio_evt *evt, struct snd_pcm_substream *st, + const struct timespec *ts) +{ + int ret; + unsigned long flags; + struct timespec ts_sub, ts_new; + struct pistachio_start_at *sa = &output->start_at; + u64 temp; + + /* Adjust start time to account for dummy frames output at start */ + temp = (u64)NSEC_PER_SEC * sa->dummy_frames; + ts_sub.tv_sec = 0; + ts_sub.tv_nsec = DIV_ROUND_CLOSEST_ULL(temp, output->active_rate); + ts_new = timespec_sub(*ts, ts_sub); + + spin_lock_irqsave(&sa->lock, flags); + + ret = pistachio_evt_set_event(evt, sa->enable, + PISTACHIO_EVT_TYPE_LEVEL, &ts_new, + pistachio_card_start_at_cb, sa); + if (!ret) + sa->substream = st; + + spin_unlock_irqrestore(&sa->lock, flags); + + return ret; +} + +static int pistachio_card_start_at_abort(struct pistachio_start_at *sa, + struct pistachio_evt *evt, struct snd_pcm_substream *st) +{ + unsigned long flags; + + if (spin_trylock_irqsave(&sa->lock, flags)) { + if (!sa->substream) { + /* Already started */ + spin_unlock_irqrestore(&sa->lock, flags); + return -EINVAL; + } + + snd_pcm_start_at_cleanup(st); + + sa->substream = NULL; + + spin_unlock_irqrestore(&sa->lock, flags); + + pistachio_evt_disable_event(evt, sa->enable); + } else { + /* In the process of being started */ + spin_unlock_irqrestore(&sa->lock, flags); + return -EINVAL; + } + + return 0; +} + +static int pistachio_card_i2s_link_init(struct pistachio_i2s *i2s, + struct snd_soc_pcm_runtime *rtd) +{ + int ret, i, id; + unsigned long rate; + struct pistachio_codec_i2s *codec; + + for (i = 0; i < i2s->num_codecs; i++) { + codec = &i2s->codecs[i]; + codec->dai = rtd->codec_dais[i]; + if (codec->mclk) { + rate = codec->mclk->cur_rate; + id = codec->mclk_index; + ret = snd_soc_dai_set_sysclk(codec->dai, id, rate, 0); + if (ret) + return ret; + } + } + + return 0; +} + +static int pistachio_card_prl_out_link_init(struct snd_soc_pcm_runtime *rtd) +{ + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + int ret = 0; + + if (pbc->tpa6130a2) + ret = tpa6130a2_stereo_enable(rtd->codec_dais[1]->codec, 1); + + return ret; +} + +static void pistachio_card_parallel_out_shutdown(struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->parallel_out->output.active_rate = 0; +} + +static int pistachio_card_parallel_out_hw_params(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_change_rate(pbc, params_rate(params), NULL, + &pbc->parallel_out->output.active_rate); +} + +static int pistachio_card_parallel_out_start_at(struct snd_pcm_substream *st, + int clock_type, const struct timespec *ts) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at(&pbc->parallel_out->output, + pbc->event_timer, st, ts); +} + +static int pistachio_card_parallel_out_start_at_abort( + struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at_abort( + &pbc->parallel_out->output.start_at, + pbc->event_timer, st); +} + +static struct snd_soc_ops pistachio_card_parallel_out_ops = { + .shutdown = pistachio_card_parallel_out_shutdown, + .hw_params = pistachio_card_parallel_out_hw_params, + .start_at = pistachio_card_parallel_out_start_at, + .start_at_abort = pistachio_card_parallel_out_start_at_abort +}; + +static void pistachio_card_spdif_out_shutdown(struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->spdif_out->active_rate = 0; +} + +static int pistachio_card_spdif_out_hw_params(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_change_rate(pbc, params_rate(params), NULL, + &pbc->spdif_out->active_rate); +} + +static int pistachio_card_spdif_out_start_at(struct snd_pcm_substream *st, + int clock_type, const struct timespec *ts) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at(pbc->spdif_out, pbc->event_timer, + st, ts); +} + +static int pistachio_card_spdif_out_start_at_abort( + struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at_abort(&pbc->spdif_out->start_at, + pbc->event_timer, st); +} + +static struct snd_soc_ops pistachio_card_spdif_out_ops = { + .shutdown = pistachio_card_spdif_out_shutdown, + .hw_params = pistachio_card_spdif_out_hw_params, + .start_at = pistachio_card_spdif_out_start_at, + .start_at_abort = pistachio_card_spdif_out_start_at_abort +}; + +static int pistachio_card_i2s_clk_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct pistachio_card *pbc; + unsigned int diff; + u64 cur_rate; + u64 tolerance; + + pbc = container_of(nb, struct pistachio_card, i2s_clk_notifier); + + cur_rate = pbc->i2s_mclk.cur_rate; + + switch (event) { + case PRE_RATE_CHANGE: + diff = abs(ndata->new_rate - cur_rate); + tolerance = DIV_ROUND_CLOSEST_ULL(cur_rate * 5, 100); + if (diff < tolerance) { + /* + * Fractional adjustment made by atu, or new rate set + * by card driver if diff is zero + */ + return NOTIFY_OK; + } else { + /* Significant change made by i2s cpu dai driver */ + return NOTIFY_STOP; + } + case POST_RATE_CHANGE: + case ABORT_RATE_CHANGE: + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +static int pistachio_card_i2s_out_link_init(struct snd_soc_pcm_runtime *rtd) +{ + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->i2s_out->cpu_dev = rtd->cpu_dai->dev; + + return pistachio_card_i2s_link_init(&pbc->i2s_out->i2s, rtd); +} + +static void pistachio_card_i2s_out_shutdown(struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->i2s_out->output.active_rate = 0; +} + +static int pistachio_card_i2s_out_hw_params(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_change_rate(pbc, params_rate(params), + &pbc->i2s_out->i2s, &pbc->i2s_out->output.active_rate); +} + +static int pistachio_card_i2s_out_start_at(struct snd_pcm_substream *st, + int clock_type, const struct timespec *ts) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at(&pbc->i2s_out->output, + pbc->event_timer, st, ts); +} + +static int pistachio_card_i2s_out_start_at_abort( + struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_start_at_abort(&pbc->i2s_out->output.start_at, + pbc->event_timer, st); +} + +static struct snd_soc_ops pistachio_card_i2s_out_ops = { + .shutdown = pistachio_card_i2s_out_shutdown, + .hw_params = pistachio_card_i2s_out_hw_params, + .start_at = pistachio_card_i2s_out_start_at, + .start_at_abort = pistachio_card_i2s_out_start_at_abort +}; + +static int pistachio_card_i2s_in_link_init_cmn(struct pistachio_i2s_in *i2s, + struct snd_soc_pcm_runtime *rtd) +{ + int ret, i; + unsigned int fmt; + + ret = pistachio_card_i2s_link_init(&i2s->i2s, rtd); + if (ret) + return ret; + + fmt = i2s->fmt | SND_SOC_DAIFMT_CBM_CFM; + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret) + return ret; + + for (i = 0; i < i2s->i2s.num_codecs; i++) { + fmt = i2s->fmt; + + if (i == i2s->frame_master) + if (i == i2s->bitclock_master) + fmt |= SND_SOC_DAIFMT_CBM_CFM; + else + fmt |= SND_SOC_DAIFMT_CBS_CFM; + else + if (i == i2s->bitclock_master) + fmt |= SND_SOC_DAIFMT_CBM_CFS; + else + fmt |= SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(rtd->codec_dais[i], fmt); + if (ret) + return ret; + } + + return 0; +} + +static int pistachio_card_i2s_in_link_init(struct snd_soc_pcm_runtime *rtd) +{ + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + u32 val; + int ret; + + pbc->i2s_in->cpu_dev = rtd->cpu_dai->dev; + + if (pbc->i2s_in->frame_master == PISTACHIO_CLOCK_MASTER_LOOPBACK) + val = PISTACHIO_I2S_LOOPBACK_CLK_LOCAL; + else + val = PISTACHIO_I2S_LOOPBACK_CLK_NONE; + + ret = regmap_update_bits(pbc->periph_regs, PISTACHIO_I2S_LOOPBACK_REG, + PISTACHIO_I2S_LOOPBACK_CLK_MASK, val); + if (ret) + return ret; + + return pistachio_card_i2s_in_link_init_cmn(pbc->i2s_in, rtd); +} + +static void pistachio_card_i2s_in_shutdown(struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->i2s_in->active_rate = 0; +} + +static int pistachio_card_i2s_in_hw_params(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_change_rate(pbc, params_rate(params), + &pbc->i2s_in->i2s, &pbc->i2s_in->active_rate); +} + +static struct snd_soc_ops pistachio_card_i2s_in_ops = { + .shutdown = pistachio_card_i2s_in_shutdown, + .hw_params = pistachio_card_i2s_in_hw_params +}; + +static int pistachio_card_i2s_in_alt_link_init(struct snd_soc_pcm_runtime *rtd) +{ + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->i2s_in_alt->cpu_dev = rtd->cpu_dai->dev; + + return pistachio_card_i2s_in_link_init_cmn(pbc->i2s_in_alt, rtd); +} + +static void pistachio_card_i2s_in_alt_shutdown(struct snd_pcm_substream *st) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + pbc->i2s_in_alt->active_rate = 0; +} + +static int pistachio_card_i2s_in_alt_hw_params(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card); + + return pistachio_card_change_rate(pbc, params_rate(params), + &pbc->i2s_in_alt->i2s, &pbc->i2s_in_alt->active_rate); +} + +static struct snd_soc_ops pistachio_card_i2s_in_alt_ops = { + .shutdown = pistachio_card_i2s_in_alt_shutdown, + .hw_params = pistachio_card_i2s_in_alt_hw_params +}; + +static int pistachio_card_parse_of_spdif_out(struct device_node *node, + struct pistachio_card *pbc, struct snd_soc_dai_link *link) +{ + struct device_node *np; + + pbc->spdif_out = devm_kzalloc(pbc->card.dev, sizeof(*pbc->spdif_out), + GFP_KERNEL); + if (!pbc->spdif_out) + return -ENOMEM; + + pbc->spdif_out->start_at.enable = PISTACHIO_EVT_ENABLE_SPDIF_OUT; + pbc->spdif_out->start_at.dummy_frames = 1; + spin_lock_init(&pbc->spdif_out->start_at.lock); + + link->name = link->stream_name = "pistachio-spdif-out"; + + np = of_parse_phandle(node, "cpu-dai", 0); + if (!np) + return -EINVAL; + + link->cpu_of_node = np; + link->platform_of_node = np; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + link->ops = &pistachio_card_spdif_out_ops; + + return 0; +} + +static int pistachio_card_parse_of_spdif_in(struct device_node *node, + struct pistachio_card *pbc, struct snd_soc_dai_link *link) +{ + struct device_node *np; + + pbc->spdif_in = true; + + link->name = link->stream_name = "pistachio-spdif-in"; + + np = of_parse_phandle(node, "cpu-dai", 0); + if (!np) + return -EINVAL; + + link->cpu_of_node = np; + link->platform_of_node = np; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + + return 0; +} + +static int pistachio_card_parse_of_parallel_out(struct device_node *node, + struct pistachio_card *pbc, struct snd_soc_dai_link *link) +{ + struct device_node *np; + int ret; + + pbc->parallel_out = devm_kzalloc(pbc->card.dev, + sizeof(*pbc->parallel_out), GFP_KERNEL); + if (!pbc->parallel_out) + return -ENOMEM; + + pbc->parallel_out->output.start_at.enable = + PISTACHIO_EVT_ENABLE_PARALLEL_OUT; + pbc->parallel_out->output.start_at.dummy_frames = 2; + spin_lock_init(&pbc->parallel_out->output.start_at.lock); + + link->name = link->stream_name = "pistachio-parallel-out"; + + np = of_parse_phandle(node, "cpu-dai", 0); + if (!np) + return -EINVAL; + + link->cpu_of_node = np; + link->platform_of_node = np; + link->codecs = pbc->parallel_out->components; + + np = of_parse_phandle(node, "sound-dai", 0); + if (!np) + return -EINVAL; + link->codecs[0].of_node = np; + link->num_codecs = 1; + ret = snd_soc_of_get_dai_name(node, &link->codecs[0].dai_name); + if (ret) + return ret; + + np = of_parse_phandle(node, "tpa6130a2", 0); + if (np) { + link->codecs[1].of_node = np; + link->codecs[1].dai_name = "tpa6130a2"; + link->num_codecs++; + pbc->tpa6130a2 = &link->codecs[1]; + } + + link->init = pistachio_card_prl_out_link_init; + link->ops = &pistachio_card_parallel_out_ops; + + return 0; +} + +static int pistachio_card_parse_of_i2s_mclk(struct device_node *np, + struct pistachio_mclk *mclk, struct pistachio_i2s_mclk_fs_info *fs) +{ + int ret, i, j, k, num_fs_rates; + u32 min_freq, max_freq, fs_rates[PISTACHIO_MAX_FS_RATES]; + + ret = of_property_read_u32(np, "mclk-min-freq", &min_freq); + if (ret) + return ret; + ret = of_property_read_u32(np, "mclk-max-freq", &max_freq); + if (ret) + return ret; + + if (max_freq < PISTACHIO_MIN_MCLK_FREQ) + return -EINVAL; + if (min_freq > mclk->min_rate) + mclk->min_rate = min_freq; + if (max_freq < mclk->max_rate) + mclk->max_rate = max_freq; + if (mclk->min_rate > mclk->max_rate) + return -EINVAL; + + num_fs_rates = of_property_count_u32_elems(np, "mclk-fs"); + if (num_fs_rates < 0) + return num_fs_rates; + if (!num_fs_rates || (num_fs_rates > PISTACHIO_MAX_FS_RATES)) + return -EINVAL; + + ret = of_property_read_u32_array(np, "mclk-fs", fs_rates, + num_fs_rates); + if (ret) + return ret; + + /* + * If this is the first fs-rates list for this combination + * of {i2s direction, mclk}, this list defines the + * current fs-rate list for this combination. Else, this list + * subtracts any fs-rates that are not present in both lists from the + * current list for this combination + */ + if (!fs->num_fs_rates) { + for (i = 0; i < num_fs_rates; i++) + fs->fs_rates[i] = fs_rates[i]; + fs->num_fs_rates = num_fs_rates; + } else { + for (j = 0; j < fs->num_fs_rates; j++) { + for (i = 0; i < num_fs_rates; i++) + if (fs->fs_rates[j] == fs_rates[i]) + break; + if (i == num_fs_rates) { + for (k = j; k < (fs->num_fs_rates - 1); k++) + fs->fs_rates[k] = fs->fs_rates[k + 1]; + fs->num_fs_rates--; + if (!fs->num_fs_rates) + return -EINVAL; + j--; + } + } + } + + return 0; +} + +static int pistachio_card_parse_of_i2s_codecs(struct device_node *np, + struct pistachio_card *pbc, + struct pistachio_i2s_codec_info *codec_info, + struct pistachio_i2s_mclk_fs_info *i2s_fs_info, + struct pistachio_i2s_mclk_fs_info *dac_fs_info) +{ + int i, j, ret; + struct device_node *subnode, *codec; + struct pistachio_i2s_codec_info_s *info; + u32 mclk_id; + struct pistachio_mclk *mclk; + struct pistachio_i2s_mclk_fs_info *fs_info; + + j = 0; + for_each_child_of_node(np, subnode) { + ret = of_property_read_u32(subnode, "mclk", &mclk_id); + if (ret) + return ret; + + switch (mclk_id) { + case PISTACHIO_MCLK_I2S: + mclk = &pbc->i2s_mclk; + fs_info = i2s_fs_info; + break; + case PISTACHIO_MCLK_DAC: + mclk = &pbc->dac_mclk; + fs_info = dac_fs_info; + break; + case PISTACHIO_MCLK_NONE: + mclk = NULL; + break; + default: + ret = -EINVAL; + goto err_subnode; + } + if (mclk) { + ret = pistachio_card_parse_of_i2s_mclk(subnode, mclk, + fs_info); + if (ret) + goto err_subnode; + } + + codec = of_parse_phandle(subnode, "sound-dai", 0); + if (!codec) + continue; + if (codec_info->total_codecs == PISTACHIO_MAX_I2S_CODECS) { + ret = -EINVAL; + of_node_put(codec); + goto err_subnode; + } + for (i = 0; i < codec_info->total_codecs; i++) + if (codec_info->codecs[i].np == codec) + break; + if (i == codec_info->total_codecs) + codec_info->unique_codecs++; + info = &codec_info->codecs[codec_info->total_codecs++]; + info->np = codec; + info->prefix = subnode->name; + ret = snd_soc_of_get_dai_name(subnode, &info->dai_name); + if (ret) + goto err_subnode; + info->mclk = mclk; + ret = of_property_read_u32(subnode, "mclk-index", + &info->mclk_index); + if (ret) + info->mclk_index = 0; + if (of_property_read_bool(subnode, "frame-master")) { + if (codec_info->frame_master_idx != -1) { + ret = -EINVAL; + goto err_subnode; + } + codec_info->frame_master_idx = j; + } + if (of_property_read_bool(subnode, "bitclock-master")) { + if (codec_info->bitclock_master_idx != -1) { + ret = -EINVAL; + goto err_subnode; + } + codec_info->bitclock_master_idx = j; + } + j++; + } + + return 0; + +err_subnode: + of_node_put(subnode); + return ret; +} + +static int pistachio_card_parse_of_i2s_common(struct device_node *node, + struct pistachio_card *pbc, struct pistachio_i2s *i2s, + struct snd_soc_dai_link *link, + struct pistachio_i2s_codec_info *codec_info, + struct pistachio_i2s_mclk_fs_info *i2s_mclk_info, + struct pistachio_i2s_mclk_fs_info *dac_mclk_info) +{ + int ret, i; + unsigned int initial_codecs = codec_info->total_codecs, size; + struct pistachio_i2s_codec_info_s *codecs; + struct pistachio_i2s_mclk *mclk; + + codecs = &codec_info->codecs[initial_codecs]; + + ret = pistachio_card_parse_of_i2s_codecs(node, pbc, codec_info, + i2s_mclk_info, dac_mclk_info); + i2s->num_codecs = codec_info->total_codecs - initial_codecs; + if (ret) + goto err_codec_info; + + mclk = &i2s->mclk_a; + + if (i2s_mclk_info->num_fs_rates) { + mclk->mclk = &pbc->i2s_mclk; + mclk->num_fs_rates = i2s_mclk_info->num_fs_rates; + size = sizeof(*mclk->fs_rates) * mclk->num_fs_rates; + mclk->fs_rates = devm_kzalloc(pbc->card.dev, size, + GFP_KERNEL); + if (!mclk->fs_rates) { + ret = -ENOMEM; + goto err_codec_info; + } + memcpy(mclk->fs_rates, i2s_mclk_info->fs_rates, size); + mclk = &i2s->mclk_b; + } + + if (dac_mclk_info->num_fs_rates) { + mclk->mclk = &pbc->dac_mclk; + mclk->num_fs_rates = dac_mclk_info->num_fs_rates; + size = sizeof(*mclk->fs_rates) * mclk->num_fs_rates; + mclk->fs_rates = devm_kzalloc(pbc->card.dev, size, + GFP_KERNEL); + if (!mclk->fs_rates) { + ret = -ENOMEM; + goto err_codec_info; + } + memcpy(mclk->fs_rates, dac_mclk_info->fs_rates, size); + } + + if (!i2s->num_codecs) { + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + return 0; + } + + i2s->codecs = devm_kzalloc(pbc->card.dev, + sizeof(*i2s->codecs) * i2s->num_codecs, GFP_KERNEL); + if (!i2s->codecs) { + ret = -ENOMEM; + goto err_codec_info; + } + + for (i = 0; i < i2s->num_codecs; i++) { + i2s->codecs[i].mclk = codecs[i].mclk; + i2s->codecs[i].mclk_index = codecs[i].mclk_index; + } + + i2s->components = devm_kzalloc(pbc->card.dev, + sizeof(*i2s->components) * i2s->num_codecs, GFP_KERNEL); + if (!i2s->components) { + ret = -ENOMEM; + goto err_codec_info; + } + + for (i = 0; i < i2s->num_codecs; i++) { + i2s->components[i].dai_name = codecs[i].dai_name; + i2s->components[i].of_node = codecs[i].np; + } + + link->codecs = i2s->components; + link->num_codecs = i2s->num_codecs; + + return 0; + +err_codec_info: + for (i = 0; i < i2s->num_codecs; i++) + of_node_put(codecs[i].np); + + return ret; +} + +static int pistachio_card_parse_of_i2s(struct device_node *i2s_out_np, + struct device_node *i2s_in_np, struct device_node *i2s_in_alt_np, + unsigned int num_i2s_in_links, unsigned int num_i2s_in_alt_links, + struct pistachio_card *pbc, struct snd_soc_dai_link *links, + struct pistachio_i2s_codec_info *codec_info, + bool i2s_loopback) +{ + int ret, i, j, k; + struct device *dev = pbc->card.dev; + unsigned int fmt; + struct device_node *np; + struct pistachio_i2s_mclk_fs_info i2s_mclk_info; + struct pistachio_i2s_mclk_fs_info dac_mclk_info; + char name[23], *str; + struct of_phandle_args args; + u32 clock_masters = 0, shared_dma, max_chan; + bool i2s_in_clock_info_acquired = false, single_cpu_dai; + + pbc->i2s_mclk.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ; + pbc->dac_mclk.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ; + + if (i2s_out_np) { + pbc->i2s_out = devm_kzalloc(dev, sizeof(*pbc->i2s_out), + GFP_KERNEL); + if (!pbc->i2s_out) + return -ENOMEM; + + pbc->i2s_out->output.start_at.enable = + PISTACHIO_EVT_ENABLE_I2S_OUT; + pbc->i2s_out->output.start_at.dummy_frames = 1; + spin_lock_init(&pbc->i2s_out->output.start_at.lock); + + links->name = links->stream_name = "pistachio-i2s-out"; + + np = of_parse_phandle(i2s_out_np, "cpu-dai", 0); + if (!np) + return -EINVAL; + + links->cpu_of_node = np; + links->platform_of_node = np; + + fmt = snd_soc_of_parse_daifmt(i2s_out_np, NULL, NULL, NULL); + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + fmt |= SND_SOC_DAIFMT_CBS_CFS; + links->dai_fmt = fmt; + + /* + * Internal i2s out controller uses i2s_mclk and + * accepts 256fs,384fs + */ + i2s_mclk_info.fs_rates[0] = 256; + i2s_mclk_info.fs_rates[1] = 384; + i2s_mclk_info.num_fs_rates = 2; + dac_mclk_info.num_fs_rates = 0; + + ret = pistachio_card_parse_of_i2s_common(i2s_out_np, pbc, + &pbc->i2s_out->i2s, links, codec_info, + &i2s_mclk_info, &dac_mclk_info); + if (ret) + return ret; + + links->init = pistachio_card_i2s_out_link_init; + links->ops = &pistachio_card_i2s_out_ops; + + links++; + } + + pbc->i2s_in_start = links; + + codec_info->bitclock_master_idx = -1; + codec_info->frame_master_idx = -1; + + if (i2s_in_np) { + pbc->i2s_in = devm_kzalloc(dev, sizeof(*pbc->i2s_in), + GFP_KERNEL); + if (!pbc->i2s_in) + return -ENOMEM; + + for (i = 0; i < num_i2s_in_links; i++) { + + sprintf(name, "pistachio-i2s-in-%d", i); + str = devm_kzalloc(pbc->card.dev, strlen(name) + 1, + GFP_KERNEL); + if (!str) + return -ENOMEM; + strcpy(str, name); + links[i].name = links[i].stream_name = str; + + if (of_property_read_bool(i2s_in_np, "cpu-dai")) { + np = of_parse_phandle(i2s_in_np, "cpu-dai", 0); + if (!np) + return -EINVAL; + single_cpu_dai = true; + } else { + ret = of_parse_phandle_with_args(i2s_in_np, + "cpu-dais", "#sound-dai-cells", + i, &args); + if (ret) + return ret; + np = args.np; + if (!np) + return -EINVAL; + single_cpu_dai = false; + } + + links[i].cpu_of_node = np; + links[i].platform_of_node = np; + + if (!i2s_in_clock_info_acquired) { + of_property_read_u32(np, + "img,clock-master", + &clock_masters); + clock_masters <<= 2; + ret = of_property_read_u32(np, + "img,i2s-channels", + &max_chan); + if (ret) + return ret; + if (of_property_read_u32(np, + "img,shared-dma", + &shared_dma)) + shared_dma = max_chan; + if (!shared_dma) + shared_dma = 1; + i2s_in_clock_info_acquired = true; + } + + if (i != 0) { + links[i].codec_dai_name = "snd-soc-dummy-dai"; + links[i].codec_name = "snd-soc-dummy"; + } + + if (single_cpu_dai) { + k = 0; + } else { + for (j = 0, k = 0; j < args.args[0]; j++) { + if (j == 0) + k += shared_dma; + else + k++; + } + + ret = snd_soc_of_get_dai_name_alt(i2s_in_np, + "cpu-dais", i, &links[i].cpu_dai_name); + if (ret) + return ret; + + ret = snd_soc_of_get_platform_name(i2s_in_np, + "cpu-dais", i, + &links[i].platform_name); + if (ret) + return ret; + } + + if (clock_masters & (1UL << ((max_chan - k) - 1))) + return -EINVAL; + } + + fmt = snd_soc_of_parse_daifmt(i2s_in_np, NULL, NULL, NULL); + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + pbc->i2s_in->fmt = fmt; + + i2s_mclk_info.num_fs_rates = 0; + dac_mclk_info.num_fs_rates = 0; + + ret = pistachio_card_parse_of_i2s_common(i2s_in_np, pbc, + &pbc->i2s_in->i2s, links, codec_info, + &i2s_mclk_info, &dac_mclk_info); + if (ret) + return ret; + + if (i2s_loopback) { + pbc->i2s_in->frame_master = + PISTACHIO_CLOCK_MASTER_LOOPBACK; + pbc->i2s_in->bitclock_master = + PISTACHIO_CLOCK_MASTER_LOOPBACK; + } else if ((codec_info->bitclock_master_idx == -1) || + (codec_info->frame_master_idx == -1)) { + pbc->i2s_in->frame_master = + PISTACHIO_CLOCK_MASTER_EXT; + pbc->i2s_in->bitclock_master = + PISTACHIO_CLOCK_MASTER_EXT; + } else { + pbc->i2s_in->frame_master = + codec_info->frame_master_idx; + pbc->i2s_in->bitclock_master = + codec_info->bitclock_master_idx; + } + + links[0].init = pistachio_card_i2s_in_link_init; + + /* + * If no mclks are used by i2s in, there is nothing for + * the ops callbacks to do, so leave this as NULL + */ + if (pbc->i2s_in->i2s.mclk_a.mclk) + links[0].ops = &pistachio_card_i2s_in_ops; + + links += num_i2s_in_links; + } + + pbc->i2s_in_alt_start = links; + + codec_info->bitclock_master_idx = -1; + codec_info->frame_master_idx = -1; + + if (i2s_in_alt_np) { + pbc->i2s_in_alt = devm_kzalloc(dev, sizeof(*pbc->i2s_in_alt), + GFP_KERNEL); + if (!pbc->i2s_in_alt) + return -ENOMEM; + + for (i = 0; i < num_i2s_in_alt_links; i++) { + + sprintf(name, "pistachio-i2s-in-alt-%d", i); + str = devm_kzalloc(pbc->card.dev, strlen(name) + 1, + GFP_KERNEL); + strcpy(str, name); + links[i].name = links[i].stream_name = str; + + if (of_property_read_bool(i2s_in_alt_np, "cpu-dai")) { + np = of_parse_phandle(i2s_in_alt_np, + "cpu-dai", 0); + if (!np) + return -EINVAL; + single_cpu_dai = true; + } else { + ret = of_parse_phandle_with_args(i2s_in_alt_np, + "cpu-dais", "#sound-dai-cells", + i, &args); + if (ret) + return ret; + np = args.np; + if (!np) + return -EINVAL; + single_cpu_dai = false; + } + + links[i].cpu_of_node = np; + links[i].platform_of_node = np; + + if (!i2s_in_clock_info_acquired) { + of_property_read_u32(np, + "img,clock-master", + &clock_masters); + clock_masters <<= 2; + ret = of_property_read_u32(np, + "img,i2s-channels", + &max_chan); + if (ret) + return ret; + if (of_property_read_u32(np, + "img,shared-dma", + &shared_dma)) + shared_dma = max_chan; + if (!shared_dma) + shared_dma = 1; + i2s_in_clock_info_acquired = true; + } + + if (i != 0) { + links[i].codec_dai_name = "snd-soc-dummy-dai"; + links[i].codec_name = "snd-soc-dummy"; + } + + if (single_cpu_dai) { + k = 0; + } else { + for (j = 0, k = 0; j < args.args[0]; j++) { + if (j == 0) + k += shared_dma; + else + k++; + } + + ret = snd_soc_of_get_dai_name_alt(i2s_in_alt_np, + "cpu-dais", i, &links[i].cpu_dai_name); + if (ret) + return ret; + + ret = snd_soc_of_get_platform_name( + i2s_in_alt_np, "cpu-dais", i, + &links[i].platform_name); + if (ret) + return ret; + } + + if (~clock_masters & (1UL << ((max_chan - k) - 1))) + return -EINVAL; + } + + fmt = snd_soc_of_parse_daifmt(i2s_in_alt_np, NULL, NULL, NULL); + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + pbc->i2s_in_alt->fmt = fmt; + + i2s_mclk_info.num_fs_rates = 0; + dac_mclk_info.num_fs_rates = 0; + + ret = pistachio_card_parse_of_i2s_common(i2s_in_alt_np, pbc, + &pbc->i2s_in_alt->i2s, links, codec_info, + &i2s_mclk_info, &dac_mclk_info); + if (ret) + return ret; + + if ((codec_info->bitclock_master_idx == -1) || + (codec_info->frame_master_idx == -1)) { + pbc->i2s_in_alt->frame_master = + PISTACHIO_CLOCK_MASTER_EXT; + pbc->i2s_in_alt->bitclock_master = + PISTACHIO_CLOCK_MASTER_EXT; + } else { + pbc->i2s_in_alt->frame_master = + codec_info->frame_master_idx; + pbc->i2s_in_alt->bitclock_master = + codec_info->bitclock_master_idx; + } + + links[0].init = pistachio_card_i2s_in_alt_link_init; + + /* + * If no mclks are used by i2s in, there is nothing for + * the ops callbacks to do, so leave this as NULL + */ + if (pbc->i2s_in_alt->i2s.mclk_a.mclk) + links[0].ops = &pistachio_card_i2s_in_alt_ops; + + links += num_i2s_in_alt_links; + } + + return 0; +} + +static int pistachio_card_parse_of_confs(struct pistachio_card *pbc, + struct pistachio_i2s_codec_info *codec_info, + struct snd_soc_dai_link *parallel_out) +{ + int i, j, n; + unsigned int size; + struct pistachio_i2s_codec_info_s *codecs; + struct snd_soc_codec_conf *conf, *c; + + n = codec_info->unique_codecs; + if (parallel_out) + n += pbc->tpa6130a2 ? 2 : 1; + codecs = codec_info->codecs; + + size = sizeof(*pbc->card.codec_conf) * n; + pbc->card.codec_conf = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL); + if (!pbc->card.codec_conf) + return -ENOMEM; + + conf = pbc->card.codec_conf; + + for (i = 0; i < codec_info->total_codecs; i++) { + for (j = 0; j < i; j++) + if (codecs[j].np == codecs[i].np) + break; + if (j == i) { + conf->of_node = codecs[i].np; + conf->name_prefix = codecs[i].prefix; + conf++; + } + } + + if (parallel_out) { + conf->of_node = parallel_out->codecs[0].of_node; + conf->name_prefix = PISTACHIO_INTERNAL_DAC_PREFIX; + conf++; + if (pbc->tpa6130a2) { + conf->of_node = parallel_out->codecs[1].of_node; + conf->name_prefix = PISTACHIO_TPA6130A2_PREFIX; + } + } + + pbc->card.num_configs = n; + + for (i = 0; i < n; i++) { + conf = &pbc->card.codec_conf[i]; + for (j = i + 1; j < n; j++) { + c = &pbc->card.codec_conf[j]; + if (!strcasecmp(conf->name_prefix, c->name_prefix)) { + dev_err(pbc->card.dev, "Prefix clash: %s\n", + conf->name_prefix); + return -EINVAL; + } + } + } + + return 0; +} + +static int pistachio_card_parse_of(struct device_node *node, + struct pistachio_card *pbc) +{ + int ret = 0; + unsigned int num_i2s_in_links, num_i2s_in_alt_links; + struct device_node *spdif_out_np, *spdif_in_np, *parallel_out_np; + struct device_node *i2s_out_np, *i2s_in_np, *i2s_in_alt_np, *event_np; + struct snd_soc_dai_link *link, *prl_out = NULL; + enum of_gpio_flags flags; + struct pistachio_i2s_codec_info i2s_codec_info; + bool i2s_loopback; + + pbc->periph_regs = syscon_regmap_lookup_by_phandle(node, + "img,cr-periph"); + if (IS_ERR(pbc->periph_regs)) + return PTR_ERR(pbc->periph_regs); + + event_np = of_parse_phandle(node, "img,event-timer", 0); + if (!event_np) + return -EINVAL; + pbc->event_timer_np = event_np; + pbc->event_timer = pistachio_evt_get(event_np); + if (IS_ERR(pbc->event_timer)) + return PTR_ERR(pbc->event_timer); + + if (of_property_read_bool(node, "img,widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(&pbc->card, + "img,widgets"); + if (ret) + return ret; + } + + if (of_property_read_bool(node, "img,routing")) { + ret = snd_soc_of_parse_audio_routing(&pbc->card, + "img,routing"); + if (ret) + return ret; + } + + spdif_out_np = of_get_child_by_name(node, "spdif-out"); + if (spdif_out_np) + pbc->card.num_links++; + + spdif_in_np = of_get_child_by_name(node, "spdif-in"); + if (spdif_in_np) + pbc->card.num_links++; + + parallel_out_np = of_get_child_by_name(node, "parallel-out"); + if (parallel_out_np) + pbc->card.num_links++; + + i2s_out_np = of_get_child_by_name(node, "i2s-out"); + if (i2s_out_np) + pbc->card.num_links++; + + i2s_in_np = of_get_child_by_name(node, "i2s-in"); + if (i2s_in_np) { + if (of_property_read_bool(i2s_in_np, "cpu-dai")) { + num_i2s_in_links = 1; + } else { + ret = of_count_phandle_with_args(i2s_in_np, "cpu-dais", + "#sound-dai-cells"); + if (ret < 0) + goto end; + if (!ret || (ret > 6)) { + ret = -EINVAL; + goto end; + } + num_i2s_in_links = ret; + } + pbc->card.num_links += num_i2s_in_links; + } + + i2s_in_alt_np = of_get_child_by_name(node, "i2s-in-alt"); + if (i2s_in_alt_np) { + if (of_property_read_bool(i2s_in_alt_np, "cpu-dai")) { + num_i2s_in_alt_links = 1; + } else { + ret = of_count_phandle_with_args(i2s_in_alt_np, + "cpu-dais", "#sound-dai-cells"); + if (ret < 0) + goto end; + if (!ret || (ret > 6)) { + ret = -EINVAL; + goto end; + } + num_i2s_in_alt_links = ret; + } + pbc->card.num_links += num_i2s_in_alt_links; + } + + i2s_loopback = of_property_read_bool(node, "img,i2s-clk-loopback"); + if (i2s_loopback && (!i2s_out_np || !i2s_in_np)) { + ret = -EINVAL; + goto end; + } + + if (!pbc->card.num_links) { + ret = -EINVAL; + goto end; + } + + pbc->card.dai_link = devm_kzalloc(pbc->card.dev, + sizeof(*pbc->card.dai_link) * pbc->card.num_links, GFP_KERNEL); + if (!pbc->card.dai_link) { + ret = -ENOMEM; + goto end; + } + + i2s_codec_info.total_codecs = 0; + i2s_codec_info.unique_codecs = 0; + + link = pbc->card.dai_link; + + if (spdif_out_np) { + ret = pistachio_card_parse_of_spdif_out(spdif_out_np, pbc, + link); + if (ret) + goto end; + link++; + } + + if (spdif_in_np) { + ret = pistachio_card_parse_of_spdif_in(spdif_in_np, pbc, + link); + if (ret) + goto end; + link++; + } + + if (parallel_out_np) { + ret = pistachio_card_parse_of_parallel_out(parallel_out_np, + pbc, link); + if (ret) + goto end; + prl_out = link; + link++; + } + + if (i2s_out_np || i2s_in_np || i2s_in_alt_np) { + ret = pistachio_card_parse_of_i2s(i2s_out_np, i2s_in_np, + i2s_in_alt_np, num_i2s_in_links, + num_i2s_in_alt_links, pbc, link, + &i2s_codec_info, i2s_loopback); + if (ret) + goto end; + } + + ret = pistachio_card_parse_of_confs(pbc, &i2s_codec_info, prl_out); + if (ret) + goto end; + + pbc->hp_jack_gpio.gpio = of_get_named_gpio_flags(node, + "img,hp-det-gpio", 0, &flags); + pbc->hp_jack_gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + if (pbc->hp_jack_gpio.gpio == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto end; + } + + pbc->mute_gpio = of_get_named_gpio_flags(node, "img,mute-gpio", 0, + &flags); + pbc->mute_gpio_inverted = !!(flags & OF_GPIO_ACTIVE_LOW); + if (pbc->mute_gpio_inverted == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto end; + } + +end: + if (spdif_out_np) + of_node_put(spdif_out_np); + if (spdif_in_np) + of_node_put(spdif_in_np); + if (parallel_out_np) + of_node_put(parallel_out_np); + if (i2s_out_np) + of_node_put(i2s_out_np); + if (i2s_in_np) + of_node_put(i2s_in_np); + + return ret; +} + +static void pistachio_card_unref(struct pistachio_card *pbc) +{ + int i, j; + struct snd_soc_dai_link *link; + + if (pbc->event_timer_np) + of_node_put(pbc->event_timer_np); + + link = pbc->card.dai_link; + if (!link) + return; + + for (i = 0; i < pbc->card.num_links; i++, link++) { + if (link->cpu_of_node) + of_node_put(link->cpu_of_node); + if (pbc->i2s_in_start && (link >= pbc->i2s_in_start) && + link->platform_of_node) + of_node_put(link->platform_of_node); + for (j = 0; j < link->num_codecs; j++) + of_node_put(link->codecs[j].of_node); + } +} + +static int pistachio_card_init_clk(struct device *dev, char *name, + struct clk **pclk) +{ + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + *pclk = clk; + + return 0; +} + +static int pistachio_card_init_rates(struct pistachio_card *pbc) +{ + unsigned int rate; + int ret; + + rate = PISTACHIO_PLL_RATE_B; + ret = clk_set_rate(pbc->audio_pll, rate); + if (ret) + return ret; + pbc->audio_pll_rate = rate; + + rate = PISTACHIO_MIN_MCLK_FREQ; + ret = clk_set_rate(pbc->i2s_mclk.mclk, rate); + if (ret) + return ret; + pbc->i2s_mclk.cur_rate = rate; + ret = clk_set_rate(pbc->dac_mclk.mclk, rate); + if (ret) + return ret; + pbc->dac_mclk.cur_rate = rate; + + return 0; +} + +static int pistachio_card_info_timespec(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LONG_MAX; + + return 0; +} + +static int pistachio_card_get_event_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uc) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + struct timespec ts; + + pistachio_evt_get_time_ts(pbc->event_timer, &ts); + + uc->value.integer.value[0] = ts.tv_sec; + uc->value.integer.value[1] = ts.tv_nsec; + + return 0; +} + +static int pistachio_card_info_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = PISTACHIO_EVT_NUM_SOURCES - 1; + + return 0; +} + +static int pistachio_card_set_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + int id) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + + return pistachio_evt_set_source(pbc->event_timer, id, + ucontrol->value.integer.value[0]); +} + +static int pistachio_card_set_source_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_set_source(kcontrol, ucontrol, 0); +} + +static int pistachio_card_set_source_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_set_source(kcontrol, ucontrol, 1); +} + +static int pistachio_card_get_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + int id) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + enum pistachio_evt_source source; + int ret; + + ret = pistachio_evt_get_source(pbc->event_timer, id, &source); + + if (!ret) + ucontrol->value.integer.value[0] = source; + + return ret; +} + +static int pistachio_card_get_source_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_source(kcontrol, ucontrol, 0); +} + +static int pistachio_card_get_source_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_source(kcontrol, ucontrol, 1); +} + +void pistachio_card_sample_rate_notify(int id, void *context) +{ + struct pistachio_card *pbc = context; + + if (pbc->sample_rate_ids[id]) + snd_ctl_notify(pbc->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + pbc->sample_rate_ids[id]); +} + +void pistachio_card_sample_rate_notify_a(void *context) +{ + pistachio_card_sample_rate_notify(0, context); +} + +void pistachio_card_sample_rate_notify_b(void *context) +{ + pistachio_card_sample_rate_notify(1, context); +} + +void pistachio_card_phase_difference_notify(void *context) +{ + struct pistachio_card *pbc = context; + + if (pbc->phase_difference_id) + snd_ctl_notify(pbc->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, + pbc->phase_difference_id); +} + +static int pistachio_card_get_sample_period(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + int id) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int ret; + u32 val, freq, nsec; + u64 temp; + + pbc->sample_rate_ids[id] = &kcontrol->id; + + ret = pistachio_evt_get_sample_rate(pbc->event_timer, id, &val, &freq, + pistachio_card_sample_rate_notify_a, pbc); + + if (!ret) { + temp = ((u64)val * NSEC_PER_SEC) + (freq / 2); + do_div(temp, freq); + nsec = do_div(temp, NSEC_PER_SEC); + ucontrol->value.integer.value[0] = temp; + ucontrol->value.integer.value[1] = nsec; + } + + return ret; +} + +static int pistachio_card_get_sample_period_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_sample_period(kcontrol, ucontrol, 0); +} + +static int pistachio_card_get_sample_period_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_sample_period(kcontrol, ucontrol, 1); +} + +static int pistachio_card_info_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LONG_MAX; + + return 0; +} + +static int pistachio_card_get_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol, + int id) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int ret; + u32 val, freq, rate; + + pbc->sample_rate_ids[id] = &kcontrol->id; + + ret = pistachio_evt_get_sample_rate(pbc->event_timer, id, &val, &freq, + pistachio_card_sample_rate_notify_b, pbc); + + if (!ret) { + if (!val) + return -EINVAL; + rate = DIV_ROUND_CLOSEST(freq, val); + ucontrol->value.integer.value[0] = rate; + } + + return ret; +} + +static int pistachio_card_get_sample_rate_a(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_sample_rate(kcontrol, ucontrol, 0); +} + +static int pistachio_card_get_sample_rate_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return pistachio_card_get_sample_rate(kcontrol, ucontrol, 1); +} + +static int pistachio_card_get_phase_difference(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int ret; + u32 val, freq, nsec; + u64 temp; + + pbc->phase_difference_id = &kcontrol->id; + + ret = pistachio_evt_get_phase_difference(pbc->event_timer, &val, + &freq, pistachio_card_phase_difference_notify, pbc); + + if (!ret) { + temp = ((u64)val * NSEC_PER_SEC) + (freq / 2); + do_div(temp, freq); + nsec = do_div(temp, NSEC_PER_SEC); + ucontrol->value.integer.value[0] = temp; + ucontrol->value.integer.value[1] = nsec; + } + + return ret; +} + +static int pistachio_card_get_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int ret; + + ret = gpio_get_value_cansleep(pbc->mute_gpio); + if (ret < 0) + return ret; + else if (pbc->mute_gpio_inverted) + ucontrol->value.integer.value[0] = !ret; + else + ucontrol->value.integer.value[0] = !!ret; + + return 0; +} + +static int pistachio_card_set_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int val; + + if (pbc->mute_gpio_inverted) + val = !ucontrol->value.integer.value[0]; + else + val = ucontrol->value.integer.value[0]; + + gpio_set_value_cansleep(pbc->mute_gpio, val); + + return 0; +} + +static int pistachio_card_info_sample_rates(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 192000; + + return 0; +} + +static int pistachio_card_set_sample_rates_mclk(struct pistachio_card *pbc, + struct pistachio_mclk *mclk, unsigned int i2s_out_rate, + unsigned int i2s_in_rate, unsigned int i2s_in_alt_rate) +{ + struct pistachio_i2s_mclk *mclk_a, *mclk_b, *mclk_c; + unsigned int rate_a, rate_b, rate_c; + int ret = 0; + + mclk_a = NULL; + mclk_b = NULL; + mclk_c = NULL; + rate_a = i2s_out_rate; + rate_b = i2s_in_rate; + rate_c = i2s_in_alt_rate; + + if (i2s_out_rate) { + if (pbc->i2s_out->i2s.mclk_a.mclk == mclk) + mclk_a = &pbc->i2s_out->i2s.mclk_a; + else if (pbc->i2s_out->i2s.mclk_b.mclk == mclk) + mclk_a = &pbc->i2s_out->i2s.mclk_b; + } + if (i2s_in_rate) { + if (pbc->i2s_in->i2s.mclk_a.mclk == mclk) + mclk_b = &pbc->i2s_in->i2s.mclk_a; + else if (pbc->i2s_in->i2s.mclk_b.mclk == mclk) + mclk_b = &pbc->i2s_in->i2s.mclk_b; + } + if (i2s_in_alt_rate) { + if (pbc->i2s_in_alt->i2s.mclk_a.mclk == mclk) + mclk_c = &pbc->i2s_in_alt->i2s.mclk_a; + else if (pbc->i2s_in_alt->i2s.mclk_b.mclk == mclk) + mclk_c = &pbc->i2s_in_alt->i2s.mclk_b; + } + if (!mclk_a) { + mclk_a = mclk_b; + rate_a = rate_b; + mclk_b = mclk_c; + rate_b = rate_c; + mclk_c = NULL; + if (!mclk_a) { + mclk_a = mclk_b; + rate_a = rate_b; + mclk_b = NULL; + } + } else if (!mclk_b) { + mclk_b = mclk_c; + rate_b = rate_c; + mclk_c = NULL; + } + + if (mclk_a) { + ret = pistachio_card_update_mclk(pbc, mclk_a, mclk_b, mclk_c, + rate_a, rate_b, rate_c); + } + + return ret; +} + +static int pistachio_card_set_sample_rates(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + int ret; + unsigned int pll_rate, i2s_out_rate = 0, i2s_in_rate = 0; + unsigned int i2s_in_alt_rate = 0; + + + if (pbc->i2s_out) + i2s_out_rate = ucontrol->value.integer.value[0]; + if (pbc->i2s_in && pbc->i2s_in->i2s.mclk_a.mclk) + i2s_in_rate = ucontrol->value.integer.value[1]; + if (pbc->i2s_in_alt && pbc->i2s_in_alt->i2s.mclk_a.mclk) + i2s_in_alt_rate = ucontrol->value.integer.value[2]; + + if (!i2s_out_rate && !i2s_in_rate && !i2s_in_alt_rate) + return 0; + + pll_rate = 0; + + if (i2s_out_rate) { + ret = pistachio_card_get_pll_rate(i2s_out_rate); + if (ret < 0) + return ret; + pll_rate = ret; + } + + if (i2s_in_rate) { + ret = pistachio_card_get_pll_rate(i2s_in_rate); + if (ret < 0) + return ret; + if (pll_rate && (ret != pll_rate)) + return -EINVAL; + pll_rate = ret; + } + + if (i2s_in_alt_rate) { + ret = pistachio_card_get_pll_rate(i2s_in_alt_rate); + if (ret < 0) + return ret; + if (pll_rate && (ret != pll_rate)) + return -EINVAL; + pll_rate = ret; + } + + mutex_lock(&pbc->rate_mutex); + + if (pbc->audio_pll_rate != pll_rate) { + ret = pistachio_card_set_pll_rate(pbc, pll_rate); + if (ret) { + mutex_unlock(&pbc->rate_mutex); + return ret; + } + } + + ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->i2s_mclk, + i2s_out_rate, i2s_in_rate, i2s_in_alt_rate); + if (ret) { + mutex_unlock(&pbc->rate_mutex); + return ret; + } + + ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->dac_mclk, + i2s_out_rate, i2s_in_rate, i2s_in_alt_rate); + + mutex_unlock(&pbc->rate_mutex); + + return ret; +} + +static struct snd_kcontrol_new pistachio_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Event Time", + .info = pistachio_card_info_timespec, + .get = pistachio_card_get_event_time + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Measurement Source A", + .info = pistachio_card_info_source, + .get = pistachio_card_get_source_a, + .put = pistachio_card_set_source_a + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Measurement Source B", + .info = pistachio_card_info_source, + .get = pistachio_card_get_source_b, + .put = pistachio_card_set_source_b + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Sample Rate A", + .info = pistachio_card_info_sample_rate, + .get = pistachio_card_get_sample_rate_a, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Sample Rate B", + .info = pistachio_card_info_sample_rate, + .get = pistachio_card_get_sample_rate_b, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Sample Period A", + .info = pistachio_card_info_timespec, + .get = pistachio_card_get_sample_period_a, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Sample Period B", + .info = pistachio_card_info_timespec, + .get = pistachio_card_get_sample_period_b, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Phase Difference", + .info = pistachio_card_info_timespec, + .get = pistachio_card_get_phase_difference, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_WRITE, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "I2S Rates", + .info = pistachio_card_info_sample_rates, + .put = pistachio_card_set_sample_rates + }, +}; + +#ifdef DEBUG + +static void pistachio_card_info_mclk(struct pistachio_card *pbc, + struct pistachio_i2s_mclk *mclk) +{ + struct device *dev = pbc->card.dev; + int i; + + dev_dbg(dev, " Min Freq: %u\n", mclk->mclk->min_rate); + dev_dbg(dev, " Max Freq: %u\n", mclk->mclk->max_rate); + dev_dbg(dev, " FS Rates:\n"); + + for (i = 0; i < mclk->num_fs_rates; i++) + dev_dbg(dev, " %u\n", mclk->fs_rates[i]); +} + +static void pistachio_card_info_mclks(struct pistachio_card *pbc, + struct pistachio_i2s *i2s) +{ + struct pistachio_i2s_mclk *i2s_mclk; + struct pistachio_i2s_mclk *dac_mclk; + struct device *dev = pbc->card.dev; + + if (i2s->mclk_a.mclk == &pbc->i2s_mclk) + i2s_mclk = &i2s->mclk_a; + else if (pbc->i2s_in->i2s.mclk_b.mclk == &pbc->i2s_mclk) + i2s_mclk = &i2s->mclk_b; + else + i2s_mclk = NULL; + + if (i2s_mclk) { + dev_dbg(dev, " I2S MCLK\n"); + pistachio_card_info_mclk(pbc, i2s_mclk); + } else { + dev_dbg(dev, " I2S MCLK NOT USED\n"); + } + + dev_dbg(dev, "\n"); + + if (i2s->mclk_a.mclk == &pbc->dac_mclk) + dac_mclk = &i2s->mclk_a; + else if (i2s->mclk_b.mclk == &pbc->dac_mclk) + dac_mclk = &i2s->mclk_b; + else + dac_mclk = NULL; + + if (dac_mclk) { + dev_dbg(dev, " DAC MCLK\n"); + pistachio_card_info_mclk(pbc, dac_mclk); + } else { + dev_dbg(dev, " DAC MCLK NOT USED\n"); + } +} + +static void pistachio_card_info_i2s_out(struct pistachio_card *pbc, + struct snd_soc_dai_link *link) +{ + int i, j; + struct snd_soc_dai_link_component *components; + struct snd_soc_codec_conf *confs; + struct device *dev = pbc->card.dev; + char *text; + + components = pbc->i2s_out->i2s.components; + confs = pbc->card.codec_conf; + + dev_dbg(dev, "I2S OUT\n"); + dev_dbg(dev, "\n"); + if (pbc->i2s_in && (pbc->i2s_in->frame_master == + PISTACHIO_CLOCK_MASTER_LOOPBACK)) + text = "(Dual Frame + Bit Clock Master)"; + else + text = "(Frame + Bit Clock Master)"; + dev_dbg(dev, " CPU DAI\n"); + dev_dbg(dev, " i2s-out (%s) %s\n", + link->cpu_of_node->name, text); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CODECS\n"); + + for (i = 0; i < pbc->i2s_out->i2s.num_codecs; i++) { + for (j = 0; j < pbc->card.num_configs; j++) + if (confs[j].of_node == components[i].of_node) + break; + + dev_dbg(dev, " %s (%s) (%s)\n", confs[j].name_prefix, + confs[j].of_node->name, + components[i].dai_name); + } + dev_dbg(dev, "\n"); + + pistachio_card_info_mclks(pbc, &pbc->i2s_out->i2s); + + dev_dbg(dev, "\n"); + + if ((link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) + text = "I2S"; + else + text = "Left Justified"; + dev_dbg(dev, " Format: %s\n", text); + + if ((link->dai_fmt & SND_SOC_DAIFMT_CLOCK_MASK) == SND_SOC_DAIFMT_CONT) + text = "Yes"; + else + text = "No"; + dev_dbg(dev, " Continuous Clock: %s\n", text); + + dev_dbg(dev, "\n"); +} + +static void pistachio_card_info_i2s_in(struct pistachio_card *pbc, + struct snd_soc_dai_link *links) +{ + int i, j; + struct snd_soc_dai_link_component *components; + struct snd_soc_codec_conf *confs; + char *text; + struct device *dev = pbc->card.dev; + + components = pbc->i2s_in->i2s.components; + confs = pbc->card.codec_conf; + + dev_dbg(dev, "I2S IN\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CPU DAIS\n"); + for (i = 0; i < (pbc->i2s_in_alt_start - pbc->i2s_in_start); i++) { + dev_dbg(dev, " i2s-in-%d (%s) (%s)\n", i, + links[i].cpu_of_node->name, links[i].cpu_dai_name); + } + dev_dbg(dev, "\n"); + dev_dbg(dev, " CODECS\n"); + + for (i = 0; i < pbc->i2s_in->i2s.num_codecs; i++) { + for (j = 0; j < pbc->card.num_configs; j++) + if (confs[j].of_node == components[i].of_node) + break; + + if (i == pbc->i2s_in->frame_master) + if (i == pbc->i2s_in->bitclock_master) + text = "(Frame + Bit Clock Master)"; + else + text = "(Frame Master)"; + else + if (i == pbc->i2s_in->bitclock_master) + text = "(Bitclock Master)"; + else + text = ""; + + dev_dbg(dev, " %s (%s) (%s) %s\n", confs[j].name_prefix, + confs[j].of_node->name, + components[i].dai_name, text); + } + dev_dbg(dev, "\n"); + + pistachio_card_info_mclks(pbc, &pbc->i2s_in->i2s); + + dev_dbg(dev, "\n"); + + if ((pbc->i2s_in->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_I2S) + text = "I2S"; + else + text = "Left Justified"; + dev_dbg(dev, " Format: %s\n", text); + + if ((pbc->i2s_in->fmt & SND_SOC_DAIFMT_CLOCK_MASK) == + SND_SOC_DAIFMT_CONT) + text = "Yes"; + else + text = "No"; + dev_dbg(dev, " Continuous Clock: %s\n", text); + + dev_dbg(dev, "\n"); +} + +static void pistachio_card_info_i2s_in_alt(struct pistachio_card *pbc, + struct snd_soc_dai_link *links) +{ + int i, j; + struct snd_soc_dai_link_component *components; + struct snd_soc_codec_conf *confs; + char *text; + struct device *dev = pbc->card.dev; + + components = pbc->i2s_in_alt->i2s.components; + confs = pbc->card.codec_conf; + + dev_dbg(dev, "I2S IN (ALTERNATE)\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CPU DAIS\n"); + for (i = 0; i < (&pbc->card.dai_link[pbc->card.num_links] - + pbc->i2s_in_alt_start); i++) { + dev_dbg(dev, " i2s-in-alt-%d (%s) (%s)\n", i, + links[i].cpu_of_node->name, links[i].cpu_dai_name); + } + dev_dbg(dev, "\n"); + dev_dbg(dev, " CODECS\n"); + + for (i = 0; i < pbc->i2s_in_alt->i2s.num_codecs; i++) { + for (j = 0; j < pbc->card.num_configs; j++) + if (confs[j].of_node == components[i].of_node) + break; + + if (i == pbc->i2s_in_alt->frame_master) + if (i == pbc->i2s_in_alt->bitclock_master) + text = "(Frame + Bit Clock Master)"; + else + text = "(Frame Master)"; + else + if (i == pbc->i2s_in_alt->bitclock_master) + text = "(Bitclock Master)"; + else + text = ""; + + dev_dbg(dev, " %s (%s) (%s) %s\n", confs[j].name_prefix, + confs[j].of_node->name, + components[i].dai_name, text); + } + dev_dbg(dev, "\n"); + + pistachio_card_info_mclks(pbc, &pbc->i2s_in_alt->i2s); + + dev_dbg(dev, "\n"); + + if ((pbc->i2s_in_alt->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_I2S) + text = "I2S"; + else + text = "Left Justified"; + dev_dbg(dev, " Format: %s\n", text); + + if ((pbc->i2s_in_alt->fmt & SND_SOC_DAIFMT_CLOCK_MASK) == + SND_SOC_DAIFMT_CONT) + text = "Yes"; + else + text = "No"; + dev_dbg(dev, " Continuous Clock: %s\n", text); + + dev_dbg(dev, "\n"); +} + +static void pistachio_card_info(struct pistachio_card *pbc) +{ + struct device *dev = pbc->card.dev; + struct snd_soc_codec_conf *conf; + struct snd_soc_dai_link *link; + char *text; + + link = pbc->card.dai_link; + + dev_dbg(dev, "\n"); + dev_dbg(dev, "####################################################\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, "Pistachio Audio Card\n"); + dev_dbg(dev, "\n"); + + if (pbc->spdif_out) { + dev_dbg(dev, "SPDIF OUT\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CPU DAI\n"); + dev_dbg(dev, " spdif-out (%s)\n", + link->cpu_of_node->name); + dev_dbg(dev, "\n"); + link++; + } + if (pbc->spdif_in) { + dev_dbg(dev, "SPDIF IN\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CPU DAI\n"); + dev_dbg(dev, " spdif-in (%s)\n", + link->cpu_of_node->name); + dev_dbg(dev, "\n"); + link++; + } + if (pbc->parallel_out) { + dev_dbg(dev, "PARALLEL OUT\n"); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CPU DAI\n"); + dev_dbg(dev, " parallel-out (%s)\n", + link->cpu_of_node->name); + dev_dbg(dev, "\n"); + dev_dbg(dev, " CODECS\n"); + conf = &pbc->card.codec_conf[pbc->card.num_configs - 1]; + if (pbc->tpa6130a2) + conf--; + dev_dbg(dev, " %s (%s) (%s)\n", conf->name_prefix, + conf->of_node->name, + pbc->parallel_out->components[0].dai_name); + if (pbc->tpa6130a2) { + conf++; + dev_dbg(dev, " %s (%s) (%s)\n", + conf->name_prefix, + conf->of_node->name, + pbc->parallel_out->components[1].dai_name); + } + dev_dbg(dev, "\n"); + link++; + } + if (pbc->i2s_out) { + pistachio_card_info_i2s_out(pbc, link); + link++; + } + if (pbc->i2s_in) { + pistachio_card_info_i2s_in(pbc, link); + link += (pbc->i2s_in_alt_start - pbc->i2s_in_start); + } + + if (pbc->i2s_in_alt) + pistachio_card_info_i2s_in_alt(pbc, link); + + if (gpio_is_valid(pbc->mute_gpio)) { + if (pbc->mute_gpio_inverted) + text = "(Active Low)"; + else + text = "(Active High)"; + dev_dbg(dev, "Mute: GPIO %u %s\n", pbc->mute_gpio, text); + } + if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) { + if (pbc->hp_jack_gpio.invert) + text = "(Active Low)"; + else + text = "(Active High)"; + dev_dbg(dev, "Headphone-Detect: GPIO %u %s\n", + pbc->hp_jack_gpio.gpio, text); + } + dev_dbg(dev, "\n"); + dev_dbg(dev, "####################################################\n"); + dev_dbg(dev, "\n"); +} + +#endif + +static int pistachio_card_probe(struct platform_device *pdev) +{ + struct pistachio_card *pbc; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + unsigned long gpio_flags; + struct snd_kcontrol_new *control; + + if (!np || !of_device_is_available(np)) + return -EINVAL; + + pbc = devm_kzalloc(dev, sizeof(*pbc), GFP_KERNEL); + if (!pbc) + return -ENOMEM; + + pbc->card.owner = THIS_MODULE; + pbc->card.dev = dev; + pbc->card.name = "pistachio-card"; + + snd_soc_card_set_drvdata(&pbc->card, pbc); + + mutex_init(&pbc->rate_mutex); + + pbc->hp_jack_gpio.gpio = -ENOENT; + pbc->mute_gpio = -ENOENT; + + ret = pistachio_card_parse_of(np, pbc); + if (ret) + goto err; + + ret = pistachio_card_init_clk(dev, "audio_pll", &pbc->audio_pll); + if (ret) + goto err; + + ret = pistachio_card_init_clk(dev, "i2s_mclk", &pbc->i2s_mclk.mclk); + if (ret) + goto err_clk_audio_pll; + + ret = pistachio_card_init_clk(dev, "dac_clk", &pbc->dac_mclk.mclk); + if (ret) + goto err_clk_i2s; + + ret = pistachio_card_init_rates(pbc); + if (ret) + goto err_clk_dac; + + pbc->i2s_clk_notifier.notifier_call = pistachio_card_i2s_clk_cb; + ret = clk_notifier_register(pbc->i2s_mclk.mclk, + &pbc->i2s_clk_notifier); + if (ret) + goto err_clk_dac; + + ret = devm_snd_soc_register_card(dev, &pbc->card); + if (ret) + goto err_notifier; + + ret = snd_soc_add_card_controls(&pbc->card, pistachio_controls, + ARRAY_SIZE(pistachio_controls)); + if (ret) + goto err_notifier; + + if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) { + pbc->hp_jack_pin.pin = "Headphones"; + pbc->hp_jack_pin.mask = SND_JACK_HEADPHONE; + pbc->hp_jack_gpio.name = "Headphone detection"; + pbc->hp_jack_gpio.report = SND_JACK_HEADPHONE; + pbc->hp_jack_gpio.debounce_time = 150; + ret = snd_soc_card_jack_new(&pbc->card, "Headphones", + SND_JACK_HEADPHONE, &pbc->hp_jack, &pbc->hp_jack_pin, + 1); + if (ret) + goto err_notifier; + ret = snd_soc_jack_add_gpios(&pbc->hp_jack, 1, + &pbc->hp_jack_gpio); + if (ret) + goto err_notifier; + } + + if (gpio_is_valid(pbc->mute_gpio)) { + if (pbc->mute_gpio_inverted) + gpio_flags = GPIOF_OUT_INIT_HIGH; + else + gpio_flags = GPIOF_OUT_INIT_LOW; + ret = gpio_request_one(pbc->mute_gpio, gpio_flags, "Mute"); + if (ret) + goto err_jack; + control = devm_kzalloc(dev, sizeof(*control), GFP_KERNEL); + if (!control) { + ret = -ENOMEM; + goto err_mute; + } + control->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + control->iface = SNDRV_CTL_ELEM_IFACE_CARD; + control->name = "Mute Switch"; + control->info = snd_ctl_boolean_mono_info; + control->get = pistachio_card_get_mute; + control->put = pistachio_card_set_mute; + ret = snd_soc_add_card_controls(&pbc->card, control, 1); + if (ret) + goto err_mute; + } + +#ifdef DEBUG + pistachio_card_info(pbc); +#endif + + return 0; + +err_mute: + if (gpio_is_valid(pbc->mute_gpio)) + gpio_free(pbc->mute_gpio); +err_jack: + if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) + snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio); +err_notifier: + clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier); +err_clk_dac: + clk_disable_unprepare(pbc->dac_mclk.mclk); +err_clk_i2s: + clk_disable_unprepare(pbc->i2s_mclk.mclk); +err_clk_audio_pll: + clk_disable_unprepare(pbc->audio_pll); +err: + pistachio_card_unref(pbc); + + return ret; +} + +static int pistachio_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct pistachio_card *pbc = snd_soc_card_get_drvdata(card); + + pistachio_evt_abort_measurements(pbc->event_timer); + if (gpio_is_valid(pbc->mute_gpio)) + gpio_free(pbc->mute_gpio); + if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) + snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio); + clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier); + clk_disable_unprepare(pbc->dac_mclk.mclk); + clk_disable_unprepare(pbc->i2s_mclk.mclk); + clk_disable_unprepare(pbc->audio_pll); + pistachio_card_unref(pbc); + + return 0; +} + +static const struct of_device_id pistachio_card_of_match[] = { + { .compatible = "img,pistachio-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pistachio_card_of_match); + +static struct platform_driver pistachio_card = { + .driver = { + .name = "pistachio-card", + .of_match_table = pistachio_card_of_match, + }, + .probe = pistachio_card_probe, + .remove = pistachio_card_remove, +}; +module_platform_driver(pistachio_card); + +MODULE_DESCRIPTION("Pistachio audio card driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a1305f827a98f0..259f75ad29c83e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -960,6 +960,9 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) if (platform->dev->of_node != dai_link->platform_of_node) continue; + if(platform_name && strcmp(platform->component.name, + platform_name)) + continue; } else { if (strcmp(platform->component.name, platform_name)) continue; @@ -2373,12 +2376,12 @@ int snd_soc_register_card(struct snd_soc_card *card) * Platform may be specified by either name or OF node, but * can be left unspecified, and a dummy platform will be used. */ - if (link->platform_name && link->platform_of_node) { + /*if (link->platform_name && link->platform_of_node) { dev_err(card->dev, "ASoC: Both platform name/of_node are set for %s\n", link->name); return -EINVAL; - } + }*/ /* * CPU device may be specified by either name or OF node, but @@ -3587,6 +3590,47 @@ static int snd_soc_get_dai_name(struct of_phandle_args *args, return ret; } +static int snd_soc_get_platform_name(struct of_phandle_args *args, + const char **platform_name) +{ + struct snd_soc_platform *platform; + int ret = -EPROBE_DEFER; + int id = -1; + + mutex_lock(&client_mutex); + list_for_each_entry(platform, &platform_list, list) { + if (platform->dev->of_node != args->np) + continue; + + switch (args->args_count) { + case 0: + *platform_name = platform->component.name; + ret = 0; + break; + case 1: + id = args->args[0]; + if(platform->component.id == id) { + *platform_name = platform->component.name; + ret = 0; + } + break; + default: + /* not supported */ + break; + } + + if (!ret) + break; + + if (id < 0) { + ret = -EINVAL; + continue; + } + } + mutex_unlock(&client_mutex); + return ret; +} + int snd_soc_of_get_dai_name(struct device_node *of_node, const char **dai_name) { @@ -3606,6 +3650,44 @@ int snd_soc_of_get_dai_name(struct device_node *of_node, } EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name); +int snd_soc_of_get_dai_name_alt(struct device_node *of_node, char *name, + int index, const char **dai_name) +{ + struct of_phandle_args args; + int ret; + + ret = of_parse_phandle_with_args(of_node, name, "#sound-dai-cells", + index, &args); + if (ret) + return ret; + + ret = snd_soc_get_dai_name(&args, dai_name); + + of_node_put(args.np); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name_alt); + +int snd_soc_of_get_platform_name(struct device_node *of_node, char *name, + int index, const char **platform_name) +{ + struct of_phandle_args args; + int ret; + + ret = of_parse_phandle_with_args(of_node, name, + "#sound-platform-cells", index, &args); + if (ret) + return ret; + + ret = snd_soc_get_platform_name(&args, platform_name); + + of_node_put(args.np); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_platform_name); + /* * snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree * @dev: Card device diff --git a/sound/soc/soc-devres.c b/sound/soc/soc-devres.c index a57921eeee81a7..86e476830a5f9a 100644 --- a/sound/soc/soc-devres.c +++ b/sound/soc/soc-devres.c @@ -159,4 +159,27 @@ int devm_snd_dmaengine_pcm_register(struct device *dev, } EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register); +int devm_snd_dmaengine_pcm_register_id_name(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags, + unsigned int id, char *platform_name) +{ + struct device **ptr; + int ret; + + ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_dmaengine_pcm_register_id_name(dev, config, flags, id, platform_name); + if (ret == 0) { + *ptr = dev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register_id_name); + #endif diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 6fd1906af3873a..870dda09532aed 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -35,6 +35,7 @@ struct dmaengine_pcm { const struct snd_dmaengine_pcm_config *config; struct snd_soc_platform platform; unsigned int flags; + bool dma_pre_started; }; static struct dmaengine_pcm *soc_platform_to_pcm(struct snd_soc_platform *p) @@ -318,14 +319,90 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( return snd_dmaengine_pcm_pointer(substream); } +int dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + struct dma_chan *dma_chan = snd_dmaengine_pcm_get_chan(substream); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if(!pcm->dma_pre_started) { + ret = dmaengine_pcm_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(dma_chan); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->runtime->hw.info & SNDRV_PCM_INFO_RESUME) + dmaengine_resume(dma_chan); + else + return -ENOSYS; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->runtime->hw.info & SNDRV_PCM_INFO_PAUSE) + dmaengine_pause(dma_chan); + else + dmaengine_terminate_all(dma_chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_all(dma_chan); + pcm->dma_pre_started = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dmaengine_start_at(struct snd_pcm_substream *substream, + int audio_clock_type, const struct timespec *ts) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + struct dma_chan *dma_chan = snd_dmaengine_pcm_get_chan(substream); + int ret; + + if(pcm->flags & SND_DMAENGINE_PCM_FLAG_EARLY_START) { + ret = dmaengine_pcm_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(dma_chan); + pcm->dma_pre_started = true; + } + + return 0; +} + +static int dmaengine_start_at_abort(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform); + struct dma_chan *dma_chan = snd_dmaengine_pcm_get_chan(substream); + + if(pcm->dma_pre_started) { + dmaengine_terminate_all(dma_chan); + pcm->dma_pre_started = false; + } + + return 0; +} + static const struct snd_pcm_ops dmaengine_pcm_ops = { .open = dmaengine_pcm_open, .close = snd_dmaengine_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = dmaengine_pcm_hw_params, .hw_free = snd_pcm_lib_free_pages, - .trigger = snd_dmaengine_pcm_trigger, + .trigger = dmaengine_pcm_trigger, .pointer = dmaengine_pcm_pointer, + .start_at = dmaengine_start_at, + .start_at_abort = dmaengine_start_at_abort }; static const struct snd_soc_platform_driver dmaengine_pcm_platform = { @@ -442,6 +519,42 @@ int snd_dmaengine_pcm_register(struct device *dev, } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register); +int snd_dmaengine_pcm_register_id_name(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags, + unsigned int id, char *platform_name) +{ + struct dmaengine_pcm *pcm; + int ret; + + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->config = config; + pcm->flags = flags; + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) + goto err_free_dma; + + ret = snd_soc_add_platform(dev, &pcm->platform, + &dmaengine_pcm_platform); + if (ret) + goto err_free_dma; + + pcm->platform.component.id = id; + kfree(pcm->platform.component.name); + pcm->platform.component.name = kstrdup(platform_name, GFP_KERNEL); + + return 0; + +err_free_dma: + dmaengine_pcm_release_chan(pcm); + kfree(pcm); + return ret; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register_id_name); + /** * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device * @dev: Parent device the PCM was register with diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 65b936e251eae1..dc00de97c578e5 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1072,6 +1072,91 @@ static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, } return 0; } + +static int soc_pcm_start_at(struct snd_pcm_substream *substream, + int audio_clock_type, const struct timespec *start_time) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && + codec_dai->driver->ops->start_at) { + ret = codec_dai->driver->ops->start_at(substream, + codec_dai, audio_clock_type, start_time); + if (ret < 0) + return ret; + } + } + + if (platform->driver->ops && platform->driver->ops->start_at) { + ret = platform->driver->ops->start_at(substream, + audio_clock_type, start_time); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops && cpu_dai->driver->ops->start_at) { + ret = cpu_dai->driver->ops->start_at(substream, + cpu_dai, audio_clock_type, start_time); + if (ret < 0) + return ret; + } + + if (rtd->dai_link->ops && rtd->dai_link->ops->start_at) { + ret = rtd->dai_link->ops->start_at(substream, + audio_clock_type, start_time); + if (ret < 0) + return ret; + } + + return 0; +} + +static int soc_pcm_start_at_abort(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && + codec_dai->driver->ops->start_at_abort) { + ret = codec_dai->driver->ops->start_at_abort( + substream, codec_dai); + if (ret < 0) + return ret; + } + } + + if (platform->driver->ops && platform->driver->ops->start_at_abort) { + ret = platform->driver->ops->start_at_abort(substream); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops && cpu_dai->driver->ops->start_at_abort) { + ret = cpu_dai->driver->ops->start_at_abort(substream, cpu_dai); + if (ret < 0) + return ret; + } + + if (rtd->dai_link->ops && rtd->dai_link->ops->start_at_abort) { + ret = rtd->dai_link->ops->start_at_abort(substream); + if (ret < 0) + return ret; + } + + return 0; +} + /* * soc level wrapper for pointer callback * If cpu_dai, codec_dai, platform driver has the delay callback, than @@ -2617,6 +2702,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.close = dpcm_fe_dai_close; rtd->ops.pointer = soc_pcm_pointer; rtd->ops.ioctl = soc_pcm_ioctl; + rtd->ops.start_at = soc_pcm_start_at; + rtd->ops.start_at_abort = soc_pcm_start_at_abort; } else { rtd->ops.open = soc_pcm_open; rtd->ops.hw_params = soc_pcm_hw_params; @@ -2626,6 +2713,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.close = soc_pcm_close; rtd->ops.pointer = soc_pcm_pointer; rtd->ops.ioctl = soc_pcm_ioctl; + rtd->ops.start_at = soc_pcm_start_at; + rtd->ops.start_at_abort = soc_pcm_start_at_abort; } if (platform->driver->ops) { diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index 0d19d5447d6c72..fb45fad76e0ba4 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -135,6 +135,8 @@ $(call allow-override,LD,$(CROSS_COMPILE)ld) PKG_CONFIG = $(CROSS_COMPILE)pkg-config +LD += $(EXTRA_LDFLAGS) + RM = rm -f LN = ln -f MKDIR = mkdir