diff options
306 files changed, 19287 insertions, 2691 deletions
diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt new file mode 100644 index 000000000000..016b768bc722 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cs35l35.txt @@ -0,0 +1,180 @@ +CS35L35 Boosted Speaker Amplifier + +Required properties: + + - compatible : "cirrus,cs35l35" + + - reg : the I2C address of the device for I2C + + - VA-supply, VP-supply : power supplies for the device, + as covered in + Documentation/devicetree/bindings/regulator/regulator.txt. + + - interrupt-parent : Specifies the phandle of the interrupt controller to + which the IRQs from CS35L35 are delivered to. + - interrupts : IRQ line info CS35L35. + (See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt + for further information relating to interrupt properties) + +Optional properties: + - reset-gpios : gpio used to reset the amplifier + + - cirrus,stereo-config : Boolean to determine if there are 2 AMPs for a + Stereo configuration + + - cirrus,audio-channel : Set Location of Audio Signal on Serial Port + 0 = Data Packet received on Left I2S Channel + 1 = Data Packet received on Right I2S Channel + + - cirrus,advisory-channel : Set Location of Advisory Signal on Serial Port + 0 = Data Packet received on Left I2S Channel + 1 = Data Packet received on Right I2S Channel + + - cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal + if 2 Devices share Boost BST_CTL + + - cirrus,external-boost : Boolean to specify the device is using an external + boost supply, note that sharing a boost from another cs35l35 would constitute + using an external supply for the slave device + + - cirrus,sp-drv-strength : Value for setting the Serial Port drive strength + Table 3-10 of the datasheet lists drive-strength specifications + 0 = 1x (Default) + 1 = .5x + - cirrus,sp-drv-unused : Determines how unused slots should be driven on the + Serial Port. + 0 - Hi-Z + 2 - Drive 0's (Default) + 3 - Drive 1's + + - cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control + powers down with a rectification FET On or Off. If VSPK is supplied + externally then FET is off. + + - cirrus,boost-ctl-millivolt : Boost Voltage Value. Configures the boost + converter's output voltage in mV. The range is from 2600mV to 9000mV with + increments of 100mV. + (Default) VP + + - cirrus,boost-peak-milliamp : Boost-converter peak current limit in mA. + Configures the peak current by monitoring the current through the boost FET. + Range starts at 1680mA and goes to a maximum of 4480mA with increments of + 110mA. + (Default) 2.46 Amps + + - cirrus,amp-gain-zc : Boolean to determine if to use Amplifier gain-change + zero-cross + +Optional H/G Algorithm sub-node: + + The cs35l35 node can have a single "cirrus,classh-internal-algo" sub-node + that will disable automatic control of the internal H/G Algorithm. + + It is strongly recommended that the Datasheet be referenced when adjusting + or using these Class H Algorithm controls over the internal Algorithm. + Serious damage can occur to the Device and surrounding components. + + - cirrus,classh-internal-algo : Sub-node for the Internal Class H Algorithm + See Section 4.3 Internal Class H Algorithm in the Datasheet. + If not used, the device manages the ClassH Algorithm internally. + +Optional properties for the "cirrus,classh-internal-algo" Sub-node + + Section 7.29 Class H Control + - cirrus,classh-bst-overide : Boolean + - cirrus,classh-bst-max-limit + - cirrus,classh-mem-depth + + Section 7.30 Class H Headroom Control + - cirrus,classh-headroom + + Section 7.31 Class H Release Rate + - cirrus,classh-release-rate + + Section 7.32 Class H Weak FET Drive Control + - cirrus,classh-wk-fet-disable + - cirrus,classh-wk-fet-delay + - cirrus,classh-wk-fet-thld + + Section 7.34 Class H VP Control + - cirrus,classh-vpch-auto + - cirrus,classh-vpch-rate + - cirrus,classh-vpch-man + +Optional Monitor Signal Format sub-node: + + The cs35l35 node can have a single "cirrus,monitor-signal-format" sub-node + for adjusting the Depth, Location and Frame of the Monitoring Signals + for Algorithms. + + See Sections 4.8.2 through 4.8.4 Serial-Port Control in the Datasheet + + -cirrus,monitor-signal-format : Sub-node for the Monitor Signaling Formating + on the I2S Port. Each of the 3 8 bit values in the array contain the settings + for depth, location, and frame. + + If not used, the defaults for the 6 monitor signals is used. + + Sections 7.44 - 7.53 lists values for the depth, location, and frame + for each monitoring signal. + + - cirrus,imon : 4 8 bit values to set the depth, location, frame and ADC + scale of the IMON monitor signal. + + - cirrus,vmon : 3 8 bit values to set the depth, location, and frame + of the VMON monitor signal. + + - cirrus,vpmon : 3 8 bit values to set the depth, location, and frame + of the VPMON monitor signal. + + - cirrus,vbstmon : 3 8 bit values to set the depth, location, and frame + of the VBSTMON monitor signal + + - cirrus,vpbrstat : 3 8 bit values to set the depth, location, and frame + of the VPBRSTAT monitor signal + + - cirrus,zerofill : 3 8 bit values to set the depth, location, and frame\ + of the ZEROFILL packet in the monitor signal + +Example: + +cs35l35: cs35l35@20 { + compatible = "cirrus,cs35l35"; + reg = <0x20>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + reset-gpios = <&axi_gpio 54 0>; + interrupt-parent = <&gpio8>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + cirrus,boost-ctl-millivolt = <9000>; + + cirrus,stereo-config; + cirrus,audio-channel = <0x00>; + cirrus,advisory-channel = <0x01>; + cirrus,shared-boost; + + cirrus,classh-internal-algo { + cirrus,classh-bst-overide; + cirrus,classh-bst-max-limit = <0x01>; + cirrus,classh-mem-depth = <0x01>; + cirrus,classh-release-rate = <0x08>; + cirrus,classh-headroom-millivolt = <0x0B>; + cirrus,classh-wk-fet-disable = <0x01>; + cirrus,classh-wk-fet-delay = <0x04>; + cirrus,classh-wk-fet-thld = <0x01>; + cirrus,classh-vpch-auto = <0x01>; + cirrus,classh-vpch-rate = <0x02>; + cirrus,classh-vpch-man = <0x05>; + }; + + /* Depth, Location, Frame */ + cirrus,monitor-signal-format { + cirrus,imon = /bits/ 8 <0x03 0x00 0x01>; + cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>; + cirrus,vpmon = /bits/ 8 <0x03 0x04 0x00>; + cirrus,vbstmon = /bits/ 8 <0x03 0x04 0x01>; + cirrus,vpbrstat = /bits/ 8 <0x00 0x04 0x00>; + cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>; + }; + +}; diff --git a/Documentation/devicetree/bindings/sound/dioo,dio2125.txt b/Documentation/devicetree/bindings/sound/dioo,dio2125.txt new file mode 100644 index 000000000000..63dbfe0f11d0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/dioo,dio2125.txt @@ -0,0 +1,12 @@ +DIO2125 Audio Driver + +Required properties: +- compatible : "dioo,dio2125" +- enable-gpios : the gpio connected to the enable pin of the dio2125 + +Example: + +amp: analog-amplifier { + compatible = "dioo,dio2125"; + enable-gpios = <&gpio GPIOH_3 0>; +}; diff --git a/Documentation/devicetree/bindings/sound/everest,es7134.txt b/Documentation/devicetree/bindings/sound/everest,es7134.txt new file mode 100644 index 000000000000..5495a3cb8b7b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/everest,es7134.txt @@ -0,0 +1,10 @@ +ES7134 i2s DA converter + +Required properties: +- compatible : "everest,es7134" or "everest,es7144" + +Example: + +i2s_codec: external-codec { + compatible = "everest,es7134"; +}; diff --git a/Documentation/devicetree/bindings/sound/fsl,ssi.txt b/Documentation/devicetree/bindings/sound/fsl,ssi.txt index 5b76be45d18b..d415888e1316 100644 --- a/Documentation/devicetree/bindings/sound/fsl,ssi.txt +++ b/Documentation/devicetree/bindings/sound/fsl,ssi.txt @@ -20,24 +20,8 @@ Required properties: have. - interrupt-parent: The phandle for the interrupt controller that services interrupts for this device. -- fsl,playback-dma: Phandle to a node for the DMA channel to use for - playback of audio. This is typically dictated by SOC - design. See the notes below. -- fsl,capture-dma: Phandle to a node for the DMA channel to use for - capture (recording) of audio. This is typically dictated - by SOC design. See the notes below. - fsl,fifo-depth: The number of elements in the transmit and receive FIFOs. This number is the maximum allowed value for SFCSR[TFWM0]. -- fsl,ssi-asynchronous: - If specified, the SSI is to be programmed in asynchronous - mode. In this mode, pins SRCK, STCK, SRFS, and STFS must - all be connected to valid signals. In synchronous mode, - SRCK and SRFS are ignored. Asynchronous mode allows - playback and capture to use different sample sizes and - sample rates. Some drivers may require that SRCK and STCK - be connected together, and SRFS and STFS be connected - together. This would still allow different sample sizes, - but not different sample rates. - clocks: "ipg" - Required clock for the SSI unit "baud" - Required clock for SSI master mode. Otherwise this clock is not used @@ -61,6 +45,24 @@ Optional properties: - fsl,mode: The operating mode for the AC97 interface only. "ac97-slave" - AC97 mode, SSI is clock slave "ac97-master" - AC97 mode, SSI is clock master +- fsl,ssi-asynchronous: + If specified, the SSI is to be programmed in asynchronous + mode. In this mode, pins SRCK, STCK, SRFS, and STFS must + all be connected to valid signals. In synchronous mode, + SRCK and SRFS are ignored. Asynchronous mode allows + playback and capture to use different sample sizes and + sample rates. Some drivers may require that SRCK and STCK + be connected together, and SRFS and STFS be connected + together. This would still allow different sample sizes, + but not different sample rates. +- fsl,playback-dma: Phandle to a node for the DMA channel to use for + playback of audio. This is typically dictated by SOC + design. See the notes below. + Only used on Power Architecture. +- fsl,capture-dma: Phandle to a node for the DMA channel to use for + capture (recording) of audio. This is typically dictated + by SOC design. See the notes below. + Only used on Power Architecture. Child 'codec' node required properties: - compatible: Compatible list, contains the name of the codec diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt new file mode 100644 index 000000000000..7a296784eb37 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.txt @@ -0,0 +1,42 @@ +* Hisilicon 6210 i2s controller + +Required properties: + +- compatible: should be one of the following: + - "hisilicon,hi6210-i2s" +- reg: physical base address of the i2s controller unit and length of + memory mapped region. +- interrupts: should contain the i2s interrupt. +- clocks: a list of phandle + clock-specifier pairs, one for each entry + in clock-names. +- clock-names: should contain following: + - "dacodec" + - "i2s-base" +- dmas: DMA specifiers for tx dma. See the DMA client binding, + Documentation/devicetree/bindings/dma/dma.txt +- dma-names: should be "tx" and "rx" +- hisilicon,sysctrl-syscon: phandle to sysctrl syscon +- #sound-dai-cells: Should be set to 1 (for multi-dai) + - The dai cell indexes reference the following interfaces: + 0: S2 interface + (Currently that is the only one available, but more may be + supported in the future) + +Example for the hi6210 i2s controller: + +i2s0: i2s@f7118000{ + compatible = "hisilicon,hi6210-i2s"; + reg = <0x0 0xf7118000 0x0 0x8000>; /* i2s unit */ + interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; /* 155 "DigACodec_intr"-32 */ + clocks = <&sys_ctrl HI6220_DACODEC_PCLK>, + <&sys_ctrl HI6220_BBPPLL0_DIV>; + clock-names = "dacodec", "i2s-base"; + dmas = <&dma0 15 &dma0 14>; + dma-names = "rx", "tx"; + hisilicon,sysctrl-syscon = <&sys_ctrl>; + #sound-dai-cells = <1>; +}; + +Then when referencing the i2s controller: + sound-dai = <&i2s0 0>; /* index 0 => S2 interface */ + diff --git a/Documentation/devicetree/bindings/sound/max98925.txt b/Documentation/devicetree/bindings/sound/max98925.txt deleted file mode 100644 index 27be63e2aa0d..000000000000 --- a/Documentation/devicetree/bindings/sound/max98925.txt +++ /dev/null @@ -1,22 +0,0 @@ -max98925 audio CODEC - -This device supports I2C. - -Required properties: - - - compatible : "maxim,max98925" - - - vmon-slot-no : slot number used to send voltage information - - - imon-slot-no : slot number used to send current information - - - reg : the I2C address of the device for I2C - -Example: - -codec: max98925@1a { - compatible = "maxim,max98925"; - vmon-slot-no = <0>; - imon-slot-no = <2>; - reg = <0x1a>; -}; diff --git a/Documentation/devicetree/bindings/sound/max98926.txt b/Documentation/devicetree/bindings/sound/max98926.txt deleted file mode 100644 index 0b7f4e4d5f9a..000000000000 --- a/Documentation/devicetree/bindings/sound/max98926.txt +++ /dev/null @@ -1,32 +0,0 @@ -max98926 audio CODEC - -This device supports I2C. - -Required properties: - - - compatible : "maxim,max98926" - - - vmon-slot-no : slot number used to send voltage information - or in inteleave mode this will be used as - interleave slot. - - - imon-slot-no : slot number used to send current information - - - interleave-mode : When using two MAX98926 in a system it is - possible to create ADC data that that will - overflow the frame size. Digital Audio Interleave - mode provides a means to output VMON and IMON data - from two devices on a single DOUT line when running - smaller frames sizes such as 32 BCLKS per LRCLK or - 48 BCLKS per LRCLK. - - - reg : the I2C address of the device for I2C - -Example: - -codec: max98926@1a { - compatible = "maxim,max98926"; - vmon-slot-no = <0>; - imon-slot-no = <2>; - reg = <0x1a>; -}; diff --git a/Documentation/devicetree/bindings/sound/max9892x.txt b/Documentation/devicetree/bindings/sound/max9892x.txt new file mode 100644 index 000000000000..f6171591ddc6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/max9892x.txt @@ -0,0 +1,41 @@ +Maxim Integrated MAX98925/MAX98926/MAX98927 Speaker Amplifier + +This device supports I2C. + +Required properties: + + - compatible : should be one of the following + - "maxim,max98925" + - "maxim,max98926" + - "maxim,max98927" + + - vmon-slot-no : slot number used to send voltage information + or in inteleave mode this will be used as + interleave slot. + MAX98925/MAX98926 slot range : 0 ~ 30, Default : 0 + MAX98927 slot range : 0 ~ 15, Default : 0 + + - imon-slot-no : slot number used to send current information + MAX98925/MAX98926 slot range : 0 ~ 30, Default : 0 + MAX98927 slot range : 0 ~ 15, Default : 0 + + - interleave-mode : When using two MAX9892X in a system it is + possible to create ADC data that that will + overflow the frame size. Digital Audio Interleave + mode provides a means to output VMON and IMON data + from two devices on a single DOUT line when running + smaller frames sizes such as 32 BCLKS per LRCLK or + 48 BCLKS per LRCLK. + Range : 0 (off), 1 (on), Default : 0 + + - reg : the I2C address of the device for I2C + +Example: + +codec: max98927@3a { + compatible = "maxim,max98927"; + vmon-slot-no = <0>; + imon-slot-no = <1>; + interleave-mode = <0>; + reg = <0x3a>; +}; diff --git a/Documentation/devicetree/bindings/sound/mt2701-wm8960.txt b/Documentation/devicetree/bindings/sound/mt2701-wm8960.txt new file mode 100644 index 000000000000..809b609ea9d0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mt2701-wm8960.txt @@ -0,0 +1,24 @@ +MT2701 with WM8960 CODEC + +Required properties: +- compatible: "mediatek,mt2701-wm8960-machine" +- mediatek,platform: the phandle of MT2701 ASoC platform +- audio-routing: a list of the connections between audio +- mediatek,audio-codec: the phandles of wm8960 codec +- pinctrl-names: Should contain only one value - "default" +- pinctrl-0: Should specify pin control groups used for this controller. + +Example: + + sound:sound { + compatible = "mediatek,mt2701-wm8960-machine"; + mediatek,platform = <&afe>; + audio-routing = + "Headphone", "HP_L", + "Headphone", "HP_R", + "LINPUT1", "AMIC", + "RINPUT1", "AMIC"; + mediatek,audio-codec = <&wm8960>; + pinctrl-names = "default"; + pinctrl-0 = <&aud_pins_default>; + }; diff --git a/Documentation/devicetree/bindings/sound/nau8824.txt b/Documentation/devicetree/bindings/sound/nau8824.txt new file mode 100644 index 000000000000..e0058b97e49a --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nau8824.txt @@ -0,0 +1,88 @@ +Nuvoton NAU8824 audio codec + +This device supports I2C only. + +Required properties: + - compatible : Must be "nuvoton,nau8824" + + - reg : the I2C address of the device. This is either 0x1a (CSB=0) or 0x1b (CSB=1). + +Optional properties: + - nuvoton,jkdet-polarity: JKDET pin polarity. 0 - active high, 1 - active low. + + - nuvoton,vref-impedance: VREF Impedance selection + 0 - Open + 1 - 25 kOhm + 2 - 125 kOhm + 3 - 2.5 kOhm + + - nuvoton,micbias-voltage: Micbias voltage level. + 0 - VDDA + 1 - VDDA + 2 - VDDA * 1.1 + 3 - VDDA * 1.2 + 4 - VDDA * 1.3 + 5 - VDDA * 1.4 + 6 - VDDA * 1.53 + 7 - VDDA * 1.53 + + - nuvoton,sar-threshold-num: Number of buttons supported + - nuvoton,sar-threshold: Impedance threshold for each button. Array that contains up to 8 buttons configuration. SAR value is calculated as + SAR = 255 * MICBIAS / SAR_VOLTAGE * R / (2000 + R) + where MICBIAS is configured by 'nuvoton,micbias-voltage', SAR_VOLTAGE is configured by 'nuvoton,sar-voltage', R - button impedance. + Refer datasheet section 10.2 for more information about threshold calculation. + + - nuvoton,sar-hysteresis: Button impedance measurement hysteresis. + + - nuvoton,sar-voltage: Reference voltage for button impedance measurement. + 0 - VDDA + 1 - VDDA + 2 - VDDA * 1.1 + 3 - VDDA * 1.2 + 4 - VDDA * 1.3 + 5 - VDDA * 1.4 + 6 - VDDA * 1.53 + 7 - VDDA * 1.53 + + - nuvoton,sar-compare-time: SAR compare time + 0 - 500 ns + 1 - 1 us + 2 - 2 us + 3 - 4 us + + - nuvoton,sar-sampling-time: SAR sampling time + 0 - 2 us + 1 - 4 us + 2 - 8 us + 3 - 16 us + + - nuvoton,short-key-debounce: Button short key press debounce time. + 0 - 30 ms + 1 - 50 ms + 2 - 100 ms + + - nuvoton,jack-eject-debounce: Jack ejection debounce time. + 0 - 0 ms + 1 - 1 ms + 2 - 10 ms + + +Example: + + headset: nau8824@1a { + compatible = "nuvoton,nau8824"; + reg = <0x1a>; + interrupt-parent = <&gpio>; + interrupts = <TEGRA_GPIO(E, 6) IRQ_TYPE_LEVEL_LOW>; + nuvoton,vref-impedance = <2>; + nuvoton,micbias-voltage = <6>; + // Setup 4 buttons impedance according to Android specification + nuvoton,sar-threshold-num = <4>; + nuvoton,sar-threshold = <0xc 0x1e 0x38 0x60>; + nuvoton,sar-hysteresis = <0>; + nuvoton,sar-voltage = <6>; + nuvoton,sar-compare-time = <1>; + nuvoton,sar-sampling-time = <1>; + nuvoton,short-key-debounce = <0>; + nuvoton,jack-eject-debounce = <1>; + }; diff --git a/Documentation/devicetree/bindings/sound/rockchip-i2s.txt b/Documentation/devicetree/bindings/sound/rockchip-i2s.txt index a6600f6dea64..206aba1b34bb 100644 --- a/Documentation/devicetree/bindings/sound/rockchip-i2s.txt +++ b/Documentation/devicetree/bindings/sound/rockchip-i2s.txt @@ -9,6 +9,7 @@ Required properties: - "rockchip,rk3066-i2s": for rk3066 - "rockchip,rk3188-i2s", "rockchip,rk3066-i2s": for rk3188 - "rockchip,rk3288-i2s", "rockchip,rk3066-i2s": for rk3288 + - "rockchip,rk3368-i2s", "rockchip,rk3066-i2s": for rk3368 - "rockchip,rk3399-i2s", "rockchip,rk3066-i2s": for rk3399 - reg: physical base address of the controller and length of memory mapped region. diff --git a/Documentation/devicetree/bindings/sound/samsung,odroid.txt b/Documentation/devicetree/bindings/sound/samsung,odroid.txt new file mode 100644 index 000000000000..c1ac70cb0afb --- /dev/null +++ b/Documentation/devicetree/bindings/sound/samsung,odroid.txt @@ -0,0 +1,57 @@ +Samsung Exynos Odroid XU3/XU4 audio complex with MAX98090 codec + +Required properties: + + - compatible - "samsung,odroidxu3-audio" - for Odroid XU3 board, + "samsung,odroidxu4-audio" - for Odroid XU4 board + - model - the user-visible name of this sound complex + - 'cpu' subnode with a 'sound-dai' property containing the phandle of the I2S + controller + - 'codec' subnode with a 'sound-dai' property containing list of phandles + to the CODEC nodes, first entry must be corresponding to the MAX98090 + CODEC and the second entry must be the phandle of the HDMI IP block node + - clocks - should contain entries matching clock names in the clock-names + property + - clock-names - should contain following entries: + - "epll" - indicating the EPLL output clock + - "i2s_rclk" - indicating the RCLK (root) clock of the I2S0 controller + - samsung,audio-widgets - this property specifies off-codec audio elements + like headphones or speakers, for details see widgets.txt + - samsung,audio-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; + valid names for sources and sinks are the MAX98090's pins (as + documented in its binding), and the jacks on the board + + For Odroid X2: + "Headphone Jack", "Mic Jack", "DMIC" + + For Odroid U3, XU3: + "Headphone Jack", "Speakers" + + For Odroid XU4: + no entries + +Example: + +sound { + compatible = "samsung,odroidxu3-audio"; + samsung,cpu-dai = <&i2s0>; + samsung,codec-dai = <&max98090>; + model = "Odroid-XU3"; + samsung,audio-routing = + "Headphone Jack", "HPL", + "Headphone Jack", "HPR", + "IN1", "Mic Jack", + "Mic Jack", "MICBIAS"; + + clocks = <&clock CLK_FOUT_EPLL>, <&i2s0 CLK_I2S_RCLK_SRC>; + clock-names = "epll", "sclk_i2s"; + + cpu { + sound-dai = <&i2s0 0>; + }; + codec { + sound-dai = <&hdmi>, <&max98090>; + }; +}; diff --git a/Documentation/devicetree/bindings/sound/sgtl5000.txt b/Documentation/devicetree/bindings/sound/sgtl5000.txt index 5666da7b8605..7a73a9d62015 100644 --- a/Documentation/devicetree/bindings/sound/sgtl5000.txt +++ b/Documentation/devicetree/bindings/sound/sgtl5000.txt @@ -26,6 +26,15 @@ Optional properties: If this node is not mentioned or the value is unknown, then the value is set to 1.25V. +- lrclk-strength: the LRCLK pad strength. Possible values are: +0, 1, 2 and 3 as per the table below: + +VDDIO 1.8V 2.5V 3.3V +0 = Disable +1 = 1.66 mA 2.87 mA 4.02 mA +2 = 3.33 mA 5.74 mA 8.03 mA +3 = 4.99 mA 8.61 mA 12.05 mA + Example: codec: sgtl5000@0a { diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt new file mode 100644 index 000000000000..c59a3d779e06 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt @@ -0,0 +1,89 @@ +STMicroelectronics STM32 Serial Audio Interface (SAI). + +The SAI interface (Serial Audio Interface) offers a wide set of audio protocols +as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97. +The SAI contains two independent audio sub-blocks. Each sub-block has +its own clock generator and I/O lines controller. + +Required properties: + - compatible: Should be "st,stm32f4-sai" + - reg: Base address and size of SAI common register set. + - clocks: Must contain phandle and clock specifier pairs for each entry + in clock-names. + - clock-names: Must contain "x8k" and "x11k" + "x8k": SAI parent clock for sampling rates multiple of 8kHz. + "x11k": SAI parent clock for sampling rates multiple of 11.025kHz. + - interrupts: cpu DAI interrupt line shared by SAI sub-blocks + +Optional properties: + - resets: Reference to a reset controller asserting the SAI + +SAI subnodes: +Two subnodes corresponding to SAI sub-block instances A et B can be defined. +Subnode can be omitted for unsused sub-block. + +SAI subnodes required properties: + - compatible: Should be "st,stm32-sai-sub-a" or "st,stm32-sai-sub-b" + for SAI sub-block A or B respectively. + - reg: Base address and size of SAI sub-block register set. + - clocks: Must contain one phandle and clock specifier pair + for sai_ck which feeds the internal clock generator. + - clock-names: Must contain "sai_ck". + - dmas: see Documentation/devicetree/bindings/dma/stm32-dma.txt + - dma-names: identifier string for each DMA request line + "tx": if sai sub-block is configured as playback DAI + "rx": if sai sub-block is configured as capture DAI + - pinctrl-names: should contain only value "default" + - pinctrl-0: see Documentation/devicetree/bindings/pinctrl/pinctrl-stm32.txt + +Example: +sound_card { + compatible = "audio-graph-card"; + dais = <&sai1b_port>; +}; + +sai1: sai1@40015800 { + compatible = "st,stm32f4-sai"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + reg = <0x40015800 0x4>; + clocks = <&rcc 1 CLK_SAIQ_PDIV>, <&rcc 1 CLK_I2SQ_PDIV>; + clock-names = "x8k", "x11k"; + interrupts = <87>; + + sai1b: audio-controller@40015824 { + #sound-dai-cells = <0>; + compatible = "st,stm32-sai-sub-b"; + reg = <0x40015824 0x1C>; + clocks = <&rcc 1 CLK_SAI2>; + clock-names = "sai_ck"; + dmas = <&dma2 5 0 0x400 0x0>; + dma-names = "tx"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sai1b>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + sai1b_port: port@0 { + reg = <0>; + cpu_endpoint: endpoint { + remote-endpoint = <&codec_endpoint>; + audio-graph-card,format = "i2s"; + audio-graph-card,bitclock-master = <&codec_endpoint>; + audio-graph-card,frame-master = <&codec_endpoint>; + }; + }; + }; + }; +}; + +audio-codec { + codec_port: port { + codec_endpoint: endpoint { + remote-endpoint = <&cpu_endpoint>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/sound/tas2552.txt b/Documentation/devicetree/bindings/sound/tas2552.txt index c49992c0b62a..2d71eb05c1d3 100644 --- a/Documentation/devicetree/bindings/sound/tas2552.txt +++ b/Documentation/devicetree/bindings/sound/tas2552.txt @@ -5,7 +5,8 @@ The tas2552 serial control bus communicates through I2C protocols Required properties: - compatible - One of: "ti,tas2552" - TAS2552 - - reg - I2C slave address + - reg - I2C slave address: it can be 0x40 if ADDR pin is 0 + or 0x41 if ADDR pin is 1. - supply-*: Required supply regulators are: "vbat" battery voltage "iovdd" I/O Voltage @@ -14,17 +15,20 @@ Required properties: Optional properties: - enable-gpio - gpio pin to enable/disable the device -tas2552 can receive it's reference clock via MCLK, BCLK, IVCLKIN pin or use the +tas2552 can receive its reference clock via MCLK, BCLK, IVCLKIN pin or use the internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL, the PDM reference clock is also selectable: PLL, IVCLKIN, BCLK or MCLK. For system integration the dt-bindings/sound/tas2552.h header file provides -defined values to selct and configure the PLL and PDM reference clocks. +defined values to select and configure the PLL and PDM reference clocks. Example: tas2552: tas2552@41 { compatible = "ti,tas2552"; reg = <0x41>; + vbat-supply = <®_vbat>; + iovdd-supply = <®_iovdd>; + avdd-supply = <®_avdd>; enable-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>; }; diff --git a/Documentation/devicetree/bindings/sound/wm8903.txt b/Documentation/devicetree/bindings/sound/wm8903.txt index 94ec32c194bb..afc51caf1137 100644 --- a/Documentation/devicetree/bindings/sound/wm8903.txt +++ b/Documentation/devicetree/bindings/sound/wm8903.txt @@ -28,6 +28,14 @@ Optional properties: performed. If any entry has the value 0xffffffff, that GPIO's configuration will not be modified. + - AVDD-supply : Analog power supply regulator on the AVDD pin. + + - CPVDD-supply : Charge pump supply regulator on the CPVDD pin. + + - DBVDD-supply : Digital buffer supply regulator for the DBVDD pin. + + - DCVDD-supply : Digital core supply regulator for the DCVDD pin. + Pins on the device (for linking into audio routes): * IN1L @@ -54,6 +62,11 @@ codec: wm8903@1a { reg = <0x1a>; interrupts = < 347 >; + AVDD-supply = <&fooreg_a>; + CPVDD-supply = <&fooreg_b>; + DBVDD-supply = <&fooreg_c>; + DCVDC-supply = <&fooreg_d>; + gpio-controller; #gpio-cells = <2>; diff --git a/Documentation/devicetree/bindings/sound/zte,tdm.txt b/Documentation/devicetree/bindings/sound/zte,tdm.txt new file mode 100644 index 000000000000..2a07ca655264 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/zte,tdm.txt @@ -0,0 +1,30 @@ +ZTE TDM DAI driver + +Required properties: + +- compatible : should be one of the following. + * zte,zx296718-tdm +- reg : physical base address of the controller and length of memory mapped + region. +- clocks : Pairs of phandle and specifier referencing the controller's clocks. +- clock-names: "wclk" for the wclk. + "pclk" for the pclk. +-#clock-cells: should be 1. +- zte,tdm-dma-sysctrl : Reference to the sysctrl controller controlling + the dma. includes: + phandle of sysctrl. + register offset in sysctrl for control dma. + mask of the register that be written to sysctrl. + +Example: + + tdm: tdm@1487000 { + compatible = "zte,zx296718-tdm"; + reg = <0x01487000 0x1000>; + clocks = <&audiocrm AUDIO_TDM_WCLK>, <&audiocrm AUDIO_TDM_PCLK>; + clock-names = "wclk", "pclk"; + #clock-cells = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&tdm_global_pin>; + zte,tdm-dma-sysctrl = <&sysctrl 0x10c 4>; + }; diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index 9eeb9b468706..f59c3cdbfaf4 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -494,6 +494,8 @@ add_hp_mic (bool) hp_mic_detect (bool) enable/disable the hp/mic shared input for a single built-in mic case; default true +vmaster (bool) + enable/disable the virtual Master control; default true mixer_nid (int) specifies the widget NID of the analog-loopback mixer diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h new file mode 100644 index 000000000000..29da899e17e4 --- /dev/null +++ b/include/sound/cs35l35.h @@ -0,0 +1,108 @@ +/* + * linux/sound/cs35l35.h -- Platform data for CS35l35 + * + * Copyright (c) 2016 Cirrus Logic Inc. + * + * 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 __CS35L35_H +#define __CS35L35_H + +struct classh_cfg { + /* + * Class H Algorithm Control Variables + * You can either have it done + * automatically or you can adjust + * these variables for tuning + * + * if you do not enable the internal algorithm + * you will get a set of mixer controls for + * Class H tuning + * + * Section 4.3 of the datasheet + */ + bool classh_bst_override; + bool classh_algo_enable; + int classh_bst_max_limit; + int classh_mem_depth; + int classh_release_rate; + int classh_headroom; + int classh_wk_fet_disable; + int classh_wk_fet_delay; + int classh_wk_fet_thld; + int classh_vpch_auto; + int classh_vpch_rate; + int classh_vpch_man; +}; + +struct monitor_cfg { + /* + * Signal Monitor Data + * highly configurable signal monitoring + * data positioning and different types of + * monitoring data. + * + * Section 4.8.2 - 4.8.4 of the datasheet + */ + bool is_present; + bool imon_specs; + bool vmon_specs; + bool vpmon_specs; + bool vbstmon_specs; + bool vpbrstat_specs; + bool zerofill_specs; + u8 imon_dpth; + u8 imon_loc; + u8 imon_frm; + u8 imon_scale; + u8 vmon_dpth; + u8 vmon_loc; + u8 vmon_frm; + u8 vpmon_dpth; + u8 vpmon_loc; + u8 vpmon_frm; + u8 vbstmon_dpth; + u8 vbstmon_loc; + u8 vbstmon_frm; + u8 vpbrstat_dpth; + u8 vpbrstat_loc; + u8 vpbrstat_frm; + u8 zerofill_dpth; + u8 zerofill_loc; + u8 zerofill_frm; +}; + +struct cs35l35_platform_data { + + /* Stereo (2 Device) */ + bool stereo; + /* serial port drive strength */ + int sp_drv_str; + /* serial port drive in unused slots */ + int sp_drv_unused; + /* Boost Power Down with FET */ + bool bst_pdn_fet_on; + /* Boost Voltage : used if ClassH Algo Enabled */ + int bst_vctl; + /* Boost Converter Peak Current CTRL */ + int bst_ipk; + /* Amp Gain Zero Cross */ + bool gain_zc; + /* Audio Input Location */ + int aud_channel; + /* Advisory Input Location */ + int adv_channel; + /* Shared Boost for stereo */ + bool shared_bst; + /* Specifies this amp is using an external boost supply */ + bool ext_bst; + /* ClassH Algorithm */ + struct classh_cfg classh_algo; + /* Monitor Config */ + struct monitor_cfg mon_cfg; +}; + +#endif /* __CS35L35_H */ diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 0013063db7f2..15fc6daf9096 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_HSW_EM4 0x100c #define AZX_REG_HSW_EM5 0x1010 -/* Skylake/Broxton display HD-A controller Extended Mode registers */ -#define AZX_REG_SKL_EM4L 0x1040 +/* Skylake/Broxton vendor-specific registers */ +#define AZX_REG_VS_EM1 0x1000 +#define AZX_REG_VS_INRC 0x1004 +#define AZX_REG_VS_OUTRC 0x1008 +#define AZX_REG_VS_FIFOTRK 0x100C +#define AZX_REG_VS_FIFOTRK2 0x1010 +#define AZX_REG_VS_EM2 0x1030 +#define AZX_REG_VS_EM3L 0x1038 +#define AZX_REG_VS_EM3U 0x103C +#define AZX_REG_VS_EM4L 0x1040 +#define AZX_REG_VS_EM4U 0x1044 +#define AZX_REG_VS_LTRC 0x1048 +#define AZX_REG_VS_D0I3C 0x104A +#define AZX_REG_VS_PCE 0x104B +#define AZX_REG_VS_L2MAGC 0x1050 +#define AZX_REG_VS_L2LAHPT 0x1054 +#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 +#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 +#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 +#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 /* PCI space */ #define AZX_PCIREG_TCSEL 0x44 @@ -243,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_ML_LOUTPAY 0x20 #define AZX_REG_ML_LINPAY 0x30 -#define AZX_MLCTL_SPA (1<<16) -#define AZX_MLCTL_CPA 23 - +#define ML_LCTL_SCF_MASK 0xF +#define AZX_MLCTL_SPA (0x1 << 16) +#define AZX_MLCTL_CPA (0x1 << 23) +#define AZX_MLCTL_SPA_SHIFT 16 +#define AZX_MLCTL_CPA_SHIFT 23 /* registers for DMA Resume Capability Structure */ #define AZX_DRSM_CAP_ID 0x5 diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 56004ec8d441..96546b30e900 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus); /* * macros for easy use */ -#define _snd_hdac_chip_write(type, chip, reg, value) \ - ((chip)->io_ops->reg_write ## type(value, (chip)->remap_addr + (reg))) -#define _snd_hdac_chip_read(type, chip, reg) \ - ((chip)->io_ops->reg_read ## type((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writeb(chip, reg, value) \ + ((chip)->io_ops->reg_writeb(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readb(chip, reg) \ + ((chip)->io_ops->reg_readb((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writew(chip, reg, value) \ + ((chip)->io_ops->reg_writew(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readw(chip, reg) \ + ((chip)->io_ops->reg_readw((chip)->remap_addr + (reg))) +#define _snd_hdac_chip_writel(chip, reg, value) \ + ((chip)->io_ops->reg_writel(value, (chip)->remap_addr + (reg))) +#define _snd_hdac_chip_readl(chip, reg) \ + ((chip)->io_ops->reg_readl((chip)->remap_addr + (reg))) /* read/write a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_writel(chip, reg, value) \ - _snd_hdac_chip_write(l, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writel(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writew(chip, reg, value) \ - _snd_hdac_chip_write(w, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writew(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_writeb(chip, reg, value) \ - _snd_hdac_chip_write(b, chip, AZX_REG_ ## reg, value) + _snd_hdac_chip_writeb(chip, AZX_REG_ ## reg, value) #define snd_hdac_chip_readl(chip, reg) \ - _snd_hdac_chip_read(l, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readl(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readw(chip, reg) \ - _snd_hdac_chip_read(w, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readw(chip, AZX_REG_ ## reg) #define snd_hdac_chip_readb(chip, reg) \ - _snd_hdac_chip_read(b, chip, AZX_REG_ ## reg) + _snd_hdac_chip_readb(chip, AZX_REG_ ## reg) /* update a register, pass without AZX_REG_ prefix */ #define snd_hdac_chip_updatel(chip, reg, mask, val) \ diff --git a/include/sound/soc.h b/include/sound/soc.h index cdfb55f7aede..5170fd81e1fd 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -434,6 +434,8 @@ int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id, int source, unsigned int freq, int dir); int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); +int snd_soc_codec_set_jack(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, void *data); int snd_soc_register_card(struct snd_soc_card *card); int snd_soc_unregister_card(struct snd_soc_card *card); @@ -497,7 +499,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream); int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, unsigned int dai_fmt); +#ifdef CONFIG_DMI int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour); +#else +static inline int snd_soc_set_dmi_name(struct snd_soc_card *card, + const char *flavour) +{ + return 0; +} +#endif /* Utility functions to get clock rates from various things */ int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots); @@ -721,6 +731,7 @@ struct snd_soc_jack_gpio { /* private: */ struct snd_soc_jack *jack; struct delayed_work work; + struct notifier_block pm_notifier; struct gpio_desc *desc; void *data; @@ -812,7 +823,6 @@ struct snd_soc_component { unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */ unsigned int registered_as_component:1; - unsigned int auxiliary:1; /* for auxiliary component of the card */ unsigned int suspended:1; /* is in suspend PM state */ struct list_head list; @@ -913,6 +923,8 @@ struct snd_soc_codec_driver { int clk_id, int source, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); + int (*set_jack)(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, void *data); /* codec IO */ struct regmap *(*get_regmap)(struct device *); diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index be353a78c303..fd41697cb4d3 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -107,9 +107,11 @@ enum { SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ + SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ + SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6 + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE }; struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12fcc78..622900488bdc 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -10,6 +10,7 @@ #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c +#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479 struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message { __u32 message; /* Digi00x-specific message */ }; +struct snd_firewire_event_motu_notification { + unsigned int type; + __u32 message; /* MOTU-specific bits. */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; struct snd_firewire_event_efw_response efw_response; struct snd_firewire_event_digi00x_message digi00x_message; + struct snd_firewire_event_motu_notification motu_notification; }; @@ -65,7 +72,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 -/* RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_MOTU 7 +#define SNDRV_FIREWIRE_TYPE_FIREFACE 8 struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/core/timer.c b/sound/core/timer.c index 6d4fbc439246..2f836ca09860 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, struct timespec tstamp; int prev, append = 0; + memset(&r1, 0, sizeof(r1)); memset(&tstamp, 0, sizeof(tstamp)); spin_lock(&tu->qlock); if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | @@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, } if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) { - memset(&r1, 0, sizeof(r1)); r1.event = SNDRV_TIMER_EVENT_RESOLUTION; r1.tstamp = tstamp; r1.val = resolution; @@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) if (id.card < 0) { id.card = 0; } else { - if (id.card < 0) { - id.card = 0; + if (id.device < 0) { + id.device = 0; } else { - if (id.device < 0) { - id.device = 0; - } else { - if (id.subdevice < 0) { - id.subdevice = 0; - } else { - id.subdevice++; - } - } + if (id.subdevice < 0) + id.subdevice = 0; + else + id.subdevice++; } } list_for_each(p, &snd_timer_list) { diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c index 289f041706cd..f684fffd1397 100644 --- a/sound/drivers/vx/vx_core.c +++ b/sound/drivers/vx/vx_core.c @@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw, return NULL; chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL); - if (! chip) { - snd_printk(KERN_ERR "vx_core: no memory\n"); + if (! chip) return NULL; - } mutex_init(&chip->lock); chip->irq = -1; chip->hw = hw; diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 9f00696c4e4a..529d9f405fa9 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -140,4 +140,24 @@ config SND_FIREWIRE_TASCAM To compile this driver as a module, choose M here: the module will be called snd-firewire-tascam. +config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2 + * 828mk3 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + +config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for RME fireface series. + * Fireface 400 + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 0ee1fb115d88..1b98fa3fa3d4 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -13,3 +13,5 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h index 9c04faf206b2..ea0d486652c8 100644 --- a/sound/firewire/amdtp-stream-trace.h +++ b/sound/firewire/amdtp-stream-trace.h @@ -14,8 +14,8 @@ #include <linux/tracepoint.h> TRACE_EVENT(in_packet, - TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index), - TP_ARGS(s, cycles, cip_header, payload_quadlets, index), + TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_length, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, index), TP_STRUCT__entry( __field(unsigned int, second) __field(unsigned int, cycle) @@ -37,7 +37,7 @@ TRACE_EVENT(in_packet, __entry->dest = fw_parent_device(s->unit)->card->node_id; __entry->cip_header0 = cip_header[0]; __entry->cip_header1 = cip_header[1]; - __entry->payload_quadlets = payload_quadlets; + __entry->payload_quadlets = payload_length / 4; __entry->packet_index = s->packet_index; __entry->irq = !!in_interrupt(); __entry->index = index; @@ -101,6 +101,94 @@ TRACE_EVENT(out_packet, __entry->index) ); +TRACE_EVENT(in_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_quadlets, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + __entry->payload_quadlets = payload_quadlets; + __entry->data_blocks = data_blocks, + __entry->data_block_counter = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %3u %3u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(out_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_length, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + __entry->payload_quadlets = payload_length / 4; + __entry->data_blocks = data_blocks, + __entry->data_blocks = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + #endif #undef TRACE_INCLUDE_PATH diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 00060c4a9deb..9e6f54f8c45d 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -27,6 +27,7 @@ /* isochronous header parameters */ #define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 #define TAG_CIP 1 /* common isochronous packet header parameters */ @@ -37,6 +38,8 @@ #define CIP_SID_MASK 0x3f000000 #define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 #define CIP_DBC_MASK 0x000000ff #define CIP_FMT_SHIFT 24 #define CIP_FMT_MASK 0x3f000000 @@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { unsigned int multiplier = 1; + unsigned int header_size = 0; if (s->flags & CIP_JUMBO_PAYLOAD) multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + header_size = 8; - return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; + return header_size + + s->syt_interval * s->data_block_quadlets * 4 * multiplier; } EXPORT_SYMBOL(amdtp_stream_get_max_payload); @@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length, goto end; p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); - p.tag = TAG_CIP; + p.tag = s->tag; p.header_length = header_length; if (payload_length > 0) p.payload_length = payload_length; @@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s, static inline int queue_in_packet(struct amdtp_stream *s) { - return queue_packet(s, IN_PACKET_HEADER_SIZE, - amdtp_stream_get_max_payload(s)); + return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length); } -static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, +static int handle_out_packet(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; unsigned int syt; unsigned int data_blocks; - unsigned int payload_length; unsigned int pcm_frames; struct snd_pcm_substream *pcm; @@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, data_blocks = calculate_data_blocks(s, syt); pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | (syt & CIP_SYT_MASK)); - s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; trace_out_packet(s, cycle, buffer, payload_length, index); @@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, return 0; } +static int handle_out_packet_without_header(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = data_blocks * 4 * s->data_block_quadlets; + + trace_out_packet_without_header(s, cycle, payload_length, data_blocks, + index); + + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + static int handle_in_packet(struct amdtp_stream *s, - unsigned int payload_quadlets, unsigned int cycle, + unsigned int payload_length, unsigned int cycle, unsigned int index) { __be32 *buffer; u32 cip_header[2]; - unsigned int fmt, fdf, syt; + unsigned int sph, fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; unsigned int data_blocks; struct snd_pcm_substream *pcm; @@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s, cip_header[0] = be32_to_cpu(buffer[0]); cip_header[1] = be32_to_cpu(buffer[1]); - trace_in_packet(s, cycle, cip_header, payload_quadlets, index); + trace_in_packet(s, cycle, cip_header, payload_length, index); /* * This module supports 'Two-quadlet CIP header with SYT field'. * For convenience, also check FMT field is AM824 or not. */ - if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || - ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { dev_info_ratelimited(&s->unit->device, "Invalid CIP header for AMDTP: %08X:%08X\n", cip_header[0], cip_header[1]); @@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s, } /* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; - if (fmt != s->fmt) { + if (sph != s->sph || fmt != s->fmt) { dev_info_ratelimited(&s->unit->device, "Detect unexpected protocol: %08x %08x\n", cip_header[0], cip_header[1]); @@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s, /* Calculate data blocks */ fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; - if (payload_quadlets < 3 || + if (payload_length < 12 || (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { data_blocks = 0; } else { @@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s, if (s->flags & CIP_WRONG_DBS) data_block_quadlets = s->data_block_quadlets; - data_blocks = (payload_quadlets - 2) / data_block_quadlets; + data_blocks = (payload_length / 4 - 2) / + data_block_quadlets; } /* Check data block counter continuity */ @@ -561,6 +609,34 @@ end: return 0; } +static int handle_in_packet_without_header(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + buffer = s->buffer.packets[s->packet_index].buffer; + data_blocks = payload_quadlets / s->data_block_quadlets; + + trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks, + index); + + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + /* * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent @@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, for (i = 0; i < packets; ++i) { cycle = increment_cycle_count(cycle, 1); - if (handle_out_packet(s, cycle, i) < 0) { + if (s->handle_packet(s, 0, cycle, i) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; @@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, { struct amdtp_stream *s = private_data; unsigned int i, packets; - unsigned int payload_quadlets, max_payload_quadlets; + unsigned int payload_length, max_payload_length; __be32 *headers = header; u32 cycle; @@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, cycle = decrement_cycle_count(cycle, packets); /* For buffer-over-run prevention. */ - max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; + max_payload_length = s->max_payload_length; for (i = 0; i < packets; i++) { cycle = increment_cycle_count(cycle, 1); - /* The number of quadlets in this packet */ - payload_quadlets = - (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4; - if (payload_quadlets > max_payload_quadlets) { + /* The number of bytes in this packet */ + payload_length = + (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT); + if (payload_length > max_payload_length) { dev_err(&s->unit->device, - "Detect jumbo payload: %02x %02x\n", - payload_quadlets, max_payload_quadlets); + "Detect jumbo payload: %04x %04x\n", + payload_length, max_payload_length); break; } - if (handle_in_packet(s, payload_quadlets, cycle, i) < 0) + if (s->handle_packet(s, payload_length, cycle, i) < 0) break; } @@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, void *header, void *private_data) { struct amdtp_stream *s = private_data; + u32 cycle; + unsigned int packets; + + s->max_payload_length = amdtp_stream_get_max_payload(s); /* * For in-stream, first packet has come. @@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, s->callbacked = true; wake_up(&s->callback_wait); - if (s->direction == AMDTP_IN_STREAM) + cycle = compute_cycle_count(tstamp); + + if (s->direction == AMDTP_IN_STREAM) { + packets = header_length / IN_PACKET_HEADER_SIZE; + cycle = decrement_cycle_count(cycle, packets); context->callback.sc = in_stream_callback; - else + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_in_packet_without_header; + else + s->handle_packet = handle_in_packet; + } else { + packets = header_length / 4; + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); context->callback.sc = out_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_out_packet_without_header; + else + s->handle_packet = handle_out_packet; + } + + s->start_cycle = cycle; context->callback.sc(context, tstamp, header_length, header, s); } @@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) amdtp_stream_update(s); + if (s->flags & CIP_NO_HEADER) + s->tag = TAG_NO_CIP_HEADER; + else + s->tag = TAG_CIP; + s->packet_index = 0; do { if (s->direction == AMDTP_IN_STREAM) @@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) /* NOTE: TAG1 matches CIP. This just affects in stream. */ tag = FW_ISO_CONTEXT_MATCH_TAG1; - if (s->flags & CIP_EMPTY_WITH_TAG0) + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) tag |= FW_ISO_CONTEXT_MATCH_TAG0; s->callbacked = false; diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index c1bc7fad056e..7e8831722821 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -18,8 +18,8 @@ * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. - * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet - * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * The value of data_block_quadlets is used instead of reported value. * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is @@ -29,6 +29,9 @@ * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an * packet is larger than IEC 61883-6 defines. Current implementation * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -39,6 +42,8 @@ enum cip_flags { CIP_SKIP_DBC_ZERO_CHECK = 0x10, CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, }; /** @@ -101,11 +106,17 @@ struct amdtp_stream { struct fw_iso_context *context; struct iso_packets_buffer buffer; int packet_index; + int tag; + int (*handle_packet)(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index); + unsigned int max_payload_length; /* For CIP headers. */ unsigned int source_node_id_field; unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int sph; unsigned int fmt; unsigned int fdf; /* quirk: fixed interval of dbc between previos/current packets. */ @@ -130,6 +141,7 @@ struct amdtp_stream { /* To wait for first packet. */ bool callbacked; wait_queue_head_t callback_wait; + u32 start_cycle; /* For backends to process data blocks. */ void *protocol; diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c index 9402cc15dbc1..f9b4225dd86f 100644 --- a/sound/firewire/bebob/bebob_command.c +++ b/sound/firewire/bebob/bebob_command.c @@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; else if (buf[0] == 0x0a) /* REJECTED */ err = -EINVAL; - else if (err > 0) + else err = 0; kfree(buf); @@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(8)); - if (err > 0 && err < 9) + if (err < 0) + ; + else if (err < 9) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 256, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 11) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, 12, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(9) | BIT(10)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 16, buf, 16, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err >= 0) && (err < 8)) + if (err < 0) + ; + else if (err < 16) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(10)); - if ((err >= 0) && (err < 12)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c index b3cffd01a19f..a4688545339c 100644 --- a/sound/firewire/digi00x/amdtp-dot.c +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -28,6 +28,9 @@ */ #define MAX_MIDI_RX_BLOCKS 8 +/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */ +#define MAX_MIDI_PORTS 3 + /* * The double-oh-three algorithm was discovered by Robin Gareus and Damien * Zammit in 2012, with reverse-engineering for Digi 003 Rack. @@ -42,10 +45,8 @@ struct amdtp_dot { unsigned int pcm_channels; struct dot_state state; - unsigned int midi_ports; - /* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */ - struct snd_rawmidi_substream *midi[2]; - int midi_fifo_used[2]; + struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS]; + int midi_fifo_used[MAX_MIDI_PORTS]; int midi_fifo_limit; void (*transfer_samples)(struct amdtp_stream *s, @@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, return -EBUSY; /* - * A first data channel is for MIDI conformant data channel, the rest is - * Multi Bit Linear Audio data channel. + * A first data channel is for MIDI messages, the rest is Multi Bit + * Linear Audio data channel. */ err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); if (err < 0) @@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, p->pcm_channels = pcm_channels; - if (s->direction == AMDTP_IN_STREAM) - p->midi_ports = DOT_MIDI_IN_PORTS; - else - p->midi_ports = DOT_MIDI_OUT_PORTS; - /* * We do not know the actual MIDI FIFO size of most devices. Just * assume two bytes, i.e., one byte can be received over the bus while @@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, b = (u8 *)&buffer[0]; len = 0; - if (port < p->midi_ports && + if (port < MAX_MIDI_PORTS && midi_ratelimit_per_packet(s, port) && p->midi[port] != NULL) len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); if (len > 0) { - b[3] = (0x10 << port) | len; + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. + * - 0010b: physical MIDI port 2. + * - 1110b: console MIDI port. + */ + if (port == 2) + b[3] = 0xe0; + else if (port == 1) + b[3] = 0x20; + else + b[3] = 0x00; + b[3] |= len; midi_use_bytes(s, port, len); } else { b[1] = 0; @@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, for (f = 0; f < data_blocks; f++) { b = (u8 *)&buffer[0]; - port = b[3] >> 4; - len = b[3] & 0x0f; - if (port < p->midi_ports && p->midi[port] && len > 0) - snd_rawmidi_receive(p->midi[port], b + 1, len); + len = b[3] & 0x0f; + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. Use port 0. + * - 1110b: console MIDI port. Use port 2. + */ + if (b[3] >> 4 > 0) + port = 2; + else + port = 0; + + if (port < MAX_MIDI_PORTS && p->midi[port]) + snd_rawmidi_receive(p->midi[port], b + 1, len); + } buffer += s->data_block_quadlets; } @@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, { struct amdtp_dot *p = s->protocol; - if (port < p->midi_ports) + if (port < MAX_MIDI_PORTS) ACCESS_ONCE(p->midi[port]) = midi; } diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c index 915d2a21223e..7ab3d0810f6b 100644 --- a/sound/firewire/digi00x/digi00x-midi.c +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -8,7 +8,7 @@ #include "digi00x.h" -static int midi_phys_open(struct snd_rawmidi_substream *substream) +static int midi_open(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; int err; @@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream) return err; } -static int midi_phys_close(struct snd_rawmidi_substream *substream) +static int midi_close(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data; @@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream) return 0; } -static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - substream); + if (substream->rmidi->device == 0) + port = substream->number; else - amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, - NULL); - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - substream); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream); else - amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, - NULL); + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static int midi_ctl_open(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream) -{ - /* Do nothing. */ - return 0; -} - -static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - - snd_fw_async_midi_port_finish(&dg00x->out_control); - - return 0; -} - -static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream, - int up) +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) { struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; unsigned long flags; - spin_lock_irqsave(&dg00x->lock, flags); - - if (up) - dg00x->in_control = substream; + if (substream->rmidi->device == 0) + port = substream->number; else - dg00x->in_control = NULL; - - spin_unlock_irqrestore(&dg00x->lock, flags); -} - -static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream, - int up) -{ - struct snd_dg00x *dg00x = substream->rmidi->private_data; - unsigned long flags; + port = 2; spin_lock_irqsave(&dg00x->lock, flags); if (up) - snd_fw_async_midi_port_run(&dg00x->out_control, substream); + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL); spin_unlock_irqrestore(&dg00x->lock, flags); } -static void set_midi_substream_names(struct snd_dg00x *dg00x, - struct snd_rawmidi_str *str, - bool is_ctl) +static void set_substream_names(struct snd_dg00x *dg00x, + struct snd_rawmidi *rmidi, bool is_console) { struct snd_rawmidi_substream *subs; - - list_for_each_entry(subs, &str->substreams, list) { - if (!is_ctl) - snprintf(subs->name, sizeof(subs->name), - "%s MIDI %d", - dg00x->card->shortname, subs->number + 1); - else - /* This port is for asynchronous transaction. */ - snprintf(subs->name, sizeof(subs->name), - "%s control", - dg00x->card->shortname); + struct snd_rawmidi_str *str; + int i; + + for (i = 0; i < 2; ++i) { + str = &rmidi->streams[i]; + + list_for_each_entry(subs, &str->substreams, list) { + if (!is_console) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, + subs->number + 1); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s control", + dg00x->card->shortname); + } + } } } -int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports, + unsigned int in_ports, bool is_console) { - static const struct snd_rawmidi_ops phys_capture_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_capture_trigger, - }; - static const struct snd_rawmidi_ops phys_playback_ops = { - .open = midi_phys_open, - .close = midi_phys_close, - .trigger = midi_phys_playback_trigger, + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, }; - static const struct snd_rawmidi_ops ctl_capture_ops = { - .open = midi_ctl_open, - .close = midi_ctl_capture_close, - .trigger = midi_ctl_capture_trigger, + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, }; - static const struct snd_rawmidi_ops ctl_playback_ops = { - .open = midi_ctl_open, - .close = midi_ctl_playback_close, - .trigger = midi_ctl_playback_trigger, - }; - struct snd_rawmidi *rmidi[2]; - struct snd_rawmidi_str *str; - unsigned int i; + const char *label; + struct snd_rawmidi *rmidi; int err; /* Add physical midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0, - DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]); + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console, + out_ports, in_ports, &rmidi); if (err < 0) return err; + rmidi->private_data = dg00x; - snprintf(rmidi[0]->name, sizeof(rmidi[0]->name), - "%s MIDI", dg00x->card->shortname); - - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT, - &phys_capture_ops); - snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT, - &phys_playback_ops); + if (!is_console) + label = "%s control"; + else + label = "%s MIDI"; + snprintf(rmidi->name, sizeof(rmidi->name), label, + dg00x->card->shortname); - /* Add a pair of control midi ports. */ - err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1, - 1, 1, &rmidi[1]); - if (err < 0) - return err; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); - snprintf(rmidi[1]->name, sizeof(rmidi[1]->name), - "%s control", dg00x->card->shortname); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT, - &ctl_capture_ops); - snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT, - &ctl_playback_ops); + set_substream_names(dg00x, rmidi, is_console); - for (i = 0; i < ARRAY_SIZE(rmidi); i++) { - rmidi[i]->private_data = dg00x; + return 0; +} - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT]; - set_midi_substream_names(dg00x, str, i); +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + int err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; - str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; - set_midi_substream_names(dg00x, str, i); + /* Add physical midi ports. */ + err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, + false); + if (err < 0) + return err; - rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; - } + if (dg00x->is_console) + err = add_substream_pair(dg00x, 1, 1, true); - return 0; + return err; } diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c index 735d35640807..af9bc8504781 100644 --- a/sound/firewire/digi00x/digi00x-transaction.c +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -9,40 +9,6 @@ #include <sound/asound.h> #include "digi00x.h" -static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf) -{ - int bytes; - - buf[0] = 0x80; - bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2); - if (bytes >= 0) - buf[3] = 0xc0 | bytes; - - return bytes; -} - -static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf, - unsigned int length) -{ - struct snd_rawmidi_substream *substream; - unsigned int i; - unsigned int len; - u8 *b; - - substream = ACCESS_ONCE(dg00x->in_control); - if (substream == NULL) - return; - - length /= 4; - - for (i = 0; i < length; i++) { - b = (u8 *)&buf[i]; - len = b[3] & 0xf; - if (len > 0) - snd_rawmidi_receive(dg00x->in_control, b + 1, len); - } -} - static void handle_unknown_message(struct snd_dg00x *dg00x, unsigned long long offset, __be32 *buf) { @@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request, struct snd_dg00x *dg00x = callback_data; __be32 *buf = (__be32 *)data; + fw_send_response(card, request, RCODE_COMPLETE); + if (offset == dg00x->async_handler.offset) handle_unknown_message(dg00x, offset, buf); - else if (offset == dg00x->async_handler.offset + 4) - handle_midi_control(dg00x, buf, length); - - fw_send_response(card, request, RCODE_COMPLETE); } int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) { struct fw_device *device = fw_parent_device(dg00x->unit); __be32 data[2]; - int err; /* Unknown. 4bytes. */ data[0] = cpu_to_be32((device->card->node_id << 16) | (dg00x->async_handler.offset >> 32)); data[1] = cpu_to_be32(dg00x->async_handler.offset); - err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, - &data, sizeof(data), 0); - if (err < 0) - return err; - - /* Asynchronous transactions for MIDI control message. */ - data[0] = cpu_to_be32((device->card->node_id << 16) | - (dg00x->async_handler.offset >> 32)); - data[1] = cpu_to_be32(dg00x->async_handler.offset + 4); return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, &data, sizeof(data), 0); } +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + if (dg00x->async_handler.callback_data == NULL) + return; + + fw_core_remove_address_handler(&dg00x->async_handler); + + dg00x->async_handler.callback_data = NULL; +} + int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) { static const struct fw_address_region resp_register_region = { @@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) }; int err; - dg00x->async_handler.length = 12; + dg00x->async_handler.length = 4; dg00x->async_handler.address_callback = handle_message; dg00x->async_handler.callback_data = dg00x; @@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) err = snd_dg00x_transaction_reregister(dg00x); if (err < 0) - goto error; - - err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit, - DG00X_ADDR_BASE + DG00X_OFFSET_MMC, - 4, fill_midi_message); - if (err < 0) - goto error; + snd_dg00x_transaction_unregister(dg00x); return err; -error: - fw_core_remove_address_handler(&dg00x->async_handler); - dg00x->async_handler.callback_data = NULL; - return err; -} - -void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) -{ - if (dg00x->async_handler.callback_data == NULL) - return; - - snd_fw_async_midi_port_destroy(&dg00x->out_control); - fw_core_remove_address_handler(&dg00x->async_handler); - - dg00x->async_handler.callback_data = NULL; } diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index cc4776c6ded3..1f5e1d23f31a 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); MODULE_LICENSE("GPL v2"); #define VENDOR_DIGIDESIGN 0x00a07e -#define MODEL_DIGI00X 0x000002 +#define MODEL_CONSOLE 0x000001 +#define MODEL_RACK 0x000002 static int name_card(struct snd_dg00x *dg00x) { @@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit, spin_lock_init(&dg00x->lock); init_waitqueue_head(&dg00x->hwdep_wait); + dg00x->is_console = entry->model_id == MODEL_CONSOLE; + /* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); snd_fw_schedule_registration(unit, &dg00x->dwork); @@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = { .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID, .vendor_id = VENDOR_DIGIDESIGN, - .model_id = MODEL_DIGI00X, + .model_id = MODEL_CONSOLE, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .model_id = MODEL_RACK, }, {} }; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 9dc761bdacca..1275a50956c0 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -58,16 +58,15 @@ struct snd_dg00x { struct fw_address_handler async_handler; u32 msg; - /* For asynchronous MIDI controls. */ - struct snd_rawmidi_substream *in_control; - struct snd_fw_async_midi_port out_control; + /* Console models have additional MIDI ports for control surface. */ + bool is_console; }; #define DG00X_ADDR_BASE 0xffffe0000000ull #define DG00X_OFFSET_STREAMING_STATE 0x0000 #define DG00X_OFFSET_STREAMING_SET 0x0004 -#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008 +/* unknown but address in host space 0x0008 */ /* For LSB of the address 0x000c */ /* unknown 0x0010 */ #define DG00X_OFFSET_MESSAGE_ADDR 0x0014 diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index cce19768f43d..61dda828f767 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, /* do transaction and check buf[1-4] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, buf[3] = 0xff & subfunction; err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); - if (err >= 0 && err < 8) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000000..8f807284ba54 --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,3 @@ +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ + ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o +obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 000000000000..780da9deb2f0 --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,155 @@ +/* + * amdtp-ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "ff.h" + +struct amdtp_ff { + unsigned int pcm_channels; +}; + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_ff *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + p->pcm_channels = pcm_channels; + data_channels = pcm_channels; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_le32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = le32_to_cpu(buffer[c]) & 0xffffff00; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_le32(0x00000000); + buffer += s->data_block_quadlets; + } +} + +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, (__le32 *)buffer, data_blocks); + pcm_frames = 0; + } + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + return pcm_frames; +} + +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, + process_data_blocks, sizeof(struct amdtp_ff)); +} diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 000000000000..3ee04b054585 --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -0,0 +1,191 @@ +/* + * ff-hwdep.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "ff.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_ff *ff = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&ff->lock); + schedule(); + finish_wait(&ff->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ff->lock); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + unsigned int events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +static int hwdep_get_info(struct snd_ff *ff, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(ff->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_ff *ff = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(ff, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(ff); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(ff); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_ff_create_hwdep_devices(struct snd_ff *ff) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, ff->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; + hwdep->ops = hwdep_ops; + hwdep->private_data = ff; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 000000000000..29ee0a7365c3 --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,131 @@ +/* + * ff-midi.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + /* Initialize internal status. */ + ff->running_status[substream->number] = 0; + ff->rx_midi_error[substream->number] = false; + + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream; + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + cancel_work_sync(&ff->rx_midi_work[substream->number]); + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL; + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = + substream; + else + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL; + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_rawmidi_str *stream, + const char *const name) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + snprintf(substream->name, sizeof(substream->name), + "%s MIDI %d", name, substream->number + 1); + } +} + +int snd_ff_create_midi_devices(struct snd_ff *ff) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + int err; + + err = snd_rawmidi_new(ff->card, ff->card->driver, 0, + ff->spec->midi_out_ports, ff->spec->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", ff->card->shortname); + rmidi->private_data = ff; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 000000000000..93cee1978e8e --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,409 @@ +/* + * ff-pcm.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static inline unsigned int get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, amdtp_rate_table[i]); + t.max = max(t.max, amdtp_rate_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, amdtp_rate_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, + const unsigned int *pcm_channels) +{ + unsigned int mode; + unsigned int rate, channels; + int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + + channels = pcm_channels[mode]; + if (pcm_channels[mode] == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + + rate = amdtp_rate_table[i]; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->rate_min = min(hw->rate_min, rate); + hw->rate_max = max(hw->rate_max, rate); + } +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int pcm_init_hw_params(struct snd_ff *ff, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + const unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->tx_stream; + pcm_channels = ff->spec->pcm_capture_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->rx_stream; + pcm_channels = ff->spec->pcm_playback_channels; + } + + /* limit rates */ + limit_channels_and_rates(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_ff_add_pcm_hw_constraints(s, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + unsigned int rate; + enum snd_ff_clock_src src; + int i, err; + + err = snd_ff_stream_lock_try(ff); + if (err < 0) + return err; + + err = pcm_init_hw_params(ff, substream); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + + err = ff->spec->protocol->get_clock(ff, &rate, &src); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + + if (src != SND_FF_CLOCK_SRC_INTERNAL) { + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + /* + * The unit is configured at sampling frequency which packet + * streaming engine can't support. + */ + if (i >= CIP_SFC_COUNT) { + snd_ff_stream_lock_release(ff); + return -EIO; + } + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } else { + if (amdtp_stream_pcm_running(&ff->rx_stream) || + amdtp_stream_pcm_running(&ff->tx_stream)) { + rate = amdtp_rate_table[ff->rx_stream.sfc]; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + } + + snd_pcm_set_sync(substream); + + return 0; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + snd_ff_stream_lock_release(ff); + + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_ff_create_pcm_devices(struct snd_ff *ff) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = ff; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", ff->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 000000000000..69441d121f71 --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -0,0 +1,63 @@ +/* + * ff-proc.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./ff.h" + +static void proc_dump_clock_config(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_clock_config(ff, buffer); +} + +static void proc_dump_sync_status(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_sync_status(ff, buffer); +} + +static void add_node(struct snd_ff *ff, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(ff->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, ff, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_ff_proc_init(struct snd_ff *ff) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(ff->card, "firewire", + ff->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(ff, root, "clock-config", proc_dump_clock_config); + add_node(ff, root, "sync-status", proc_dump_sync_status); +} diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c new file mode 100644 index 000000000000..fcec6de80eeb --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-ff400.c @@ -0,0 +1,371 @@ +/* + * ff-protocol-ff400.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "ff.h" + +#define FF400_STF 0x000080100500ull +#define FF400_RX_PACKET_FORMAT 0x000080100504ull +#define FF400_ISOC_COMM_START 0x000080100508ull +#define FF400_TX_PACKET_FORMAT 0x00008010050cull +#define FF400_ISOC_COMM_STOP 0x000080100510ull +#define FF400_SYNC_STATUS 0x0000801c0000ull +#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ +#define FF400_CLOCK_CONFIG 0x0000801c0004ull + +#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull +#define FF400_MIDI_RX_PORT_0 0x000080180000ull +#define FF400_MIDI_RX_PORT_1 0x000080190000ull + +static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return err; + data = le32_to_cpu(reg); + + /* Calculate sampling rate. */ + switch ((data >> 1) & 0x03) { + case 0x01: + *rate = 32000; + break; + case 0x00: + *rate = 44100; + break; + case 0x03: + *rate = 48000; + break; + case 0x02: + default: + return -EIO; + } + + if (data & 0x08) + *rate *= 2; + else if (data & 0x10) + *rate *= 4; + + /* Calculate source of clock. */ + if (data & 0x01) { + *src = SND_FF_CLOCK_SRC_INTERNAL; + } else { + /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ + switch ((data >> 10) & 0x07) { + case 0x03: + *src = SND_FF_CLOCK_SRC_SPDIF; + break; + case 0x04: + *src = SND_FF_CLOCK_SRC_WORD; + break; + case 0x05: + *src = SND_FF_CLOCK_SRC_LTC; + break; + case 0x00: + default: + *src = SND_FF_CLOCK_SRC_ADAT; + break; + } + } + + return 0; +} + +static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) +{ + __le32 reg; + int i, err; + + /* Check whether the given value is supported or not. */ + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i == CIP_SFC_COUNT) + return -EINVAL; + + /* Set the number of data blocks transferred in a second. */ + reg = cpu_to_le32(rate); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_STF, ®, sizeof(reg), 0); + if (err < 0) + return err; + + msleep(100); + + /* + * Set isochronous channel and the number of quadlets of received + * packets. + */ + reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | + ff->rx_resources.channel); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* + * Set isochronous channel and the number of quadlets of transmitted + * packet. + */ + /* TODO: investigate the purpose of this 0x80. */ + reg = cpu_to_le32((0x80 << 24) | + (ff->tx_resources.channel << 5) | + (ff->tx_stream.data_block_quadlets)); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Allow to transmit packets. */ + reg = cpu_to_le32(0x00000001); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_START, ®, sizeof(reg), 0); +} + +static void ff400_finish_session(struct snd_ff *ff) +{ + __le32 reg; + + reg = cpu_to_le32(0x80000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); +} + +static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) +{ + __le32 *reg; + int i; + + reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL); + if (reg == NULL) + return -ENOMEM; + + if (enable) { + /* + * Each quadlet is corresponding to data channels in a data + * blocks in reverse order. Precisely, quadlets for available + * data channels should be enabled. Here, I take second best + * to fetch PCM frames from all of data channels regardless of + * stf. + */ + for (i = 0; i < 18; ++i) + reg[i] = cpu_to_le32(0x00000001); + } + + return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, + FF400_FETCH_PCM_FRAMES, reg, + sizeof(__le32) * 18, 0); +} + +static void ff400_dump_sync_status(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "External source detection:\n"); + + snd_iprintf(buffer, "Word Clock:"); + if ((data >> 24) & 0x20) { + if ((data >> 24) & 0x40) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "S/PDIF:"); + if ((data >> 16) & 0x10) { + if ((data >> 16) & 0x04) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "ADAT:"); + if ((data >> 8) & 0x04) { + if ((data >> 8) & 0x10) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "\nUsed external source:\n"); + + if (((data >> 22) & 0x07) == 0x07) { + snd_iprintf(buffer, "None\n"); + } else { + switch ((data >> 22) & 0x07) { + case 0x00: + snd_iprintf(buffer, "ADAT:"); + break; + case 0x03: + snd_iprintf(buffer, "S/PDIF:"); + break; + case 0x04: + snd_iprintf(buffer, "Word:"); + break; + case 0x07: + snd_iprintf(buffer, "Nothing:"); + break; + case 0x01: + case 0x02: + case 0x05: + case 0x06: + default: + snd_iprintf(buffer, "unknown:"); + break; + } + + if ((data >> 25) & 0x07) { + switch ((data >> 25) & 0x07) { + case 0x01: + snd_iprintf(buffer, "32000\n"); + break; + case 0x02: + snd_iprintf(buffer, "44100\n"); + break; + case 0x03: + snd_iprintf(buffer, "48000\n"); + break; + case 0x04: + snd_iprintf(buffer, "64000\n"); + break; + case 0x05: + snd_iprintf(buffer, "88200\n"); + break; + case 0x06: + snd_iprintf(buffer, "96000\n"); + break; + case 0x07: + snd_iprintf(buffer, "128000\n"); + break; + case 0x08: + snd_iprintf(buffer, "176400\n"); + break; + case 0x09: + snd_iprintf(buffer, "192000\n"); + break; + case 0x00: + snd_iprintf(buffer, "unknown\n"); + break; + } + } + } + + snd_iprintf(buffer, "Multiplied:"); + snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); +} + +static void ff400_dump_clock_config(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + unsigned int rate; + const char *src; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", + (data & 0x20) ? "Professional" : "Consumer", + (data & 0x40) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + ((data >> 8) & 0x20) ? "on" : "off"); + + snd_iprintf(buffer, "S/PDIF input interface: %s\n", + ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); + + switch ((data >> 1) & 0x03) { + case 0x01: + rate = 32000; + break; + case 0x00: + rate = 44100; + break; + case 0x03: + rate = 48000; + break; + case 0x02: + default: + return; + } + + if (data & 0x08) + rate *= 2; + else if (data & 0x10) + rate *= 4; + + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (data & 0x01) { + src = "Internal"; + } else { + switch ((data >> 10) & 0x07) { + case 0x00: + src = "ADAT"; + break; + case 0x03: + src = "S/PDIF"; + break; + case 0x04: + src = "Word"; + break; + case 0x05: + src = "LTC"; + break; + default: + return; + } + } + + snd_iprintf(buffer, "Sync to clock source: %s\n", src); +} + +struct snd_ff_protocol snd_ff_protocol_ff400 = { + .get_clock = ff400_get_clock, + .begin_session = ff400_begin_session, + .finish_session = ff400_finish_session, + .switch_fetching_mode = ff400_switch_fetching_mode, + + .dump_sync_status = ff400_dump_sync_status, + .dump_clock_config = ff400_dump_clock_config, + + .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, + .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, + .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, +}; diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000000..78880922120e --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,282 @@ +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define CALLBACK_TIMEOUT_MS 200 + +static int get_rate_mode(unsigned int rate, unsigned int *mode) +{ + int i; + + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + + if (i == CIP_SFC_COUNT) + return -EINVAL; + + *mode = ((int)i - 1) / 2; + + return 0; +} + +/* + * Fireface 400 manages isochronous channel number in 3 bit field. Therefore, + * we can allocate between 0 and 7 channel. + */ +static int keep_resources(struct snd_ff *ff, unsigned int rate) +{ + int mode; + int err; + + err = get_rate_mode(rate, &mode); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + ff->tx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->tx_resources, + amdtp_stream_get_max_payload(&ff->tx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for out-stream. */ + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + ff->rx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->rx_resources, + amdtp_stream_get_max_payload(&ff->rx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + fw_iso_resources_free(&ff->tx_resources); + + return err; +} + +static void release_resources(struct snd_ff *ff) +{ + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); +} + +static inline void finish_session(struct snd_ff *ff) +{ + ff->spec->protocol->finish_session(ff); + ff->spec->protocol->switch_fetching_mode(ff, false); +} + +static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + int err; + struct fw_iso_resources *resources; + struct amdtp_stream *stream; + + if (dir == AMDTP_IN_STREAM) { + resources = &ff->tx_resources; + stream = &ff->tx_stream; + } else { + resources = &ff->rx_resources; + stream = &ff->rx_stream; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(stream, ff->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + if (dir == AMDTP_IN_STREAM) { + amdtp_stream_destroy(&ff->tx_stream); + fw_iso_resources_destroy(&ff->tx_resources); + } else { + amdtp_stream_destroy(&ff->rx_stream); + fw_iso_resources_destroy(&ff->rx_resources); + } +} + +int snd_ff_stream_init_duplex(struct snd_ff *ff) +{ + int err; + + err = init_stream(ff, AMDTP_OUT_STREAM); + if (err < 0) + goto end; + + err = init_stream(ff, AMDTP_IN_STREAM); + if (err < 0) + destroy_stream(ff, AMDTP_OUT_STREAM); +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + destroy_stream(ff, AMDTP_IN_STREAM); + destroy_stream(ff, AMDTP_OUT_STREAM); +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + if (ff->substreams_counter == 0) + return 0; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + finish_session(ff); + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + release_resources(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + err = keep_resources(ff, rate); + if (err < 0) + goto error; + + err = ff->spec->protocol->begin_session(ff, rate); + if (err < 0) + goto error; + + err = amdtp_stream_start(&ff->rx_stream, + ff->rx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; + } + + if (!amdtp_stream_running(&ff->tx_stream)) { + err = amdtp_stream_start(&ff->tx_stream, + ff->tx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + finish_session(ff); + release_resources(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter > 0) + return; + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + finish_session(ff); + release_resources(ff); +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + /* The device discontinue to transfer packets. */ + amdtp_stream_pcm_abort(&ff->tx_stream); + amdtp_stream_stop(&ff->tx_stream); + + amdtp_stream_pcm_abort(&ff->rx_stream); + amdtp_stream_stop(&ff->rx_stream); + + fw_iso_resources_update(&ff->tx_resources); + fw_iso_resources_update(&ff->rx_resources); +} + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000000..dd6c8e839647 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,295 @@ +/* + * ff-transaction.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, + int rcode) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + + if (rcode_is_permanent_error(rcode)) { + ff->rx_midi_error[port] = true; + return; + } + + if (rcode != RCODE_COMPLETE) { + /* Transfer the message again, immediately. */ + ff->next_ktime[port] = 0; + schedule_work(&ff->rx_midi_work[port]); + return; + } + + snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]); + ff->rx_bytes[port] = 0; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&ff->rx_midi_work[port]); +} + +static void finish_transmit_midi0_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[0]); + finish_transmit_midi_msg(ff, 0, rcode); +} + +static void finish_transmit_midi1_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[1]); + finish_transmit_midi_msg(ff, 1, rcode); +} + +static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port, + unsigned int index, u8 byte) +{ + ff->msg_buf[port][index] = cpu_to_le32(byte); +} + +static void transmit_midi_msg(struct snd_ff *ff, unsigned int port) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + u8 *buf = (u8 *)ff->msg_buf[port]; + int i, len; + + struct fw_device *fw_dev = fw_parent_device(ff->unit); + unsigned long long addr; + int generation; + fw_transaction_callback_t callback; + + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port]) + return; + + /* Do it in next chance. */ + if (ktime_after(ff->next_ktime[port], ktime_get())) { + schedule_work(&ff->rx_midi_work[port]); + return; + } + + len = snd_rawmidi_transmit_peek(substream, buf, + SND_FF_MAXIMIM_MIDI_QUADS); + if (len <= 0) + return; + + for (i = len - 1; i >= 0; i--) + fill_midi_buf(ff, port, i, buf[i]); + + if (port == 0) { + addr = ff->spec->protocol->midi_rx_port_0_reg; + callback = finish_transmit_midi0_msg; + } else { + addr = ff->spec->protocol->midi_rx_port_1_reg; + callback = finish_transmit_midi1_msg; + } + + /* Set interval to next transaction. */ + ff->next_ktime[port] = ktime_add_ns(ktime_get(), + len * 8 * NSEC_PER_SEC / 31250); + ff->rx_bytes[port] = len; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = fw_dev->generation; + smp_rmb(); + fw_send_request(fw_dev->card, &ff->transactions[port], + TCODE_WRITE_BLOCK_REQUEST, + fw_dev->node_id, generation, fw_dev->max_speed, + addr, &ff->msg_buf[port], len * 4, + callback, &ff->transactions[port]); +} + +static void transmit_midi0_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]); + + transmit_midi_msg(ff, 0); +} + +static void transmit_midi1_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]); + + transmit_midi_msg(ff, 1); +} + +static void handle_midi_msg(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_ff *ff = callback_data; + __le32 *buf = data; + u32 quad; + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + int i; + + fw_send_response(card, request, RCODE_COMPLETE); + + for (i = 0; i < length / 4; i++) { + quad = le32_to_cpu(buf[i]); + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +static int allocate_own_address(struct snd_ff *ff, int i) +{ + struct fw_address_region midi_msg_region; + int err; + + ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4; + ff->async_handler.address_callback = handle_midi_msg; + ff->async_handler.callback_data = ff; + + midi_msg_region.start = 0x000100000000ull * i; + midi_msg_region.end = midi_msg_region.start + ff->async_handler.length; + + err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region); + if (err >= 0) { + /* Controllers are allowed to register this region. */ + if (ff->async_handler.offset & 0x0000ffffffff) { + fw_core_remove_address_handler(&ff->async_handler); + err = -EAGAIN; + } + } + + return err; +} + +/* + * The configuration to start asynchronous transactions for MIDI messages is in + * 0x'0000'8010'051c. This register includes the other options, thus this driver + * doesn't touch it and leaves the decision to userspace. The userspace MUST add + * 0x04000000 to write transactions to the register to receive any MIDI + * messages. + * + * Here, I just describe MIDI-related offsets of the register, in little-endian + * order. + * + * Controllers are allowed to register higher 4 bytes of address to receive + * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the + * controllers are not allowed to register lower 4 bytes of the address. They + * are forced to select from 4 options by writing corresponding bits to + * 0x'0000'8010'051c. + * + * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes + * of address to which the device transferrs the transactions. + * - 6th: 0x'....'....'0000'0180 + * - 5th: 0x'....'....'0000'0100 + * - 4th: 0x'....'....'0000'0080 + * - 3rd: 0x'....'....'0000'0000 + * + * This driver configure 0x'....'....'0000'0000 for units to receive MIDI + * messages. 3rd bit of the register should be configured, however this driver + * deligates this task to user space applications due to a restriction that + * this register is write-only and the other bits have own effects. + * + * The 1st and 2nd bits in LSB of this register are used to cancel transferring + * asynchronous transactions. These two bits have the same effect. + * - 1st/2nd: cancel transferring + */ +int snd_ff_transaction_reregister(struct snd_ff *ff) +{ + struct fw_card *fw_card = fw_parent_device(ff->unit)->card; + u32 addr; + __le32 reg; + + /* + * Controllers are allowed to register its node ID and upper 2 byte of + * local address to listen asynchronous transactions. + */ + addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32); + reg = cpu_to_le32(addr); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); +} + +int snd_ff_transaction_register(struct snd_ff *ff) +{ + int i, err; + + /* + * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should + * be zero due to device specification. + */ + for (i = 0; i < 0xffff; i++) { + err = allocate_own_address(ff, i); + if (err != -EBUSY && err != -EAGAIN) + break; + } + if (err < 0) + return err; + + err = snd_ff_transaction_reregister(ff); + if (err < 0) + return err; + + INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg); + INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg); + + return 0; +} + +void snd_ff_transaction_unregister(struct snd_ff *ff) +{ + __le32 reg; + + if (ff->async_handler.callback_data == NULL) + return; + ff->async_handler.callback_data = NULL; + + /* Release higher 4 bytes of address. */ + reg = cpu_to_le32(0x00000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 000000000000..eee7c8eac7a6 --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,209 @@ +/* + * ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define OUI_RME 0x000a35 + +MODULE_DESCRIPTION("RME Fireface series Driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, ff->spec->name); + strcpy(ff->card->mixername, ff->spec->name); + snprintf(ff->card->longname, sizeof(ff->card->longname), + "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&ff->unit->device), 100 << fw_dev->max_speed); +} + +static void ff_free(struct snd_ff *ff) +{ + snd_ff_stream_destroy_duplex(ff); + snd_ff_transaction_unregister(ff); + + fw_unit_put(ff->unit); + + mutex_destroy(&ff->mutex); + kfree(ff); +} + +static void ff_card_free(struct snd_card *card) +{ + ff_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + + err = snd_ff_transaction_register(ff); + if (err < 0) + goto error; + + name_card(ff); + + err = snd_ff_stream_init_duplex(ff); + if (err < 0) + goto error; + + snd_ff_proc_init(ff); + + err = snd_ff_create_midi_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_pcm_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_hwdep_devices(ff); + if (err < 0) + goto error; + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + ff->registered = true; + + return; +error: + snd_ff_transaction_unregister(ff); + snd_ff_stream_destroy_duplex(ff); + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_ff *ff; + + ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL); + if (ff == NULL) + return -ENOMEM; + + /* initialize myself */ + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait); + + ff->spec = (const struct snd_ff_spec *)entry->driver_data; + + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork); + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); + } else { + /* Don't forget this case. */ + ff_free(ff); + } +} + +static struct snd_ff_spec spec_ff400 = { + .name = "Fireface400", + .pcm_capture_channels = {18, 14, 10}, + .pcm_playback_channels = {18, 14, 10}, + .midi_in_ports = 2, + .midi_out_ports = 2, + .protocol = &snd_ff_protocol_ff400, +}; + +static const struct ieee1394_device_id snd_ff_id_table[] = { + /* Fireface 400 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_RME, + .specifier_id = 0x000a35, + .version = 0x000002, + .model_id = 0x101800, + .driver_data = (kernel_ulong_t)&spec_ff400, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); + +static struct fw_driver ff_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireface", + .bus = &fw_bus_type, + }, + .probe = snd_ff_probe, + .update = snd_ff_update, + .remove = snd_ff_remove, + .id_table = snd_ff_id_table, +}; + +static int __init snd_ff_init(void) +{ + return driver_register(&ff_driver.driver); +} + +static void __exit snd_ff_exit(void) +{ + driver_unregister(&ff_driver.driver); +} + +module_init(snd_ff_init); +module_exit(snd_ff_exit); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 000000000000..3cb812a50030 --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,146 @@ +/* + * ff.h - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREFACE_H_INCLUDED +#define SOUND_FIREFACE_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/hwdep.h> +#include <sound/firewire.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +#define SND_FF_STREAM_MODES 3 + +#define SND_FF_MAXIMIM_MIDI_QUADS 9 +#define SND_FF_IN_MIDI_PORTS 2 +#define SND_FF_OUT_MIDI_PORTS 2 + +struct snd_ff_protocol; +struct snd_ff_spec { + const char *const name; + + const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES]; + const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES]; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + struct snd_ff_protocol *protocol; +}; + +struct snd_ff { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + const struct snd_ff_spec *spec; + + /* To handle MIDI tx. */ + struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS]; + struct fw_address_handler async_handler; + + /* TO handle MIDI rx. */ + struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS]; + u8 running_status[SND_FF_OUT_MIDI_PORTS]; + __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS]; + struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS]; + struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS]; + ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; + bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; + unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; + + unsigned int substreams_counter; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_ff_clock_src { + SND_FF_CLOCK_SRC_INTERNAL, + SND_FF_CLOCK_SRC_SPDIF, + SND_FF_CLOCK_SRC_ADAT, + SND_FF_CLOCK_SRC_WORD, + SND_FF_CLOCK_SRC_LTC, + /* TODO: perhaps ADAT2 and TCO exists. */ +}; + +struct snd_ff_protocol { + int (*get_clock)(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src); + int (*begin_session)(struct snd_ff *ff, unsigned int rate); + void (*finish_session)(struct snd_ff *ff); + int (*switch_fetching_mode)(struct snd_ff *ff, bool enable); + + void (*dump_sync_status)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + void (*dump_clock_config)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + + u64 midi_high_addr_reg; + u64 midi_rx_port_0_reg; + u64 midi_rx_port_1_reg; +}; + +extern struct snd_ff_protocol snd_ff_protocol_ff400; + +int snd_ff_transaction_register(struct snd_ff *ff); +int snd_ff_transaction_reregister(struct snd_ff *ff); +void snd_ff_transaction_unregister(struct snd_ff *ff); + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); + +int snd_ff_stream_init_duplex(struct snd_ff *ff); +void snd_ff_stream_destroy_duplex(struct snd_ff *ff); +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); +void snd_ff_stream_stop_duplex(struct snd_ff *ff); +void snd_ff_stream_update_duplex(struct snd_ff *ff); + +void snd_ff_stream_lock_changed(struct snd_ff *ff); +int snd_ff_stream_lock_try(struct snd_ff *ff); +void snd_ff_stream_lock_release(struct snd_ff *ff); + +void snd_ff_proc_init(struct snd_ff *ff); + +int snd_ff_create_midi_devices(struct snd_ff *ff); + +int snd_ff_create_pcm_devices(struct snd_ff *ff); + +int snd_ff_create_hwdep_devices(struct snd_ff *ff); + +#endif diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 7683238283b6..39dfa74906ef 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit, } EXPORT_SYMBOL(snd_fw_schedule_registration); -static void async_midi_port_callback(struct fw_card *card, int rcode, - void *data, size_t length, - void *callback_data) -{ - struct snd_fw_async_midi_port *port = callback_data; - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - - /* This port is closed. */ - if (substream == NULL) - return; - - if (rcode == RCODE_COMPLETE) - snd_rawmidi_transmit_ack(substream, port->consume_bytes); - else if (!rcode_is_permanent_error(rcode)) - /* To start next transaction immediately for recovery. */ - port->next_ktime = 0; - else - /* Don't continue processing. */ - port->error = true; - - port->idling = true; - - if (!snd_rawmidi_transmit_empty(substream)) - schedule_work(&port->work); -} - -static void midi_port_work(struct work_struct *work) -{ - struct snd_fw_async_midi_port *port = - container_of(work, struct snd_fw_async_midi_port, work); - struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); - int generation; - int type; - - /* Under transacting or error state. */ - if (!port->idling || port->error) - return; - - /* Nothing to do. */ - if (substream == NULL || snd_rawmidi_transmit_empty(substream)) - return; - - /* Do it in next chance. */ - if (ktime_after(port->next_ktime, ktime_get())) { - schedule_work(&port->work); - return; - } - - /* - * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). - * Later, snd_rawmidi_transmit_ack() is called. - */ - memset(port->buf, 0, port->len); - port->consume_bytes = port->fill(substream, port->buf); - if (port->consume_bytes <= 0) { - /* Do it in next chance, immediately. */ - if (port->consume_bytes == 0) { - port->next_ktime = 0; - schedule_work(&port->work); - } else { - /* Fatal error. */ - port->error = true; - } - return; - } - - /* Calculate type of transaction. */ - if (port->len == 4) - type = TCODE_WRITE_QUADLET_REQUEST; - else - type = TCODE_WRITE_BLOCK_REQUEST; - - /* Set interval to next transaction. */ - port->next_ktime = ktime_add_ns(ktime_get(), - port->consume_bytes * 8 * NSEC_PER_SEC / 31250); - - /* Start this transaction. */ - port->idling = false; - - /* - * In Linux FireWire core, when generation is updated with memory - * barrier, node id has already been updated. In this module, After - * this smp_rmb(), load/store instructions to memory are completed. - * Thus, both of generation and node id are available with recent - * values. This is a light-serialization solution to handle bus reset - * events on IEEE 1394 bus. - */ - generation = port->parent->generation; - smp_rmb(); - - fw_send_request(port->parent->card, &port->transaction, type, - port->parent->node_id, generation, - port->parent->max_speed, port->addr, - port->buf, port->len, async_midi_port_callback, - port); -} - -/** - * snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure - * @port: the asynchronous MIDI port to initialize - * @unit: the target of the asynchronous transaction - * @addr: the address to which transactions are transferred - * @len: the length of transaction - * @fill: the callback function to fill given buffer, and returns the - * number of consumed bytes for MIDI message. - * - */ -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill) -{ - port->len = DIV_ROUND_UP(len, 4) * 4; - port->buf = kzalloc(port->len, GFP_KERNEL); - if (port->buf == NULL) - return -ENOMEM; - - port->parent = fw_parent_device(unit); - port->addr = addr; - port->fill = fill; - port->idling = true; - port->next_ktime = 0; - port->error = false; - - INIT_WORK(&port->work, midi_port_work); - - return 0; -} -EXPORT_SYMBOL(snd_fw_async_midi_port_init); - -/** - * snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure - * @port: the asynchronous MIDI port structure - */ -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) -{ - snd_fw_async_midi_port_finish(port); - cancel_work_sync(&port->work); - kfree(port->buf); -} -EXPORT_SYMBOL(snd_fw_async_midi_port_destroy); - MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index c3768cd494a5..eef70922ed89 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode) void snd_fw_schedule_registration(struct fw_unit *unit, struct delayed_work *dwork); -struct snd_fw_async_midi_port; -typedef int (*snd_fw_async_midi_port_fill)( - struct snd_rawmidi_substream *substream, - u8 *buf); - -struct snd_fw_async_midi_port { - struct fw_device *parent; - struct work_struct work; - bool idling; - ktime_t next_ktime; - bool error; - - u64 addr; - struct fw_transaction transaction; - - u8 *buf; - unsigned int len; - - struct snd_rawmidi_substream *substream; - snd_fw_async_midi_port_fill fill; - int consume_bytes; -}; - -int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, - struct fw_unit *unit, u64 addr, unsigned int len, - snd_fw_async_midi_port_fill fill); -void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); - -/** - * snd_fw_async_midi_port_run - run transactions for the async MIDI port - * @port: the asynchronous MIDI port - * @substream: the MIDI substream - */ -static inline void -snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, - struct snd_rawmidi_substream *substream) -{ - if (!port->error) { - port->substream = substream; - schedule_work(&port->work); - } -} - -/** - * snd_fw_async_midi_port_finish - finish the asynchronous MIDI port - * @port: the asynchronous MIDI port - */ -static inline void -snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) -{ - port->substream = NULL; - port->error = false; -} - #endif diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000000..728f586e754b --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,6 @@ +CFLAGS_amdtp-motu.o := -I$(src) + +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o motu-protocol-v3.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000000..cd0cbfa9f96f --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,123 @@ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include <linux/tracepoint.h> + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(in_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(out_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(in_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +TRACE_EVENT(out_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include <trace/define_trace.h> diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000000..96f0091144bb --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,427 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + +#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 +#define MOTU_FDF_AM824 0x22 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + pcm_chunks = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + + /* + * Each data block includes SPH in its head. Data chunks follow with + * 3 byte alignment. Padding follows with zero to conform to quadlet + * alignment. + */ + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + channels = p->pcm_chunks; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | (byte[1] << 16) | byte[2]; + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + channels = p->pcm_chunks; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* This is just for v2/v3 protocol. */ + for (i = 0; i < data_blocks; ++i) { + *frames = (be32_to_cpu(buffer[1]) << 16) | + (be32_to_cpu(buffer[2]) >> 16); + buffer += data_block_quadlets; + frames++; + } +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = s->protocol; + struct snd_pcm_substream *pcm; + + trace_in_data_block_sph(s, data_blocks, buffer); + trace_in_data_block_message(s, data_blocks, buffer); + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + write_sph(s, buffer, data_blocks); + + trace_out_data_block_sph(s, data_blocks, buffer); + trace_out_data_block_message(s, data_blocks, buffer); + + return data_blocks; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_data_blocks = process_tx_data_blocks; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (protocol == &snd_motu_protocol_v3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } + } else { + process_data_blocks = process_rx_data_blocks; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + s->fdf = MOTU_FDF_AM824; + + return 0; +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000000..b87ccb69d597 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,198 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed && motu->msg == 0) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + unsigned int events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed || motu->msg) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | POLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000000..e3acfcc53f4e --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,169 @@ +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->capture_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->playback_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->capture_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->playback_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000000..94558f3d218b --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,398 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm_params.h> +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + hw->info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_BLOCK_TRANSFER; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + limit_period_and_buffer(hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + enum snd_motu_clock_source src; + unsigned int rate; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = protocol->cache_packet_formats(motu); + if (err < 0) + goto err_locked; + + err = init_hw_info(motu, substream); + if (err < 0) + goto err_locked; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + err = protocol->get_clock_source(motu, &src); + if (err < 0) + goto err_locked; + if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = protocol->get_clock_rate(motu, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->capture_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->playback_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->capture_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->playback_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000000..4edc064999ed --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,118 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int rate; + enum snd_motu_clock_source source; + + if (protocol->get_clock_rate(motu, &rate) < 0) + return; + if (protocol->get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (protocol->cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000000..05b5d287c2f3 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,237 @@ +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + /* To check the configuration of optical interface. */ + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + if (be32_to_cpu(reg) & 0x00000200) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + return -EIO; + } + + return 0; +} + +static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + /* V2 protocol doesn't have this feature. */ + return 0; +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * All of v2 models have a pair of coaxial interfaces for digital in/out + * port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these + * ports. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* This part should be multiples of 4. */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, + u32 data, u32 mask, u32 shift) +{ + unsigned char pcm_chunks[3] = {0, 0}; + + /* + * When optical interfaces are configured for S/PDIF (TOSLINK), + * the above PCM frames come from them, instead of coaxial + * interfaces. + */ + data = (data & mask) >> shift; + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && + data == V2_OPT_IFACE_MODE_ADAT) { + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + + /* At mode x4, no data chunks are supported in this part. */ + formats->differed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->differed_part_pcm_chunks[1] = pcm_chunks[1]; +} + +static int v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags, + data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags, + data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT); + + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v2 = { + .get_clock_rate = v2_get_clock_rate, + .set_clock_rate = v2_set_clock_rate, + .get_clock_source = v2_get_clock_source, + .switch_fetching_mode = v2_switch_fetching_mode, + .cache_packet_formats = v2_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 000000000000..ddb647254ed2 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,311 @@ +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + /* Cost expensive. */ + if (msleep_interruptible(4000) > 0) + return -EINTR; + } + + return 0; +} + +static int v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + unsigned int val; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + val = data & V3_CLOCK_SOURCE_MASK; + if (val == 0x00) { + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + } else if (val == 0x01) { + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + } else if (val == 0x10) { + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + } else if (val == 0x18 || val == 0x19) { + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (val == 0x18) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + } else { + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + } + + return 0; +} + +static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * At least, packets have two data chunks for S/PDIF on coaxial + * interface. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* + * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As + * a result, this part can includes empty data chunks. + */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, u32 data, + u32 a_enable_mask, u32 a_no_adat_mask, + u32 b_enable_mask, u32 b_no_adat_mask) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + int i; + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { + if (data & a_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface A. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * A. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { + if (data & b_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface B. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * B. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + for (i = 0; i < 3; ++i) { + if (pcm_chunks[i] > 0) + pcm_chunks[i] = round_up(pcm_chunks[i], 4); + + formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; + } +} + +static int v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, + V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, + V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); + + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v3 = { + .get_clock_rate = v3_get_clock_rate, + .set_clock_rate = v3_set_clock_rate, + .get_clock_source = v3_get_clock_source, + .switch_fetching_mode = v3_switch_fetching_mode, + .cache_packet_formats = v3_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000000..bd458029099e --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,381 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int start_both_streams(struct snd_motu *motu, unsigned int rate) +{ + unsigned int midi_ports = 0; + __be32 reg; + u32 data; + int err; + + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) + midi_ports = 1; + + /* Set packet formation to our packet streaming engine. */ + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports, + &motu->rx_packet_formats); + if (err < 0) + return err; + + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports, + &motu->tx_packet_formats); + if (err < 0) + return err; + + /* Get isochronous resources on the bus. */ + err = fw_iso_resources_allocate(&motu->rx_resources, + amdtp_stream_get_max_payload(&motu->rx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + err = fw_iso_resources_allocate(&motu->tx_resources, + amdtp_stream_get_max_payload(&motu->tx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Configure the unit to start isochronous communication. */ + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void stop_both_streams(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = motu->spec->protocol->switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); +} + +static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + int err; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + err = amdtp_stream_start(stream, resources->channel, + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); + return -ETIMEDOUT; + } + + return 0; +} + +static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + const struct snd_motu_protocol *protocol = motu->spec->protocol; + unsigned int curr_rate; + int err = 0; + + if (motu->capture_substreams == 0 && motu->playback_substreams == 0) + return 0; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = protocol->get_clock_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate: %d\n", err); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = protocol->set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = start_both_streams(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = start_isoc_ctx(motu, &motu->rx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IT context: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = protocol->switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + stop_both_streams(motu); + return err; + } + } + + if (!amdtp_stream_running(&motu->tx_stream) && + motu->capture_substreams > 0) { + err = start_isoc_ctx(motu, &motu->tx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IR context: %d", err); + amdtp_stream_stop(&motu->rx_stream); + stop_both_streams(motu); + return err; + } + } + + return 0; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->capture_substreams == 0) { + if (amdtp_stream_running(&motu->tx_stream)) + stop_isoc_ctx(motu, &motu->tx_stream); + + if (motu->playback_substreams == 0) { + if (amdtp_stream_running(&motu->rx_stream)) + stop_isoc_ctx(motu, &motu->rx_stream); + stop_both_streams(motu); + } + } +} + +static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +{ + int err; + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } + + return err; +} + +static void destroy_stream(struct snd_motu *motu, + enum amdtp_stream_direction dir) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_free(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, AMDTP_IN_STREAM); + if (err < 0) + return err; + + err = init_stream(motu, AMDTP_OUT_STREAM); + if (err < 0) + destroy_stream(motu, AMDTP_IN_STREAM); + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + destroy_stream(motu, AMDTP_IN_STREAM); + destroy_stream(motu, AMDTP_OUT_STREAM); + + motu->playback_substreams = 0; + motu->capture_substreams = 0; +} + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000000..7fc30091e0de --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,137 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000000..bf779cfeef0d --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,264 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_VERSION: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU %s (version:%d), GUID %08x%08x at %s, S%d", + motu->spec->name, version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_free(struct snd_motu *motu) +{ + snd_motu_transaction_unregister(motu); + + snd_motu_stream_destroy_duplex(motu); + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); + kfree(motu); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void motu_card_free(struct snd_card *card) +{ + motu_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; + + if (motu->registered) + return; + + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; + + name_card(motu); + + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + + snd_motu_proc_init(motu); + + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + + if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + + err = snd_card_register(motu->card); + if (err < 0) + goto error; + + /* + * After registered, motu instance can be released corresponding to + * releasing the sound card instance. + */ + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + motu->registered = true; + + return; +error: + snd_motu_transaction_unregister(motu); + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL); + if (motu == NULL) + return -ENOMEM; + + motu->spec = (const struct snd_motu_spec *)entry->driver_data; + motu->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, motu); + + mutex_init(&motu->mutex); + spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + + return 0; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); + } else { + /* Don't forget this case. */ + motu_free(motu); + } +} + +static void motu_bus_update(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); +} + +static struct snd_motu_spec motu_828mk2 = { + .name = "828mk2", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +static struct snd_motu_spec motu_828mk3 = { + .name = "828mk3", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_TX_REVERB_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_OPT_IFACE_B | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID | \ + IEEE1394_MATCH_SPECIFIER_ID, \ + .vendor_id = OUI_MOTU, \ + .model_id = model, \ + .specifier_id = OUI_MOTU, \ + .driver_data = (kernel_ulong_t)data, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), + SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */ + SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */ + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000000..8d6a4a3af9cc --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,161 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char fixed_part_pcm_chunks[3]; + unsigned char differed_part_pcm_chunks[3]; +}; + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int capture_substreams; + unsigned int playback_substreams; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001, + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002, + SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004, + SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008, + SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010, + SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020, + SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040, + SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080, + SND_MOTU_SPEC_HAS_MIDI = 0x0100, +}; + +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +struct snd_motu_protocol { + int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate); + int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate); + int (*get_clock_source)(struct snd_motu *motu, + enum snd_motu_clock_source *source); + int (*switch_fetching_mode)(struct snd_motu *motu, bool enable); + int (*cache_packet_formats)(struct snd_motu *motu); +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_spec_flags flags; + + unsigned char analog_in_ports; + unsigned char analog_out_ports; + + const struct snd_motu_protocol *const protocol; +}; + +extern const struct snd_motu_protocol snd_motu_protocol_v2; +extern const struct snd_motu_protocol snd_motu_protocol_v3; + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); +#endif diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c index 12ef3253bc89..ac3e2e301666 100644 --- a/sound/firewire/oxfw/oxfw-command.c +++ b/sound/firewire/oxfw/oxfw-command.c @@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8)); - if ((err > 0) && (err < len + 10)) + if (err < 0) + ; + else if (err < len + 10) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit, err = fcp_avc_transaction(unit, buf, 12, buf, *len, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7)); - if ((err > 0) && (err < 10)) + if (err < 0) + ; + else if (err < 12) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; @@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, /* do transaction and check buf[1-5] are the same against command */ err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if ((err > 0) && (err < 8)) + if (err < 0) + ; + else if (err < 8) err = -EIO; else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ err = -ENOSYS; diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c index df4f95d65925..4a741570d536 100644 --- a/sound/firewire/tascam/tascam-midi.c +++ b/sound/firewire/tascam/tascam-midi.c @@ -18,9 +18,8 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_tscm *tscm = substream->rmidi->private_data; - /* Initialize internal status. */ - tscm->running_status[substream->number] = 0; - tscm->on_sysex[substream->number] = 0; + snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); + return 0; } @@ -32,11 +31,14 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) static int midi_playback_close(struct snd_rawmidi_substream *substream) { + return 0; +} + +static void midi_playback_drain(struct snd_rawmidi_substream *substream) +{ struct snd_tscm *tscm = substream->rmidi->private_data; snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); - - return 0; } static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) @@ -78,6 +80,7 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm) static const struct snd_rawmidi_ops playback_ops = { .open = midi_playback_open, .close = midi_playback_close, + .drain = midi_playback_drain, .trigger = midi_playback_trigger, }; struct snd_rawmidi *rmidi; diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c index 040a96d1ba8e..8967c52f5032 100644 --- a/sound/firewire/tascam/tascam-transaction.c +++ b/sound/firewire/tascam/tascam-transaction.c @@ -58,39 +58,38 @@ static inline int calculate_message_bytes(u8 status) return -EINVAL; } -static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) +static int fill_message(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) { - struct snd_tscm *tscm = substream->rmidi->private_data; - unsigned int port = substream->number; int i, len, consume; u8 *label, *msg; u8 status; /* The first byte is used for label, the rest for MIDI bytes. */ - label = buf; - msg = buf + 1; + label = port->buf; + msg = port->buf + 1; consume = snd_rawmidi_transmit_peek(substream, msg, 3); if (consume == 0) return 0; /* On exclusive message. */ - if (tscm->on_sysex[port]) { + if (port->on_sysex) { /* Seek the end of exclusives. */ for (i = 0; i < consume; ++i) { if (msg[i] == 0xf7) { - tscm->on_sysex[port] = false; + port->on_sysex = false; break; } } /* At the end of exclusive message, use label 0x07. */ - if (!tscm->on_sysex[port]) { + if (!port->on_sysex) { consume = i + 1; - *label = (port << 4) | 0x07; + *label = (substream->number << 4) | 0x07; /* During exclusive message, use label 0x04. */ } else if (consume == 3) { - *label = (port << 4) | 0x04; + *label = (substream->number << 4) | 0x04; /* We need to fill whole 3 bytes. Go to next change. */ } else { return 0; @@ -101,12 +100,12 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) /* The beginning of exclusives. */ if (msg[0] == 0xf0) { /* Transfer it in next chance in another condition. */ - tscm->on_sysex[port] = true; + port->on_sysex = true; return 0; } else { /* On running-status. */ if ((msg[0] & 0x80) != 0x80) - status = tscm->running_status[port]; + status = port->running_status; else status = msg[0]; @@ -124,18 +123,18 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) msg[2] = msg[1]; msg[1] = msg[0]; - msg[0] = tscm->running_status[port]; + msg[0] = port->running_status; } else { /* Enough MIDI bytes were not retrieved. */ if (consume < len) return 0; consume = len; - tscm->running_status[port] = msg[0]; + port->running_status = msg[0]; } } - *label = (port << 4) | (msg[0] >> 4); + *label = (substream->number << 4) | (msg[0] >> 4); } if (len > 0 && len < 3) @@ -144,6 +143,106 @@ static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) return consume; } +static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + + /* This port is closed. */ + if (substream == NULL) + return; + + if (rcode == RCODE_COMPLETE) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_ktime = 0; + else + /* Don't continue processing. */ + port->error = true; + + port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&port->work); +} + +static void midi_port_work(struct work_struct *work) +{ + struct snd_fw_async_midi_port *port = + container_of(work, struct snd_fw_async_midi_port, work); + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + int generation; + + /* Under transacting or error state. */ + if (!port->idling || port->error) + return; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* Do it in next chance. */ + if (ktime_after(port->next_ktime, ktime_get())) { + schedule_work(&port->work); + return; + } + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() is called. + */ + memset(port->buf, 0, 4); + port->consume_bytes = fill_message(port, substream); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) { + port->next_ktime = 0; + schedule_work(&port->work); + } else { + /* Fatal error. */ + port->error = true; + } + return; + } + + /* Set interval to next transaction. */ + port->next_ktime = ktime_add_ns(ktime_get(), + port->consume_bytes * 8 * NSEC_PER_SEC / 31250); + + /* Start this transaction. */ + port->idling = false; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = port->parent->generation; + smp_rmb(); + + fw_send_request(port->parent->card, &port->transaction, + TCODE_WRITE_QUADLET_REQUEST, + port->parent->node_id, generation, + port->parent->max_speed, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, + port->buf, 4, async_midi_port_callback, + port); +} + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) +{ + port->idling = true; + port->error = false; + port->running_status = 0; + port->on_sysex = false; +} + static void handle_midi_tx(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, @@ -219,12 +318,9 @@ int snd_tscm_transaction_register(struct snd_tscm *tscm) goto error; for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { - err = snd_fw_async_midi_port_init( - &tscm->out_ports[i], tscm->unit, - TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, - 4, fill_message); - if (err < 0) - goto error; + tscm->out_ports[i].parent = fw_parent_device(tscm->unit); + tscm->out_ports[i].next_ktime = 0; + INIT_WORK(&tscm->out_ports[i].work, midi_port_work); } return err; @@ -275,7 +371,6 @@ int snd_tscm_transaction_reregister(struct snd_tscm *tscm) void snd_tscm_transaction_unregister(struct snd_tscm *tscm) { __be32 reg; - unsigned int i; if (tscm->async_handler.callback_data == NULL) return; @@ -302,7 +397,4 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm) fw_core_remove_address_handler(&tscm->async_handler); tscm->async_handler.callback_data = NULL; - - for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) - snd_fw_async_midi_port_destroy(&tscm->out_ports[i]); } diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index d3cd4065722b..08ecfae5c584 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -45,6 +45,23 @@ struct snd_tscm_spec { #define TSCM_MIDI_IN_PORT_MAX 4 #define TSCM_MIDI_OUT_PORT_MAX 4 +struct snd_fw_async_midi_port { + struct fw_device *parent; + struct work_struct work; + bool idling; + ktime_t next_ktime; + bool error; + + struct fw_transaction transaction; + + u8 buf[4]; + u8 running_status; + bool on_sysex; + + struct snd_rawmidi_substream *substream; + int consume_bytes; +}; + struct snd_tscm { struct snd_card *card; struct fw_unit *unit; @@ -72,8 +89,6 @@ struct snd_tscm { /* For MIDI message outgoing transactions. */ struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; - u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; - bool on_sysex[TSCM_MIDI_OUT_PORT_MAX]; }; #define TSCM_ADDR_BASE 0xffff00000000ull @@ -131,6 +146,26 @@ void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); int snd_tscm_stream_lock_try(struct snd_tscm *tscm); void snd_tscm_stream_lock_release(struct snd_tscm *tscm); +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port); + +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + if (!port->error) { + port->substream = substream; + schedule_work(&port->work); + } +} + +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; + cancel_work_sync(&port->work); + port->error = false; +} + int snd_tscm_transaction_register(struct snd_tscm *tscm); int snd_tscm_transaction_reregister(struct snd_tscm *tscm); void snd_tscm_transaction_unregister(struct snd_tscm *tscm); diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c index 261469188566..84f3b8168716 100644 --- a/sound/hda/ext/hdac_ext_controller.c +++ b/sound/hda/ext/hdac_ext_controller.c @@ -171,7 +171,7 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) { int timeout; u32 val; - int mask = (1 << AZX_MLCTL_CPA); + int mask = (1 << AZX_MLCTL_CPA_SHIFT); udelay(3); timeout = 150; @@ -179,10 +179,10 @@ static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) do { val = readl(link->ml_addr + AZX_REG_ML_LCTL); if (enable) { - if (((val & mask) >> AZX_MLCTL_CPA)) + if (((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } else { - if (!((val & mask) >> AZX_MLCTL_CPA)) + if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT)) return 0; } udelay(3); diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 043065867656..ee08c389b4d6 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -268,11 +268,11 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) unsigned int offset; unsigned int counter = 0; - offset = snd_hdac_chip_readl(bus, LLCH); + offset = snd_hdac_chip_readw(bus, LLCH); /* Lets walk the linked capabilities list */ do { - cur_cap = _snd_hdac_chip_read(l, bus, offset); + cur_cap = _snd_hdac_chip_readl(bus, offset); dev_dbg(bus->dev, "Capability version: 0x%x\n", (cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF); diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index c6994ebb4567..e1472c7ab6c1 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -555,12 +555,12 @@ void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set, if (!reg) reg = AZX_REG_SSYNC; - val = _snd_hdac_chip_read(l, bus, reg); + val = _snd_hdac_chip_readl(bus, reg); if (set) val |= streams; else val &= ~streams; - _snd_hdac_chip_write(l, bus, reg, val); + _snd_hdac_chip_writel(bus, reg, val); } EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger); diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c index e2cf508841b1..81cf26fa28d6 100644 --- a/sound/isa/es1688/es1688_lib.c +++ b/sound/isa/es1688/es1688_lib.c @@ -742,7 +742,7 @@ int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device) pcm->private_data = chip; pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; - sprintf(pcm->name, snd_es1688_chip_id(chip)); + strcpy(pcm->name, snd_es1688_chip_id(chip)); chip->pcm = pcm; snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 92b819e4f729..34bbc2e730a6 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -1339,7 +1339,7 @@ static int snd_ali_prepare(struct snd_pcm_substream *substream) rate = snd_ali_get_spdif_in_rate(codec); if (rate == 0) { dev_warn(codec->card->dev, - "ali_capture_preapre: spdif rate detect err!\n"); + "ali_capture_prepare: spdif rate detect err!\n"); rate = 48000; } spin_lock_irq(&codec->reg_lock); diff --git a/sound/pci/au88x0/au88x0_a3d.c b/sound/pci/au88x0/au88x0_a3d.c index ab0f87312911..7a4558a70fb9 100644 --- a/sound/pci/au88x0/au88x0_a3d.c +++ b/sound/pci/au88x0/au88x0_a3d.c @@ -846,7 +846,7 @@ snd_vortex_a3d_filter_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new vortex_a3d_kcontrol = { +static const struct snd_kcontrol_new vortex_a3d_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Playback PCM advanced processing", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, diff --git a/sound/pci/au88x0/au88x0_core.c b/sound/pci/au88x0/au88x0_core.c index e1af24f87566..c308a4f70550 100644 --- a/sound/pci/au88x0/au88x0_core.c +++ b/sound/pci/au88x0/au88x0_core.c @@ -2279,6 +2279,9 @@ vortex_adb_allocroute(vortex_t *vortex, int dma, int nr_ch, int dir, } else { int src[2], mix[2]; + if (nr_ch < 1) + return -EINVAL; + /* Get SRC and MIXER hardware resources. */ for (i = 0; i < nr_ch; i++) { if ((mix[i] = diff --git a/sound/pci/au88x0/au88x0_eq.c b/sound/pci/au88x0/au88x0_eq.c index 9585c5c63b96..b566b44e4da7 100644 --- a/sound/pci/au88x0/au88x0_eq.c +++ b/sound/pci/au88x0/au88x0_eq.c @@ -757,7 +757,7 @@ snd_vortex_eqtoggle_put(struct snd_kcontrol *kcontrol, return 1; /* Allways changes */ } -static struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { +static const struct snd_kcontrol_new vortex_eqtoggle_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Enable", .index = 0, @@ -815,7 +815,7 @@ snd_vortex_eq_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucon return changed; } -static struct snd_kcontrol_new vortex_eq_kcontrol = { +static const struct snd_kcontrol_new vortex_eq_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = " .", .index = 0, @@ -855,7 +855,7 @@ snd_vortex_peaks_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u return 0; } -static struct snd_kcontrol_new vortex_levels_kcontrol = { +static const struct snd_kcontrol_new vortex_levels_kcontrol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "EQ Peaks", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/au88x0/au88x0_pcm.c b/sound/pci/au88x0/au88x0_pcm.c index df5741a78fd2..335979a753fe 100644 --- a/sound/pci/au88x0/au88x0_pcm.c +++ b/sound/pci/au88x0/au88x0_pcm.c @@ -601,7 +601,7 @@ static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400); -static struct snd_kcontrol_new snd_vortex_pcm_vol = { +static const struct snd_kcontrol_new snd_vortex_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 57bbb87d0c62..8356180bfe0e 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -202,7 +202,7 @@ static const struct snd_pcm_ops snd_aw2_capture_ops = { .pointer = snd_aw2_pcm_pointer_capture, }; -static struct snd_kcontrol_new aw2_control = { +static const struct snd_kcontrol_new aw2_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Route", .index = 0, diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index f2c0709d7441..099efb046b1c 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -598,7 +598,7 @@ static int snd_bt87x_capture_volume_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_volume = { +static const struct snd_kcontrol_new snd_bt87x_capture_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Volume", .info = snd_bt87x_capture_volume_info, @@ -634,7 +634,7 @@ static int snd_bt87x_capture_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_boost = { +static const struct snd_kcontrol_new snd_bt87x_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Boost", .info = snd_bt87x_capture_boost_info, @@ -676,7 +676,7 @@ static int snd_bt87x_capture_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_bt87x_capture_source = { +static const struct snd_kcontrol_new snd_bt87x_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = snd_bt87x_capture_source_info, diff --git a/sound/pci/ca0106/ca0106_mixer.c b/sound/pci/ca0106/ca0106_mixer.c index 025805cba779..b4d3415331f6 100644 --- a/sound/pci/ca0106/ca0106_mixer.c +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -301,7 +301,7 @@ static int snd_ca0106_capture_mic_line_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = +static const struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Mic/Line in Capture Switch", @@ -310,7 +310,7 @@ static struct snd_kcontrol_new snd_ca0106_capture_mic_line_in = .put = snd_ca0106_capture_mic_line_in_put }; -static struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = +static const struct snd_kcontrol_new snd_ca0106_capture_line_in_side_out = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Shared Line in/Side out Capture Switch", diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index aeedc270ed9b..227c9d3802b8 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -1045,7 +1045,7 @@ static int snd_cmipci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_default = +static const struct snd_kcontrol_new snd_cmipci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1072,7 +1072,7 @@ static int snd_cmipci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_cmipci_spdif_mask = +static const struct snd_kcontrol_new snd_cmipci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1119,7 +1119,7 @@ static int snd_cmipci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_cmipci_spdif_stream = +static const struct snd_kcontrol_new snd_cmipci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index fa7c51684dd2..f870697aca67 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -1055,7 +1055,7 @@ static int snd_cs4281_put_volume(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dsp, -4650, 150, 0); -static struct snd_kcontrol_new snd_cs4281_fm_vol = +static const struct snd_kcontrol_new snd_cs4281_fm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Synth Playback Volume", @@ -1066,7 +1066,7 @@ static struct snd_kcontrol_new snd_cs4281_fm_vol = .tlv = { .p = db_scale_dsp }, }; -static struct snd_kcontrol_new snd_cs4281_pcm_vol = +static const struct snd_kcontrol_new snd_cs4281_pcm_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Stream Playback Volume", diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 937071760bc4..d15ecf9febbf 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -1039,7 +1039,7 @@ static int snd_echo_output_gain_put(struct snd_kcontrol *kcontrol, #ifdef ECHOCARD_HAS_LINE_OUT_GAIN /* On the Mia this one controls the line-out volume */ -static struct snd_kcontrol_new snd_echo_line_output_gain = { +static const struct snd_kcontrol_new snd_echo_line_output_gain = { .name = "Line Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1050,7 +1050,7 @@ static struct snd_kcontrol_new snd_echo_line_output_gain = { .tlv = {.p = db_scale_output_gain}, }; #else -static struct snd_kcontrol_new snd_echo_pcm_output_gain = { +static const struct snd_kcontrol_new snd_echo_pcm_output_gain = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1120,7 +1120,7 @@ static int snd_echo_input_gain_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_input_gain, -2500, 50, 0); -static struct snd_kcontrol_new snd_echo_line_input_gain = { +static const struct snd_kcontrol_new snd_echo_line_input_gain = { .name = "Line Capture Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -1184,7 +1184,7 @@ static int snd_echo_output_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_output_nominal_level = { +static const struct snd_kcontrol_new snd_echo_output_nominal_level = { .name = "Line Playback Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_output_nominal_info, @@ -1250,7 +1250,7 @@ static int snd_echo_input_nominal_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_intput_nominal_level = { +static const struct snd_kcontrol_new snd_echo_intput_nominal_level = { .name = "Line Capture Switch (-10dBV)", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_echo_input_nominal_info, @@ -1477,7 +1477,7 @@ static int snd_echo_digital_mode_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_digital_mode_switch = { +static const struct snd_kcontrol_new snd_echo_digital_mode_switch = { .name = "Digital mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_digital_mode_info, @@ -1527,7 +1527,7 @@ static int snd_echo_spdif_mode_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_spdif_mode_switch = { +static const struct snd_kcontrol_new snd_echo_spdif_mode_switch = { .name = "S/PDIF mode Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_spdif_mode_info, @@ -1600,7 +1600,7 @@ static int snd_echo_clock_source_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_clock_source_switch = { +static const struct snd_kcontrol_new snd_echo_clock_source_switch = { .name = "Sample Clock Source", .iface = SNDRV_CTL_ELEM_IFACE_PCM, .info = snd_echo_clock_source_info, @@ -1643,7 +1643,7 @@ static int snd_echo_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_phantom_power_switch = { +static const struct snd_kcontrol_new snd_echo_phantom_power_switch = { .name = "Phantom power Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_phantom_power_info, @@ -1686,7 +1686,7 @@ static int snd_echo_automute_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_echo_automute_switch = { +static const struct snd_kcontrol_new snd_echo_automute_switch = { .name = "Digital Capture Switch (automute)", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .info = snd_echo_automute_info, @@ -1713,7 +1713,7 @@ static int snd_echo_vumeters_switch_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new snd_echo_vumeters_switch = { +static const struct snd_kcontrol_new snd_echo_vumeters_switch = { .name = "VU-meters Switch", .iface = SNDRV_CTL_ELEM_IFACE_CARD, .access = SNDRV_CTL_ELEM_ACCESS_WRITE, @@ -1751,7 +1751,7 @@ static int snd_echo_vumeters_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_vumeters = { +static const struct snd_kcontrol_new snd_echo_vumeters = { .name = "VU-meters", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | @@ -1804,7 +1804,7 @@ static int snd_echo_channels_info_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_echo_channels_info = { +static const struct snd_kcontrol_new snd_echo_channels_info = { .name = "Channels info", .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index 32842734ada6..77a4413f4564 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1112,7 +1112,7 @@ static int snd_emu10k1x_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1x_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Analog/Digital Output Jack", @@ -1171,7 +1171,7 @@ static int snd_emu10k1x_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1181,7 +1181,7 @@ static struct snd_kcontrol_new snd_emu10k1x_spdif_mask_control = .get = snd_emu10k1x_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1x_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1x_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 076b117009c5..b2219a73c17c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -795,7 +795,7 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_internal_clock = +static const struct snd_kcontrol_new snd_emu1010_internal_clock = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -847,7 +847,7 @@ static int snd_emu1010_optical_out_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_out = { +static const struct snd_kcontrol_new snd_emu1010_optical_out = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Output Mode", @@ -898,7 +898,7 @@ static int snd_emu1010_optical_in_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu1010_optical_in = { +static const struct snd_kcontrol_new snd_emu1010_optical_in = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Optical Input Mode", @@ -978,7 +978,7 @@ static int snd_audigy_i2c_capture_source_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_audigy_i2c_capture_source = +static const struct snd_kcontrol_new snd_audigy_i2c_capture_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", @@ -1177,7 +1177,7 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1187,7 +1187,7 @@ static struct snd_kcontrol_new snd_emu10k1_spdif_mask_control = .get = snd_emu10k1_spdif_get_mask }; -static struct snd_kcontrol_new snd_emu10k1_spdif_control = +static const struct snd_kcontrol_new snd_emu10k1_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1293,7 +1293,7 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1364,7 +1364,7 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1429,7 +1429,7 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1501,7 +1501,7 @@ static int snd_emu10k1_efx_send_routing_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_routing_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1568,7 +1568,7 @@ static int snd_emu10k1_efx_send_volume_put(struct snd_kcontrol *kcontrol, } -static struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_send_volume_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1626,7 +1626,7 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_efx_attn_control = +static const struct snd_kcontrol_new snd_emu10k1_efx_attn_control = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1691,7 +1691,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_emu10k1_shared_spdif = +static const struct snd_kcontrol_new snd_emu10k1_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "SB Live Analog/Digital Output Jack", @@ -1700,7 +1700,7 @@ static struct snd_kcontrol_new snd_emu10k1_shared_spdif = .put = snd_emu10k1_shared_spdif_put }; -static struct snd_kcontrol_new snd_audigy_shared_spdif = +static const struct snd_kcontrol_new snd_audigy_shared_spdif = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Audigy Analog/Digital Output Jack", @@ -1738,7 +1738,7 @@ static int snd_audigy_capture_boost_put(struct snd_kcontrol *kcontrol, return snd_ac97_update(emu->ac97, AC97_REC_GAIN, val); } -static struct snd_kcontrol_new snd_audigy_capture_boost = +static const struct snd_kcontrol_new snd_audigy_capture_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mic Extra Boost", diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 37be1e14d756..ef1cf530c929 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1542,7 +1542,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st return change; } -static struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { +static const struct snd_kcontrol_new snd_emu10k1_pcm_efx_voices_mask = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Captured FX8010 Outputs", .info = snd_emu10k1_pcm_efx_voices_mask_info, diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 164adad91650..5d10349d11ce 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -1530,7 +1530,7 @@ static int snd_es1373_rear_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ens1373_rear = +static const struct snd_kcontrol_new snd_ens1373_rear = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "AC97 2ch->4ch Copy Switch", @@ -1575,7 +1575,7 @@ static int snd_es1373_line_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new snd_ens1373_line = +static const struct snd_kcontrol_new snd_ens1373_line = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line In->Rear Out Switch", diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index a03cf68d0bcd..d3ea73171a3d 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -580,6 +580,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, has_multiple_pins = 1; if (has_multiple_pins && type == AUTO_PIN_MIC) has_multiple_pins &= check_mic_location_need(codec, cfg, input); + has_multiple_pins |= codec->force_pin_prefix; return hda_get_input_pin_label(codec, &cfg->inputs[input], cfg->inputs[input].pin, has_multiple_pins); diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 8fd745cb3f36..70bb365a08d2 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1965,7 +1965,7 @@ static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol, return 1; } -static struct snd_kcontrol_new vmaster_mute_mode = { +static const struct snd_kcontrol_new vmaster_mute_mode = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Mute-LED Mode", .info = vmaster_mute_mode_info, @@ -2705,7 +2705,7 @@ static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new spdif_share_sw = { +static const struct snd_kcontrol_new spdif_share_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "IEC958 Default PCM Playback Switch", .info = snd_ctl_boolean_mono_info, diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index f17f25245e52..d6fb2d5d01a7 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -256,6 +256,7 @@ struct hda_codec { unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ unsigned int power_save_node:1; /* advanced PM for each widget */ unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */ + unsigned int force_pin_prefix:1; /* Add location prefix */ #ifdef CONFIG_PM unsigned long power_on_acct; unsigned long power_off_acct; diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index e7c8f4f076d5..2842c82363c0 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -196,6 +196,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "hp_mic_detect"); if (val >= 0) spec->suppress_hp_mic_detect = !val; + val = snd_hda_get_bool_hint(codec, "vmaster"); + if (val >= 0) + spec->suppress_vmaster = !val; if (!snd_hda_get_int_hint(codec, "mixer_nid", &val)) spec->mixer_nid = val; @@ -1125,6 +1128,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, *index = 0; if (cfg->line_outs == 1 && !spec->multi_ios && + !codec->force_pin_prefix && !cfg->hp_outs && !cfg->speaker_outs) return spec->vmaster_mute.hook ? "PCM" : "Master"; @@ -1132,6 +1136,7 @@ static const char *get_line_out_pfx(struct hda_codec *codec, int ch, * use it master (or "PCM" if a vmaster hook is present) */ if (spec->multiout.num_dacs == 1 && !spec->mixer_nid && + !codec->force_pin_prefix && !spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0]) return spec->vmaster_mute.hook ? "PCM" : "Master"; @@ -5031,7 +5036,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) } /* if we have no master control, let's create it */ - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { err = snd_hda_add_vmaster(codec, "Master Playback Volume", spec->vmaster_tlv, slave_pfxs, @@ -5039,7 +5044,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) if (err < 0) return err; } - if (!spec->no_analog && + if (!spec->no_analog && !spec->suppress_vmaster && !snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { err = __snd_hda_add_vmaster(codec, "Master Playback Switch", NULL, slave_pfxs, diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index f66fc7e25e07..61772317de46 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -229,6 +229,7 @@ struct hda_gen_spec { unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ unsigned int power_down_unused:1; /* power down unused widgets */ unsigned int dac_min_mute:1; /* minimal = mute for DACs */ + unsigned int suppress_vmaster:1; /* don't create vmaster kctls */ /* other internal flags */ unsigned int no_analog:1; /* digital I/O only */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index c8256a89375a..b786fbab029f 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -77,6 +77,7 @@ enum { POS_FIX_POSBUF, POS_FIX_VIACOMBO, POS_FIX_COMBO, + POS_FIX_SKL, }; /* Defines for ATI HD Audio support in SB450 south bridge */ @@ -148,7 +149,7 @@ module_param_array(model, charp, NULL, 0444); MODULE_PARM_DESC(model, "Use the given board model."); module_param_array(position_fix, int, NULL, 0444); MODULE_PARM_DESC(position_fix, "DMA pointer read method." - "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO)."); + "(-1 = system default, 0 = auto, 1 = LPIB, 2 = POSBUF, 3 = VIACOMBO, 4 = COMBO, 5 = SKL+)."); module_param_array(bdl_pos_adj, int, NULL, 0644); MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); module_param_array(probe_mask, int, NULL, 0444); @@ -369,8 +370,10 @@ enum { #define IS_KBL_LP(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9d71) #define IS_KBL_H(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa2f0) #define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) +#define IS_GLK(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x3198) #define IS_SKL_PLUS(pci) (IS_SKL(pci) || IS_SKL_LP(pci) || IS_BXT(pci)) || \ - IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) + IS_KBL(pci) || IS_KBL_LP(pci) || IS_KBL_H(pci) || \ + IS_GLK(pci) static char *driver_short_names[] = { [AZX_DRIVER_ICH] = "HDA Intel", @@ -534,9 +537,101 @@ static void bxt_reduce_dma_latency(struct azx *chip) { u32 val; - val = azx_readl(chip, SKL_EM4L); + val = azx_readl(chip, VS_EM4L); val &= (0x3 << 20); - azx_writel(chip, SKL_EM4L, val); + azx_writel(chip, VS_EM4L, val); +} + +/* + * ML_LCAP bits: + * bit 0: 6 MHz Supported + * bit 1: 12 MHz Supported + * bit 2: 24 MHz Supported + * bit 3: 48 MHz Supported + * bit 4: 96 MHz Supported + * bit 5: 192 MHz Supported + */ +static int intel_get_lctl_scf(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + static int preferred_bits[] = { 2, 3, 1, 4, 5 }; + u32 val, t; + int i; + + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCAP); + + for (i = 0; i < ARRAY_SIZE(preferred_bits); i++) { + t = preferred_bits[i]; + if (val & (1 << t)) + return t; + } + + dev_warn(chip->card->dev, "set audio clock frequency to 6MHz"); + return 0; +} + +static int intel_ml_lctl_set_power(struct azx *chip, int state) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int timeout; + + /* + * the codecs are sharing the first link setting by default + * If other links are enabled for stream, they need similar fix + */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + val &= ~AZX_MLCTL_SPA; + val |= state << AZX_MLCTL_SPA_SHIFT; + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* wait for CPA */ + timeout = 50; + while (timeout) { + if (((readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL)) & + AZX_MLCTL_CPA) == (state << AZX_MLCTL_CPA_SHIFT)) + return 0; + timeout--; + udelay(10); + } + + return -1; +} + +static void intel_init_lctl(struct azx *chip) +{ + struct hdac_bus *bus = azx_bus(chip); + u32 val; + int ret; + + /* 0. check lctl register value is correct or not */ + val = readl(bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + /* if SCF is already set, let's use it */ + if ((val & ML_LCTL_SCF_MASK) != 0) + return; + + /* + * Before operating on SPA, CPA must match SPA. + * Any deviation may result in undefined behavior. + */ + if (((val & AZX_MLCTL_SPA) >> AZX_MLCTL_SPA_SHIFT) != + ((val & AZX_MLCTL_CPA) >> AZX_MLCTL_CPA_SHIFT)) + return; + + /* 1. turn link down: set SPA to 0 and wait CPA to 0 */ + ret = intel_ml_lctl_set_power(chip, 0); + udelay(100); + if (ret) + goto set_spa; + + /* 2. update SCF to select a properly audio clock*/ + val &= ~ML_LCTL_SCF_MASK; + val |= intel_get_lctl_scf(chip); + writel(val, bus->mlcap + AZX_ML_BASE + AZX_REG_ML_LCTL); + +set_spa: + /* 4. turn link up: set SPA to 1 and wait CPA to 1 */ + intel_ml_lctl_set_power(chip, 1); + udelay(100); } static void hda_intel_init_chip(struct azx *chip, bool full_reset) @@ -564,6 +659,9 @@ static void hda_intel_init_chip(struct azx *chip, bool full_reset) /* reduce dma latency to avoid noise */ if (IS_BXT(pci)) bxt_reduce_dma_latency(chip); + + if (bus->mlcap != NULL) + intel_init_lctl(chip); } /* calculate runtime delay from LPIB */ @@ -815,6 +913,31 @@ static unsigned int azx_via_get_position(struct azx *chip, return bound_pos + mod_dma_pos; } +static unsigned int azx_skl_get_dpib_pos(struct azx *chip, + struct azx_dev *azx_dev) +{ + return _snd_hdac_chip_readl(azx_bus(chip), + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + azx_dev->core.index)); +} + +/* get the current DMA position with correction on SKL+ chips */ +static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev) +{ + /* DPIB register gives a more accurate position for playback */ + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return azx_skl_get_dpib_pos(chip, azx_dev); + + /* For capture, we need to read posbuf, but it requires a delay + * for the possible boundary overlap; the read of DPIB fetches the + * actual posbuf + */ + udelay(20); + azx_skl_get_dpib_pos(chip, azx_dev); + return azx_get_pos_posbuf(chip, azx_dev); +} + #ifdef CONFIG_PM static DEFINE_MUTEX(card_list_lock); static LIST_HEAD(card_list); @@ -1351,6 +1474,7 @@ static int check_position_fix(struct azx *chip, int fix) case POS_FIX_POSBUF: case POS_FIX_VIACOMBO: case POS_FIX_COMBO: + case POS_FIX_SKL: return fix; } @@ -1371,6 +1495,10 @@ static int check_position_fix(struct azx *chip, int fix) dev_dbg(chip->card->dev, "Using LPIB position fix\n"); return POS_FIX_LPIB; } + if (IS_SKL_PLUS(chip->pci)) { + dev_dbg(chip->card->dev, "Using SKL position fix\n"); + return POS_FIX_SKL; + } return POS_FIX_AUTO; } @@ -1382,6 +1510,7 @@ static void assign_position_fix(struct azx *chip, int fix) [POS_FIX_POSBUF] = azx_get_pos_posbuf, [POS_FIX_VIACOMBO] = azx_via_get_position, [POS_FIX_COMBO] = azx_get_pos_lpib, + [POS_FIX_SKL] = azx_get_pos_skl, }; chip->get_position[0] = chip->get_position[1] = callbacks[fix]; @@ -1390,7 +1519,7 @@ static void assign_position_fix(struct azx *chip, int fix) if (fix == POS_FIX_COMBO) chip->get_position[1] = NULL; - if (fix == POS_FIX_POSBUF && + if ((fix == POS_FIX_POSBUF || fix == POS_FIX_SKL) && (chip->driver_caps & AZX_DCAPS_COUNT_LPIB_DELAY)) { chip->get_delay[0] = chip->get_delay[1] = azx_get_delay_from_lpib; diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 07a9deb17477..a148176c16a9 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -857,7 +857,7 @@ static int chipio_write_address(struct hda_codec *codec, chip_addx >> 16); } - spec->curr_chip_addx = (res < 0) ? ~0UL : chip_addx; + spec->curr_chip_addx = (res < 0) ? ~0U : chip_addx; return res; } @@ -882,7 +882,7 @@ static int chipio_write_data(struct hda_codec *codec, unsigned int data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } @@ -933,7 +933,7 @@ static int chipio_read_data(struct hda_codec *codec, unsigned int *data) /*If no error encountered, automatically increment the address as per chip behaviour*/ spec->curr_chip_addx = (res != -EIO) ? - (spec->curr_chip_addx + 4) : ~0UL; + (spec->curr_chip_addx + 4) : ~0U; return res; } @@ -1168,7 +1168,7 @@ static int dspio_write_multiple(struct hda_codec *codec, int status = 0; unsigned int count; - if ((buffer == NULL)) + if (buffer == NULL) return -EINVAL; count = 0; @@ -1210,7 +1210,7 @@ static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer, unsigned int skip_count; unsigned int dummy; - if ((buffer == NULL)) + if (buffer == NULL) return -1; count = 0; diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 69266b8ea2ad..e8253737c47a 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -52,6 +52,12 @@ struct conexant_spec { bool dc_enable; unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */ struct nid_path *dc_mode_path; + + int mute_led_polarity; + unsigned int gpio_led; + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; + }; @@ -264,6 +270,7 @@ enum { CXT_FIXUP_HP_DOCK, CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, + CXT_FIXUP_MUTE_LED_GPIO, }; /* for hda_fixup_thinkpad_acpi() */ @@ -646,6 +653,74 @@ static void cxt_fixup_hp_gate_mic_jack(struct hda_codec *codec, snd_hda_jack_set_gating_jack(codec, 0x19, 0x16); } +/* update LED status via GPIO */ +static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask, + bool enabled) +{ + struct conexant_spec *spec = codec->spec; + unsigned int oldval = spec->gpio_led; + + if (spec->mute_led_polarity) + enabled = !enabled; + + if (enabled) + spec->gpio_led &= ~mask; + else + spec->gpio_led |= mask; + if (spec->gpio_led != oldval) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + +/* turn on/off mute LED via GPIO per vmaster hook */ +static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, enabled); +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static void cxt_fixup_gpio_mic_mute_hook(struct hda_codec *codec, + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct conexant_spec *spec = codec->spec; + + if (ucontrol) + cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, + ucontrol->value.integer.value[0] || + ucontrol->value.integer.value[1]); +} + + +static void cxt_fixup_mute_led_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + static const struct hda_verb gpio_init[] = { + { 0x01, AC_VERB_SET_GPIO_MASK, 0x03 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 }, + {} + }; + codec_info(codec, "action: %d gpio_led: %d\n", action, spec->gpio_led); + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook; + spec->gen.cap_sync_hook = cxt_fixup_gpio_mic_mute_hook; + spec->gpio_led = 0; + spec->mute_led_polarity = 0; + spec->gpio_mute_led_mask = 0x01; + spec->gpio_mic_led_mask = 0x02; + } + snd_hda_add_verbs(codec, gpio_init); + if (spec->gpio_led) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -799,6 +874,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_hp_gate_mic_jack, }, + [CXT_FIXUP_MUTE_LED_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_mute_led_gpio, + }, }; static const struct snd_pci_quirk cxt5045_fixups[] = { @@ -851,6 +930,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8174, "HP Spectre x360", CXT_FIXUP_HP_SPECTRE), SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), + SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410), @@ -882,6 +962,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = { { .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" }, { .id = CXT_FIXUP_MUTE_LED_EAPD, .name = "mute-led-eapd" }, { .id = CXT_FIXUP_HP_DOCK, .name = "hp-dock" }, + { .id = CXT_FIXUP_MUTE_LED_GPIO, .name = "mute-led-gpio" }, {} }; diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 1461ef8eb749..90e4ff87445e 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -177,6 +177,7 @@ struct hdmi_spec { bool i915_bound; /* was i915 bound in this driver? */ struct hdac_chmap chmap; + hda_nid_t vendor_nid; }; #ifdef CONFIG_SND_HDA_I915 @@ -383,7 +384,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new eld_bytes_ctl = { +static const struct snd_kcontrol_new eld_bytes_ctl = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "ELD", @@ -2372,6 +2373,7 @@ static void intel_haswell_fixup_connect_list(struct hda_codec *codec, } #define INTEL_VENDOR_NID 0x08 +#define INTEL_GLK_VENDOR_NID 0x0B #define INTEL_GET_VENDOR_VERB 0xf81 #define INTEL_SET_VENDOR_VERB 0x781 #define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */ @@ -2381,14 +2383,15 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, bool update_tree) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS) return; vendor_param |= INTEL_EN_ALL_PIN_CVTS; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); if (vendor_param == -1) return; @@ -2400,8 +2403,9 @@ static void intel_haswell_enable_all_pins(struct hda_codec *codec, static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) { unsigned int vendor_param; + struct hdmi_spec *spec = codec->spec; - vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0, + vendor_param = snd_hda_codec_read(codec, spec->vendor_nid, 0, INTEL_GET_VENDOR_VERB, 0); if (vendor_param == -1 || vendor_param & INTEL_EN_DP12) return; @@ -2409,7 +2413,7 @@ static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec) /* enable DP1.2 mode */ vendor_param |= INTEL_EN_DP12; snd_hdac_regmap_add_vendor_verb(&codec->core, INTEL_SET_VENDOR_VERB); - snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0, + snd_hda_codec_write_cache(codec, spec->vendor_nid, 0, INTEL_SET_VENDOR_VERB, vendor_param); } @@ -2503,7 +2507,7 @@ static void i915_pin_cvt_fixup(struct hda_codec *codec, } /* Intel Haswell and onwards; audio component with eld notifier */ -static int patch_i915_hsw_hdmi(struct hda_codec *codec) +static int intel_hsw_common_init(struct hda_codec *codec, hda_nid_t vendor_nid) { struct hdmi_spec *spec; int err; @@ -2520,6 +2524,7 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) spec = codec->spec; codec->dp_mst = true; spec->dyn_pcm_assign = true; + spec->vendor_nid = vendor_nid; intel_haswell_enable_all_pins(codec, true); intel_haswell_fixup_enable_dp12(codec); @@ -2548,6 +2553,16 @@ static int patch_i915_hsw_hdmi(struct hda_codec *codec) return 0; } +static int patch_i915_hsw_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_VENDOR_NID); +} + +static int patch_i915_glk_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID); +} + /* Intel Baytrail and Braswell; with eld notifier */ static int patch_i915_byt_hdmi(struct hda_codec *codec) { @@ -3800,7 +3815,7 @@ HDA_CODEC_ENTRY(0x80862808, "Broadwell HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x80862809, "Skylake HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280a, "Broxton HDMI", patch_i915_hsw_hdmi), HDA_CODEC_ENTRY(0x8086280b, "Kabylake HDMI", patch_i915_hsw_hdmi), -HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_hsw_hdmi), +HDA_CODEC_ENTRY(0x8086280d, "Geminilake HDMI", patch_i915_glk_hdmi), HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi), HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi), diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 299835d1fbaa..58df440013c5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1800,6 +1800,7 @@ enum { ALC882_FIXUP_NO_PRIMARY_HP, ALC887_FIXUP_ASUS_BASS, ALC887_FIXUP_BASS_CHMAP, + ALC1220_FIXUP_GB_DUAL_CODECS, }; static void alc889_fixup_coef(struct hda_codec *codec, @@ -1962,6 +1963,61 @@ static void alc882_fixup_no_primary_hp(struct hda_codec *codec, static void alc_fixup_bass_chmap(struct hda_codec *codec, const struct hda_fixup *fix, int action); +/* For dual-codec configuration, we need to disable some features to avoid + * conflicts of kctls and PCM streams + */ +static void alc_fixup_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + /* disable vmaster */ + spec->gen.suppress_vmaster = 1; + /* auto-mute and auto-mic switch don't work with multiple codecs */ + spec->gen.suppress_auto_mute = 1; + spec->gen.suppress_auto_mic = 1; + /* disable aamix as well */ + spec->gen.mixer_nid = 0; + /* add location prefix to avoid conflicts */ + codec->force_pin_prefix = 1; +} + +static void rename_ctl(struct hda_codec *codec, const char *oldname, + const char *newname) +{ + struct snd_kcontrol *kctl; + + kctl = snd_hda_find_mixer_ctl(codec, oldname); + if (kctl) + strcpy(kctl->id.name, newname); +} + +static void alc1220_fixup_gb_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Gigabyte-ALC1220DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + static const struct hda_fixup alc882_fixups[] = { [ALC882_FIXUP_ABIT_AW9D_MAX] = { .type = HDA_FIXUP_PINS, @@ -2198,6 +2254,10 @@ static const struct hda_fixup alc882_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_bass_chmap, }, + [ALC1220_FIXUP_GB_DUAL_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_gb_dual_codecs, + }, }; static const struct snd_pci_quirk alc882_fixup_tbl[] = { @@ -2267,6 +2327,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1462, 0x7350, "MSI-7350", ALC889_FIXUP_CD), SND_PCI_QUIRK_VENDOR(0x1462, "MSI", ALC882_FIXUP_GPIO3), SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte EP45-DS3/Z87X-UD3H", ALC889_FIXUP_FRONT_HP_NO_PRESENCE), + SND_PCI_QUIRK(0x1458, 0xa0b8, "Gigabyte AZ370-Gaming", ALC1220_FIXUP_GB_DUAL_CODECS), SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", ALC882_FIXUP_ABIT_AW9D_MAX), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD), SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD), @@ -4663,7 +4724,6 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, { 0x1b, 0x21114000 }, /* dock speaker pin */ {} }; - struct snd_kcontrol *kctl; switch (action) { case HDA_FIXUP_ACT_PRE_PROBE: @@ -4678,12 +4738,10 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, /* this is a bit tricky; give more sane names for the main * (tablet) speaker and the dock speaker, respectively */ - kctl = snd_hda_find_mixer_ctl(codec, "Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Dock Speaker Playback Switch"); - kctl = snd_hda_find_mixer_ctl(codec, "Bass Speaker Playback Switch"); - if (kctl) - strcpy(kctl->id.name, "Speaker Playback Switch"); + rename_ctl(codec, "Speaker Playback Switch", + "Dock Speaker Playback Switch"); + rename_ctl(codec, "Bass Speaker Playback Switch", + "Speaker Playback Switch"); break; } } @@ -4766,6 +4824,30 @@ static void alc280_fixup_hp_9480m(struct hda_codec *codec, } } +static void alc233_alc662_fixup_lenovo_dual_codecs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + alc_fixup_dual_codecs(codec, fix, action); + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + /* override card longname to provide a unique UCM profile */ + strcpy(codec->card->longname, "HDAudio-Lenovo-DualCodecs"); + break; + case HDA_FIXUP_ACT_BUILD: + /* rename Capture controls depending on the codec */ + rename_ctl(codec, "Capture Volume", + codec->addr == 0 ? + "Rear-Panel Capture Volume" : + "Front-Panel Capture Volume"); + rename_ctl(codec, "Capture Switch", + codec->addr == 0 ? + "Rear-Panel Capture Switch" : + "Front-Panel Capture Switch"); + break; + } +} + /* for hda_fixup_thinkpad_acpi() */ #include "thinkpad_helper.c" @@ -4833,6 +4915,8 @@ enum { ALC290_FIXUP_SUBWOOFER_HSJACK, ALC269_FIXUP_THINKPAD_ACPI, ALC269_FIXUP_DMIC_THINKPAD_ACPI, + ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, ALC255_FIXUP_HEADSET_MODE, @@ -4872,6 +4956,12 @@ enum { ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, ALC269_FIXUP_ATIV_BOOK_8, ALC221_FIXUP_HP_MIC_NO_PRESENCE, + ALC256_FIXUP_ASUS_HEADSET_MODE, + ALC256_FIXUP_ASUS_MIC, + ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, + ALC233_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc269_fixups[] = { @@ -5289,6 +5379,24 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_THINKPAD_ACPI, }, + [ALC255_FIXUP_ACER_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, + [ALC255_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC255_FIXUP_HEADSET_MODE + }, [ALC255_FIXUP_DELL1_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5579,6 +5687,54 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE }, + [ALC256_FIXUP_ASUS_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, + [ALC256_FIXUP_ASUS_MIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x13, 0x90a60160 }, /* use as internal mic */ + { 0x19, 0x04a11120 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_HEADSET_MODE + }, + [ALC256_FIXUP_ASUS_AIO_GPIO2] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Set up GPIO2 for the speaker amp */ + { 0x01, AC_VERB_SET_GPIO_MASK, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x04 }, + { 0x01, AC_VERB_SET_GPIO_DATA, 0x04 }, + {} + }, + }, + [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, + [ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Enables internal speaker */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x40}, + {0x20, AC_VERB_SET_PROC_COEF, 0x8800}, + {} + }, + .chained = true, + .chain_id = ALC233_FIXUP_ASUS_MIC_NO_PRESENCE + }, + [ALC233_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -5692,15 +5848,27 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), SND_PCI_QUIRK(0x103c, 0x82bf, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x82c0, "HP", ALC221_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), SND_PCI_QUIRK(0x1043, 0x1b13, "Asus U41SV", ALC269_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1bbd, "ASUS Z550MA", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x3030, "ASUS ZN270IE", ALC256_FIXUP_ASUS_AIO_GPIO2), SND_PCI_QUIRK(0x1043, 0x831a, "ASUS P901", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x834a, "ASUS S101", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x8398, "ASUS P1005", ALC269_FIXUP_STEREO_DMIC), @@ -5721,6 +5889,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC233_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x20f2, "Thinkpad SL410/510", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x215e, "Thinkpad L512", ALC269_FIXUP_SKU_IGNORE), SND_PCI_QUIRK(0x17aa, 0x21b8, "Thinkpad Edge 14", ALC269_FIXUP_SKU_IGNORE), @@ -5875,6 +6044,18 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {0x21, 0x03211020} static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1025, "Acer", ALC255_FIXUP_ACER_MIC_NO_PRESENCE, + {0x12, 0x90a601c0}, + {0x14, 0x90171120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1043, "ASUS", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, + {0x1a, 0x90a70130}, + {0x1b, 0x90170110}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0225, 0x1028, "Dell", ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, ALC225_STANDARD_PINS, {0x12, 0xb7a60130}, @@ -5995,6 +6176,14 @@ static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { {0x21, 0x02211020}), SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, ALC256_STANDARD_PINS), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1043, "ASUS", ALC256_FIXUP_ASUS_MIC, + {0x14, 0x90170110}, + {0x1b, 0x90a70130}, + {0x21, 0x03211020}), SND_HDA_PIN_QUIRK(0x10ec0280, 0x103c, "HP", ALC280_FIXUP_HP_GPIO4, {0x12, 0x90a60130}, {0x14, 0x90170110}, @@ -6714,6 +6903,7 @@ enum { ALC668_FIXUP_DELL_DISABLE_AAMIX, ALC668_FIXUP_DELL_XPS13, ALC662_FIXUP_ASUS_Nx50, + ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, ALC668_FIXUP_ASUS_Nx51, ALC891_FIXUP_HEADSET_MODE, ALC891_FIXUP_DELL_MIC_NO_PRESENCE, @@ -6721,6 +6911,7 @@ enum { ALC892_FIXUP_ASROCK_MOBO, ALC662_FIXUP_USI_FUNC, ALC662_FIXUP_USI_HEADSET_MODE, + ALC662_FIXUP_LENOVO_MULTI_CODECS, }; static const struct hda_fixup alc662_fixups[] = { @@ -6967,14 +7158,21 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_BASS_1A }, + [ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_alc668, + .chain_id = ALC662_FIXUP_BASS_CHMAP + }, [ALC668_FIXUP_ASUS_Nx51] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { - {0x1a, 0x90170151}, /* bass speaker */ + { 0x19, 0x03a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x90170151 }, /* bass speaker */ + { 0x1b, 0x03a1113c }, /* use as headset mic, without its own jack detect */ {} }, .chained = true, - .chain_id = ALC662_FIXUP_BASS_CHMAP, + .chain_id = ALC668_FIXUP_ASUS_Nx51_HEADSET_MODE, }, [ALC891_FIXUP_HEADSET_MODE] = { .type = HDA_FIXUP_FUNC, @@ -7019,6 +7217,10 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC662_FIXUP_USI_FUNC }, + [ALC662_FIXUP_LENOVO_MULTI_CODECS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_alc662_fixup_lenovo_dual_codecs, + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -7056,6 +7258,7 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), + SND_PCI_QUIRK(0x17aa, 0x1036, "Lenovo P520", ALC662_FIXUP_LENOVO_MULTI_CODECS), SND_PCI_QUIRK(0x17aa, 0x38af, "Lenovo Ideapad Y550P", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Ideapad Y550", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x1849, 0x5892, "ASRock B150M", ALC892_FIXUP_ASROCK_MOBO), diff --git a/sound/pci/ice1712/delta.c b/sound/pci/ice1712/delta.c index 3bfdc78cbc5f..da5f37b7fdd0 100644 --- a/sound/pci/ice1712/delta.c +++ b/sound/pci/ice1712/delta.c @@ -432,7 +432,7 @@ static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kco return 0; } -static struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = { .access = (SNDRV_CTL_ELEM_ACCESS_READ), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index 5cb587cf360e..ec07136fc288 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -719,7 +719,7 @@ static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, st return ndata != data; } -static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, @@ -728,7 +728,7 @@ static struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = { .count = 8, }; -static struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { +static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Output Sensitivity Switch", .info = snd_ice1712_ewx_io_sense_info, diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index b4aa4c1370a8..1d8612cabb9e 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -279,7 +279,7 @@ static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, stru return val != nval; } -static struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { +static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Mixer To AC97", .info = snd_ice1712_digmix_route_ac97_info, @@ -1410,7 +1410,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = { .private_value = 10, }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH), .info = snd_ice1712_pro_mixer_switch_info, @@ -1432,7 +1432,7 @@ static struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = { .tlv = { .p = db_scale_playback } }; -static struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { +static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME), .info = snd_ice1712_pro_mixer_volume_info, @@ -1630,7 +1630,7 @@ static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_eeprom = { +static const struct snd_kcontrol_new snd_ice1712_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1712 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1666,7 +1666,7 @@ static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_default = +static const struct snd_kcontrol_new snd_ice1712_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1717,7 +1717,7 @@ static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_maskc = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1726,7 +1726,7 @@ static struct snd_kcontrol_new snd_ice1712_spdif_maskc = .get = snd_ice1712_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_ice1712_spdif_maskp = +static const struct snd_kcontrol_new snd_ice1712_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1753,7 +1753,7 @@ static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_spdif_stream = +static const struct snd_kcontrol_new snd_ice1712_spdif_stream = { .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE), @@ -1878,7 +1878,7 @@ static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_ice1712_pro_internal_clock_info, @@ -1943,7 +1943,7 @@ static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcont return change; } -static struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { +static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock Default", .info = snd_ice1712_pro_internal_clock_default_info, @@ -1974,7 +1974,7 @@ static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_ice1712_pro_rate_locking_info, @@ -2005,7 +2005,7 @@ static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { +static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_ice1712_pro_rate_reset_info, @@ -2173,7 +2173,7 @@ static struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = { .put = snd_ice1712_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_ice1712_pro_route_info, @@ -2215,7 +2215,7 @@ static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Volume Rate", .info = snd_ice1712_pro_volume_rate_info, @@ -2248,7 +2248,7 @@ static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 842744e7a139..9cd6e55c0642 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -1598,7 +1598,7 @@ static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_eeprom = { +static const struct snd_kcontrol_new snd_vt1724_eeprom = { .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "ICE1724 EEPROM", .access = SNDRV_CTL_ELEM_ACCESS_READ, @@ -1711,7 +1711,7 @@ static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol, return val != old; } -static struct snd_kcontrol_new snd_vt1724_spdif_default = +static const struct snd_kcontrol_new snd_vt1724_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), @@ -1743,7 +1743,7 @@ static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_spdif_maskc = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskc = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1752,7 +1752,7 @@ static struct snd_kcontrol_new snd_vt1724_spdif_maskc = .get = snd_vt1724_spdif_maskc_get, }; -static struct snd_kcontrol_new snd_vt1724_spdif_maskp = +static const struct snd_kcontrol_new snd_vt1724_spdif_maskp = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1789,7 +1789,7 @@ static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol, return old != val; } -static struct snd_kcontrol_new snd_vt1724_spdif_switch = +static const struct snd_kcontrol_new snd_vt1724_spdif_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* FIXME: the following conflict with IEC958 Playback Route */ @@ -1964,7 +1964,7 @@ static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol, return old_rate != new_rate; } -static struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { +static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Internal Clock", .info = snd_vt1724_pro_internal_clock_info, @@ -1995,7 +1995,7 @@ static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Locking", .info = snd_vt1724_pro_rate_locking_info, @@ -2026,7 +2026,7 @@ static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { +static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Multi Track Rate Reset", .info = snd_vt1724_pro_rate_reset_info, @@ -2151,7 +2151,7 @@ static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route = .put = snd_vt1724_pro_route_analog_put, }; -static struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route", .info = snd_vt1724_pro_route_info, @@ -2187,7 +2187,7 @@ static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { +static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Multi Track Peak", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, diff --git a/sound/pci/lola/lola_mixer.c b/sound/pci/lola/lola_mixer.c index e7fe15dd5a90..cb25acf7bc49 100644 --- a/sound/pci/lola/lola_mixer.c +++ b/sound/pci/lola/lola_mixer.c @@ -645,7 +645,7 @@ static int lola_input_src_put(struct snd_kcontrol *kcontrol, return lola_set_src_config(chip, mask, true); } -static struct snd_kcontrol_new lola_input_src_mixer = { +static const struct snd_kcontrol_new lola_input_src_mixer = { .name = "Digital SRC Capture Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = lola_input_src_info, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index c0f0c349c3ec..f9c3e86d55d5 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -899,7 +899,7 @@ static int lx_control_playback_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new lx_control_playback_switch = { +static const struct snd_kcontrol_new lx_control_playback_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .index = 0, diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c index 51e53497f0ad..4a4616aac787 100644 --- a/sound/pci/mixart/mixart_mixer.c +++ b/sound/pci/mixart/mixart_mixer.c @@ -448,7 +448,7 @@ static int mixart_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ele return changed; } -static struct snd_kcontrol_new mixart_control_output_switch = { +static const struct snd_kcontrol_new mixart_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = mixart_sw_info, /* shared */ @@ -1024,7 +1024,7 @@ static int mixart_monitor_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return changed; } -static struct snd_kcontrol_new mixart_control_monitor_vol = { +static const struct snd_kcontrol_new mixart_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -1091,7 +1091,7 @@ static int mixart_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_e return (changed != 0); } -static struct snd_kcontrol_new mixart_control_monitor_sw = { +static const struct snd_kcontrol_new mixart_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Switch", .info = mixart_sw_info, /* shared */ diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c index 74afb6b75976..e36ed8af55ad 100644 --- a/sound/pci/oxygen/oxygen.c +++ b/sound/pci/oxygen/oxygen.c @@ -767,6 +767,8 @@ static int get_oxygen_model(struct oxygen *chip, [MODEL_FANTASIA] = "TempoTec HiFier Fantasia", [MODEL_SERENADE] = "TempoTec HiFier Serenade", [MODEL_HG2PCI] = "CMI8787-HG2PCI", + [MODEL_XONAR_DG] = "Xonar DG", + [MODEL_XONAR_DGX] = "Xonar DGX", }; chip->model = model_generic; @@ -829,12 +831,8 @@ static int get_oxygen_model(struct oxygen *chip, chip->model.dac_channels_mixer = 2; break; case MODEL_XONAR_DG: - chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DG"; - break; case MODEL_XONAR_DGX: chip->model = model_xonar_dg; - chip->model.shortname = "Xonar DGX"; break; } if (id->driver_data == MODEL_MERIDIAN || diff --git a/sound/pci/pcxhr/pcxhr_mix22.c b/sound/pci/pcxhr/pcxhr_mix22.c index 6a56e5306a65..8b4d0282efb8 100644 --- a/sound/pci/pcxhr/pcxhr_mix22.c +++ b/sound/pci/pcxhr/pcxhr_mix22.c @@ -744,7 +744,7 @@ static int hr222_mic_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_level = { +static const struct snd_kcontrol_new hr222_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -794,7 +794,7 @@ static int hr222_mic_boost_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_control_mic_boost = { +static const struct snd_kcontrol_new hr222_control_mic_boost = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -836,7 +836,7 @@ static int hr222_phantom_power_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new hr222_phantom_power_switch = { +static const struct snd_kcontrol_new hr222_phantom_power_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Phantom Power Switch", .info = hr222_phantom_power_info, diff --git a/sound/pci/pcxhr/pcxhr_mixer.c b/sound/pci/pcxhr/pcxhr_mixer.c index 63136c4f3f3d..36875df30dbf 100644 --- a/sound/pci/pcxhr/pcxhr_mixer.c +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -235,7 +235,7 @@ static int pcxhr_audio_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_output_switch = { +static const struct snd_kcontrol_new pcxhr_control_output_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -460,7 +460,7 @@ static int pcxhr_pcm_sw_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_pcm_switch = { +static const struct snd_kcontrol_new pcxhr_control_pcm_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .count = PCXHR_PLAYBACK_STREAMS, @@ -509,7 +509,7 @@ static int pcxhr_monitor_vol_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_monitor_vol = { +static const struct snd_kcontrol_new pcxhr_control_monitor_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -562,7 +562,7 @@ static int pcxhr_monitor_sw_put(struct snd_kcontrol *kcontrol, return (changed != 0); } -static struct snd_kcontrol_new pcxhr_control_monitor_sw = { +static const struct snd_kcontrol_new pcxhr_control_monitor_sw = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitoring Playback Switch", .info = pcxhr_sw_info, /* shared */ @@ -697,7 +697,7 @@ static int pcxhr_audio_src_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_audio_src = { +static const struct snd_kcontrol_new pcxhr_control_audio_src = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source", .info = pcxhr_audio_src_info, @@ -798,7 +798,7 @@ static int pcxhr_clock_type_put(struct snd_kcontrol *kcontrol, return ret; } -static struct snd_kcontrol_new pcxhr_control_clock_type = { +static const struct snd_kcontrol_new pcxhr_control_clock_type = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Clock Mode", .info = pcxhr_clock_type_info, @@ -842,7 +842,7 @@ static int pcxhr_clock_rate_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new pcxhr_control_clock_rate = { +static const struct snd_kcontrol_new pcxhr_control_clock_rate = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = "Clock Rates", @@ -1017,14 +1017,14 @@ static int pcxhr_iec958_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), .info = pcxhr_iec958_info, @@ -1033,14 +1033,14 @@ static struct snd_kcontrol_new pcxhr_control_playback_iec958 = { .private_value = 0 /* playback */ }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), .info = pcxhr_iec958_info, .get = pcxhr_iec958_mask_get }; -static struct snd_kcontrol_new pcxhr_control_capture_iec958 = { +static const struct snd_kcontrol_new pcxhr_control_capture_iec958 = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT), diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c index 92ad2d7a6bf8..64d3b8eba4bb 100644 --- a/sound/pci/trident/trident_main.c +++ b/sound/pci/trident/trident_main.c @@ -2356,7 +2356,7 @@ static int snd_trident_spdif_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_control = +static const struct snd_kcontrol_new snd_trident_spdif_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), @@ -2419,7 +2419,7 @@ static int snd_trident_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_default = +static const struct snd_kcontrol_new snd_trident_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -2452,7 +2452,7 @@ static int snd_trident_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_trident_spdif_mask = +static const struct snd_kcontrol_new snd_trident_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2514,7 +2514,7 @@ static int snd_trident_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_spdif_stream = +static const struct snd_kcontrol_new snd_trident_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -2564,7 +2564,7 @@ static int snd_trident_ac97_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_ac97_rear_control = +static const struct snd_kcontrol_new snd_trident_ac97_rear_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Rear Path", @@ -2622,7 +2622,7 @@ static int snd_trident_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_vol_music_control = +static const struct snd_kcontrol_new snd_trident_vol_music_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Music Playback Volume", @@ -2633,7 +2633,7 @@ static struct snd_kcontrol_new snd_trident_vol_music_control = .tlv = { .p = db_scale_gvol }, }; -static struct snd_kcontrol_new snd_trident_vol_wave_control = +static const struct snd_kcontrol_new snd_trident_vol_wave_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Wave Playback Volume", @@ -2700,7 +2700,7 @@ static int snd_trident_pcm_vol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_vol_control = +static const struct snd_kcontrol_new snd_trident_pcm_vol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Front Playback Volume", @@ -2764,7 +2764,7 @@ static int snd_trident_pcm_pan_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_pan_control = +static const struct snd_kcontrol_new snd_trident_pcm_pan_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Pan Playback Control", @@ -2821,7 +2821,7 @@ static int snd_trident_pcm_rvol_control_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_crvol, -3175, 25, 1); -static struct snd_kcontrol_new snd_trident_pcm_rvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_rvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Reverb Playback Volume", @@ -2877,7 +2877,7 @@ static int snd_trident_pcm_cvol_control_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_trident_pcm_cvol_control = +static const struct snd_kcontrol_new snd_trident_pcm_cvol_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Chorus Playback Volume", diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 2d8c14e3f8d2..d078e86414c2 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1683,7 +1683,7 @@ static int snd_via8233_dxs3_spdif_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { +static const struct snd_kcontrol_new snd_via8233_dxs3_spdif_control = { .name = SNDRV_CTL_NAME_IEC958("Output ",NONE,SWITCH), .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_via8233_dxs3_spdif_info, @@ -1772,7 +1772,7 @@ static int snd_via8233_pcmdxs_volume_put(struct snd_kcontrol *kcontrol, static const DECLARE_TLV_DB_SCALE(db_scale_dxs, -4650, 150, 1); -static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .name = "PCM Playback Volume", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | @@ -1783,7 +1783,7 @@ static struct snd_kcontrol_new snd_via8233_pcmdxs_volume_control = { .tlv = { .p = db_scale_dxs } }; -static struct snd_kcontrol_new snd_via8233_dxs_volume_control = { +static const struct snd_kcontrol_new snd_via8233_dxs_volume_control = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .device = 0, /* .subdevice set later */ diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index 8e457ea27f89..7df1663ea510 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -945,7 +945,7 @@ static int vx_mic_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v return 0; } -static struct snd_kcontrol_new vx_control_input_level = { +static const struct snd_kcontrol_new vx_control_input_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), @@ -956,7 +956,7 @@ static struct snd_kcontrol_new vx_control_input_level = { .tlv = { .p = db_scale_mic }, }; -static struct snd_kcontrol_new vx_control_mic_level = { +static const struct snd_kcontrol_new vx_control_mic_level = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c index ffee284898b3..fe4ba463b57c 100644 --- a/sound/pci/ymfpci/ymfpci_main.c +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -1316,7 +1316,7 @@ static int snd_ymfpci_spdif_default_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_default = +static const struct snd_kcontrol_new snd_ymfpci_spdif_default = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), @@ -1344,7 +1344,7 @@ static int snd_ymfpci_spdif_mask_get(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_spdif_mask = +static const struct snd_kcontrol_new snd_ymfpci_spdif_mask = { .access = SNDRV_CTL_ELEM_ACCESS_READ, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1391,7 +1391,7 @@ static int snd_ymfpci_spdif_stream_put(struct snd_kcontrol *kcontrol, return change; } -static struct snd_kcontrol_new snd_ymfpci_spdif_stream = +static const struct snd_kcontrol_new snd_ymfpci_spdif_stream = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_INACTIVE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1439,7 +1439,7 @@ static int snd_ymfpci_drec_source_put(struct snd_kcontrol *kcontrol, struct snd_ return reg != old_reg; } -static struct snd_kcontrol_new snd_ymfpci_drec_source = { +static const struct snd_kcontrol_new snd_ymfpci_drec_source = { .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Direct Recording Source", @@ -1609,7 +1609,7 @@ static int snd_ymfpci_put_dup4ch(struct snd_kcontrol *kcontrol, struct snd_ctl_e return change; } -static struct snd_kcontrol_new snd_ymfpci_dup4ch = { +static const struct snd_kcontrol_new snd_ymfpci_dup4ch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "4ch Duplication", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, @@ -1712,7 +1712,7 @@ static int snd_ymfpci_gpio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ return 0; } -static struct snd_kcontrol_new snd_ymfpci_rear_shared = { +static const struct snd_kcontrol_new snd_ymfpci_rear_shared = { .name = "Shared Rear/Line-In Switch", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .info = snd_ymfpci_gpio_sw_info, @@ -1776,7 +1776,7 @@ static int snd_ymfpci_pcm_vol_put(struct snd_kcontrol *kcontrol, return 0; } -static struct snd_kcontrol_new snd_ymfpci_pcm_volume = { +static const struct snd_kcontrol_new snd_ymfpci_pcm_volume = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "PCM Playback Volume", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 182d92efc7c8..c0abad2067e1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -47,6 +47,7 @@ source "sound/soc/cirrus/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" +source "sound/soc/hisilicon/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" @@ -63,6 +64,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sirf/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sti/Kconfig" +source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9a30f21d16ee..39c27a58158d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ +obj-$(CONFIG_SND_SOC) += hisilicon/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += img/ obj-$(CONFIG_SND_SOC) += intel/ @@ -43,6 +44,7 @@ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sirf/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sti/ +obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/blackfin/bfin-eval-adau1373.c b/sound/soc/blackfin/bfin-eval-adau1373.c index 72ac78988426..64b88fdc1f6c 100644 --- a/sound/soc/blackfin/bfin-eval-adau1373.c +++ b/sound/soc/blackfin/bfin-eval-adau1373.c @@ -119,7 +119,7 @@ static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; } -static struct snd_soc_ops bfin_eval_adau1373_ops = { +static const struct snd_soc_ops bfin_eval_adau1373_ops = { .hw_params = bfin_eval_adau1373_hw_params, }; diff --git a/sound/soc/blackfin/bfin-eval-adav80x.c b/sound/soc/blackfin/bfin-eval-adav80x.c index 1037477d10b2..99e5ecabdcda 100644 --- a/sound/soc/blackfin/bfin-eval-adav80x.c +++ b/sound/soc/blackfin/bfin-eval-adav80x.c @@ -64,7 +64,7 @@ static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops bfin_eval_adav80x_ops = { +static const struct snd_soc_ops bfin_eval_adav80x_ops = { .hw_params = bfin_eval_adav80x_hw_params, }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e49e9da7f1f6..883ed4c8a551 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS35L32 if I2C select SND_SOC_CS35L33 if I2C select SND_SOC_CS35L34 if I2C + select SND_SOC_CS35L35 if I2C select SND_SOC_CS42L42 if I2C select SND_SOC_CS42L51_I2C if I2C select SND_SOC_CS42L52 if I2C && INPUT @@ -69,9 +70,11 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7219 if I2C select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C + select SND_SOC_DIO2125 select SND_SOC_DMIC select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES7134 select SND_SOC_GTM601 select SND_SOC_HDAC_HDMI select SND_SOC_ICS43432 @@ -89,6 +92,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX9867 if I2C select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C + select SND_SOC_MAX98927 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9860 if I2C select SND_SOC_MAX9768 if I2C @@ -97,6 +101,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ML26124 if I2C select SND_SOC_NAU8540 if I2C select SND_SOC_NAU8810 if I2C + select SND_SOC_NAU8824 if I2C select SND_SOC_NAU8825 if I2C select SND_SOC_HDMI_CODEC select SND_SOC_PCM1681 if I2C @@ -303,12 +308,14 @@ config SND_SOC_ADAU1761 select SND_SOC_ADAU17X1 config SND_SOC_ADAU1761_I2C - tristate + tristate "Analog Devices AU1761 CODEC - I2C" + depends on I2C select SND_SOC_ADAU1761 select REGMAP_I2C config SND_SOC_ADAU1761_SPI - tristate + tristate "Analog Devices AU1761 CODEC - SPI" + depends on SPI select SND_SOC_ADAU1761 select REGMAP_SPI @@ -408,6 +415,10 @@ config SND_SOC_CS35L34 tristate "Cirrus Logic CS35L34 CODEC" depends on I2C +config SND_SOC_CS35L35 + tristate "Cirrus Logic CS35L35 CODEC" + depends on I2C + config SND_SOC_CS42L42 tristate "Cirrus Logic CS42L42 CODEC" depends on I2C @@ -516,6 +527,10 @@ config SND_SOC_DA732X config SND_SOC_DA9055 tristate +config SND_SOC_DIO2125 + tristate "Dioo DIO2125 Amplifier" + select GPIOLIB + config SND_SOC_DMIC tristate @@ -525,6 +540,9 @@ config SND_SOC_HDMI_CODEC select SND_PCM_IEC958 select HDMI +config SND_SOC_ES7134 + tristate "Everest Semi ES7134 CODEC" + config SND_SOC_ES8328 tristate @@ -588,6 +606,10 @@ config SND_SOC_MAX98925 config SND_SOC_MAX98926 tristate +config SND_SOC_MAX98927 + tristate "Maxim Integrated MAX98927 Speaker Amplifier" + depends on I2C + config SND_SOC_MAX9850 tristate @@ -1116,6 +1138,10 @@ config SND_SOC_NAU8810 tristate "Nuvoton Technology Corporation NAU88C10 CODEC" depends on I2C +config SND_SOC_NAU8824 + tristate "Nuvoton Technology Corporation NAU88L24 CODEC" + depends on I2C + config SND_SOC_NAU8825 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1796cb987e71..28a63fdaf982 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs35l34-objs := cs35l34.o +snd-soc-cs35l35-objs := cs35l35.o snd-soc-cs42l42-objs := cs42l42.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o @@ -63,6 +64,7 @@ snd-soc-da7219-objs := da7219.o da7219-aad.o snd-soc-da732x-objs := da732x.o snd-soc-da9055-objs := da9055.o snd-soc-dmic-objs := dmic.o +snd-soc-es7134-objs := es7134.o snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o @@ -84,6 +86,7 @@ snd-soc-max98371-objs := max98371.o snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o +snd-soc-max98927-objs := max98927.o snd-soc-max9850-objs := max9850.o snd-soc-max9860-objs := max9860.o snd-soc-mc13783-objs := mc13783.o @@ -92,6 +95,7 @@ snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o snd-soc-nau8540-objs := nau8540.o snd-soc-nau8810-objs := nau8810.o +snd-soc-nau8824-objs := nau8824.o snd-soc-nau8825-objs := nau8825.o snd-soc-hdmi-codec-objs := hdmi-codec.o snd-soc-pcm1681-objs := pcm1681.o @@ -221,6 +225,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o # Amp +snd-soc-dio2125-objs := dio2125.o snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o snd-soc-tpa6130a2-objs := tpa6130a2.o @@ -269,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o +obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o @@ -293,6 +299,7 @@ obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_ES7134) += snd-soc-es7134.o obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o @@ -313,6 +320,7 @@ obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o +obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o @@ -321,6 +329,7 @@ obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o +obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o @@ -448,6 +457,7 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp +obj-$(CONFIG_SND_SOC_DIO2125) += snd-soc-dio2125.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index e819dd8c82fd..b2dfddead227 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -75,6 +75,12 @@ #define DFS_DOUBLE_SPEED (1 << 2) #define DFS_QUAD_SPEED (2 << 2) +/* ICTRL */ +#define ICTRL_MASK (0x3) + +/* OCTRL */ +#define OCTRL_MASK (0x3F) + struct ak4613_formats { unsigned int width; unsigned int fmt; @@ -365,8 +371,8 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, CTRL1, FMT_MASK, fmt_ctrl); snd_soc_update_bits(codec, CTRL2, DFS_MASK, ctrl2); - snd_soc_write(codec, ICTRL, priv->ic); - snd_soc_write(codec, OCTRL, priv->oc); + snd_soc_update_bits(codec, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_update_bits(codec, OCTRL, OCTRL_MASK, priv->oc); hw_params_end: if (ret < 0) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c new file mode 100644 index 000000000000..f8aef5869b03 --- /dev/null +++ b/sound/soc/codecs/cs35l35.c @@ -0,0 +1,1580 @@ +/* + * cs35l35.c -- CS35L35 ALSA SoC audio driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/gpio.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/cs35l35.h> +#include <linux/of_irq.h> +#include <linux/completion.h> + +#include "cs35l35.h" + +/* + * Some fields take zero as a valid value so use a high bit flag that won't + * get written to the device to mark those. + */ +#define CS35L35_VALID_PDATA 0x80000000 + +static const struct reg_default cs35l35_reg[] = { + {CS35L35_PWRCTL1, 0x01}, + {CS35L35_PWRCTL2, 0x11}, + {CS35L35_PWRCTL3, 0x00}, + {CS35L35_CLK_CTL1, 0x04}, + {CS35L35_CLK_CTL2, 0x12}, + {CS35L35_CLK_CTL3, 0xCF}, + {CS35L35_SP_FMT_CTL1, 0x20}, + {CS35L35_SP_FMT_CTL2, 0x00}, + {CS35L35_SP_FMT_CTL3, 0x02}, + {CS35L35_MAG_COMP_CTL, 0x00}, + {CS35L35_AMP_INP_DRV_CTL, 0x01}, + {CS35L35_AMP_DIG_VOL_CTL, 0x12}, + {CS35L35_AMP_DIG_VOL, 0x00}, + {CS35L35_ADV_DIG_VOL, 0x00}, + {CS35L35_PROTECT_CTL, 0x06}, + {CS35L35_AMP_GAIN_AUD_CTL, 0x13}, + {CS35L35_AMP_GAIN_PDM_CTL, 0x00}, + {CS35L35_AMP_GAIN_ADV_CTL, 0x00}, + {CS35L35_GPI_CTL, 0x00}, + {CS35L35_BST_CVTR_V_CTL, 0x00}, + {CS35L35_BST_PEAK_I, 0x07}, + {CS35L35_BST_RAMP_CTL, 0x85}, + {CS35L35_BST_CONV_COEF_1, 0x24}, + {CS35L35_BST_CONV_COEF_2, 0x24}, + {CS35L35_BST_CONV_SLOPE_COMP, 0x4E}, + {CS35L35_BST_CONV_SW_FREQ, 0x04}, + {CS35L35_CLASS_H_CTL, 0x0B}, + {CS35L35_CLASS_H_HEADRM_CTL, 0x0B}, + {CS35L35_CLASS_H_RELEASE_RATE, 0x08}, + {CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41}, + {CS35L35_CLASS_H_VP_CTL, 0xC5}, + {CS35L35_VPBR_CTL, 0x0A}, + {CS35L35_VPBR_VOL_CTL, 0x90}, + {CS35L35_VPBR_TIMING_CTL, 0x6A}, + {CS35L35_VPBR_MODE_VOL_CTL, 0x00}, + {CS35L35_SPKR_MON_CTL, 0xC0}, + {CS35L35_IMON_SCALE_CTL, 0x30}, + {CS35L35_AUDIN_RXLOC_CTL, 0x00}, + {CS35L35_ADVIN_RXLOC_CTL, 0x80}, + {CS35L35_VMON_TXLOC_CTL, 0x00}, + {CS35L35_IMON_TXLOC_CTL, 0x80}, + {CS35L35_VPMON_TXLOC_CTL, 0x04}, + {CS35L35_VBSTMON_TXLOC_CTL, 0x84}, + {CS35L35_VPBR_STATUS_TXLOC_CTL, 0x04}, + {CS35L35_ZERO_FILL_LOC_CTL, 0x00}, + {CS35L35_AUDIN_DEPTH_CTL, 0x0F}, + {CS35L35_SPKMON_DEPTH_CTL, 0x0F}, + {CS35L35_SUPMON_DEPTH_CTL, 0x0F}, + {CS35L35_ZEROFILL_DEPTH_CTL, 0x00}, + {CS35L35_MULT_DEV_SYNCH1, 0x02}, + {CS35L35_MULT_DEV_SYNCH2, 0x80}, + {CS35L35_PROT_RELEASE_CTL, 0x00}, + {CS35L35_DIAG_MODE_REG_LOCK, 0x00}, + {CS35L35_DIAG_MODE_CTL_1, 0x40}, + {CS35L35_DIAG_MODE_CTL_2, 0x00}, + {CS35L35_INT_MASK_1, 0xFF}, + {CS35L35_INT_MASK_2, 0xFF}, + {CS35L35_INT_MASK_3, 0xFF}, + {CS35L35_INT_MASK_4, 0xFF}, + +}; + +static bool cs35l35_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_DEVID_AB ... CS35L35_PWRCTL3: + case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3: + case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL: + case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I: + case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ: + case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL: + case CS35L35_CLASS_H_STATUS: + case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL: + case CS35L35_VPBR_ATTEN_STATUS: + case CS35L35_SPKR_MON_CTL: + case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL: + case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL: + case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2: + case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static int cs35l35_wait_for_pdn(struct cs35l35_private *cs35l35) +{ + int ret; + + if (cs35l35->pdata.ext_bst) { + usleep_range(5000, 5500); + return 0; + } + + reinit_completion(&cs35l35->pdn_done); + + ret = wait_for_completion_timeout(&cs35l35->pdn_done, + msecs_to_jiffies(100)); + if (ret == 0) { + dev_err(cs35l35->dev, "PDN_DONE did not complete\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 0 << CS35L35_MCLK_DIS_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 0 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 1 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 1); + + /* Already muted, so disable volume ramp for faster shutdown */ + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, 0); + + ret = cs35l35_wait_for_pdn(cs35l35); + + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 1 << CS35L35_MCLK_DIS_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, + 1 << CS35L35_AMP_DIGSFT_SHIFT); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + unsigned int reg[4]; + int i; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(5000, 5100); + /* If in PDM mode we must use VP for Voltage control */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + 0 << CS35L35_BST_CTL_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 0); + + for (i = 0; i < 2; i++) + regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1, + ®, ARRAY_SIZE(reg)); + + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, + 1 << CS35L35_AMP_MUTE_SHIFT); + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMD: + usleep_range(5000, 5100); + /* + * If PDM mode we should switch back to pdata value + * for Voltage control when we go down + */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl + << CS35L35_BST_CTL_SHIFT); + + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + } + return 0; +} + +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l35_aud_controls[] = { + SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Audio Volume", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0, + amp_gain_tlv), + SOC_SINGLE_TLV("PDM Volume", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_kcontrol_new cs35l35_adv_controls[] = { + SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Advisory Volume", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1, + cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1), + + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VBST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0, + cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l35_audio_map[] = { + {"VPMON ADC", NULL, "VP"}, + {"VBSTMON ADC", NULL, "VBST"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VMON ADC", NULL, "VSENSE"}, + {"SDOUT", NULL, "IMON ADC"}, + {"SDOUT", NULL, "VMON ADC"}, + {"SDOUT", NULL, "VBSTMON ADC"}, + {"SDOUT", NULL, "VPMON ADC"}, + {"AMP Capture", NULL, "SDOUT"}, + + {"SDIN", NULL, "AMP Playback"}, + {"CLASS H", NULL, "SDIN"}, + {"Main AMP", NULL, "CLASS H"}, + {"SPK", NULL, "Main AMP"}, +}; + +static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs35l35->i2s_mode = true; + cs35l35->pdm_mode = false; + break; + case SND_SOC_DAIFMT_PDM: + cs35l35->pdm_mode = true; + cs35l35->i2s_mode = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l35_sysclk_config { + int sysclk; + int srate; + u8 clk_cfg; +}; + +static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = { + + /* SYSCLK, Sample Rate, Serial Port Cfg */ + {5644800, 44100, 0x00}, + {5644800, 88200, 0x40}, + {6144000, 48000, 0x10}, + {6144000, 96000, 0x50}, + {11289600, 44100, 0x01}, + {11289600, 88200, 0x41}, + {11289600, 176400, 0x81}, + {12000000, 44100, 0x03}, + {12000000, 48000, 0x13}, + {12000000, 88200, 0x43}, + {12000000, 96000, 0x53}, + {12000000, 176400, 0x83}, + {12000000, 192000, 0x93}, + {12288000, 48000, 0x11}, + {12288000, 96000, 0x51}, + {12288000, 192000, 0x91}, + {13000000, 44100, 0x07}, + {13000000, 48000, 0x17}, + {13000000, 88200, 0x47}, + {13000000, 96000, 0x57}, + {13000000, 176400, 0x87}, + {13000000, 192000, 0x97}, + {22579200, 44100, 0x02}, + {22579200, 88200, 0x42}, + {22579200, 176400, 0x82}, + {24000000, 44100, 0x0B}, + {24000000, 48000, 0x1B}, + {24000000, 88200, 0x4B}, + {24000000, 96000, 0x5B}, + {24000000, 176400, 0x8B}, + {24000000, 192000, 0x9B}, + {24576000, 48000, 0x12}, + {24576000, 96000, 0x52}, + {24576000, 192000, 0x92}, + {26000000, 44100, 0x0F}, + {26000000, 48000, 0x1F}, + {26000000, 88200, 0x4F}, + {26000000, 96000, 0x5F}, + {26000000, 176400, 0x8F}, + {26000000, 192000, 0x9F}, +}; + +static int cs35l35_get_clk_config(int sysclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) { + if (cs35l35_clk_ctl[i].sysclk == sysclk && + cs35l35_clk_ctl[i].srate == srate) + return cs35l35_clk_ctl[i].clk_cfg; + } + return -EINVAL; +} + +static int cs35l35_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 cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + int srate = params_rate(params); + int ret = 0; + u8 sp_sclks; + int audin_format; + int errata_chk; + + int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate); + + if (clk_ctl < 0) { + dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n", + cs35l35->sysclk, srate); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2, + CS35L35_CLK_CTL2_MASK, clk_ctl); + if (ret != 0) { + dev_err(codec->dev, "Failed to set port config %d\n", ret); + return ret; + } + + /* + * Rev A0 Errata + * When configured for the weak-drive detection path (CH_WKFET_DIS = 0) + * the Class H algorithm does not enable weak-drive operation for + * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10 + */ + errata_chk = clk_ctl & CS35L35_SP_RATE_MASK; + + if (classh->classh_wk_fet_disable == 0x00 && + (errata_chk == 0x01 || errata_chk == 0x03)) { + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + 0 << CS35L35_CH_WKFET_DEL_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fet config %d\n", + ret); + return ret; + } + } + + /* + * You can pull more Monitor data from the SDOUT pin than going to SDIN + * Just make sure your SCLK is fast enough to fill the frame + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_width(params)) { + case 8: + audin_format = CS35L35_SDIN_DEPTH_8; + break; + case 16: + audin_format = CS35L35_SDIN_DEPTH_16; + break; + case 24: + audin_format = CS35L35_SDIN_DEPTH_24; + break; + default: + dev_err(codec->dev, "Unsupported Width %d\n", + params_width(params)); + return -EINVAL; + } + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_AUDIN_DEPTH_MASK, + audin_format << + CS35L35_AUDIN_DEPTH_SHIFT); + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_ADVIN_DEPTH_MASK, + audin_format << + CS35L35_ADVIN_DEPTH_SHIFT); + } + } + + if (cs35l35->i2s_mode) { + /* We have to take the SCLK to derive num sclks + * to configure the CLOCK_CTL3 register correctly + */ + if ((cs35l35->sclk / srate) % 4) { + dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n", + cs35l35->sclk, srate); + return -EINVAL; + } + sp_sclks = ((cs35l35->sclk / srate) / 4) - 1; + + /* Only certain ratios are supported in I2S Slave Mode */ + if (cs35l35->slave_mode) { + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_48FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + } + } else { + /* Only certain ratios supported in I2S MASTER Mode */ + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + } + } + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLK_CTL3, + CS35L35_SP_SCLKS_MASK, sp_sclks << + CS35L35_SP_SCLKS_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fsclk %d\n", ret); + return ret; + } + } + + return ret; +} + +static const unsigned int cs35l35_src_rates[] = { + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_constraints = { + .count = ARRAY_SIZE(cs35l35_src_rates), + .list = cs35l35_src_rates, +}; + +static int cs35l35_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 0 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static const unsigned int cs35l35_pdm_rates[] = { + 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = { + .count = ARRAY_SIZE(cs35l35_pdm_rates), + .list = cs35l35_pdm_rates, +}; + +static int cs35l35_pdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l35_pdm_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 1 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + /* Need the SCLK Frequency regardless of sysclk source for I2S */ + cs35l35->sclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops cs35l35_ops = { + .startup = cs35l35_pcm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, + .set_sysclk = cs35l35_dai_set_sysclk, +}; + +static const struct snd_soc_dai_ops cs35l35_pdm_ops = { + .startup = cs35l35_pdm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, +}; + +static struct snd_soc_dai_driver cs35l35_dai[] = { + { + .name = "cs35l35-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_ops, + .symmetric_rates = 1, + }, + { + .name = "cs35l35-pdm", + .id = 1, + .playback = { + .stream_name = "PDM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_pdm_ops, + }, +}; + +static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int clksrc; + int ret = 0; + + switch (clk_id) { + case 0: + clksrc = CS35L35_CLK_SOURCE_MCLK; + break; + case 1: + clksrc = CS35L35_CLK_SOURCE_SCLK; + break; + case 2: + clksrc = CS35L35_CLK_SOURCE_PDM; + break; + default: + dev_err(codec->dev, "Invalid CLK Source\n"); + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 6144000: + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 22579200: + case 24000000: + case 24576000: + case 26000000: + cs35l35->sysclk = freq; + break; + default: + dev_err(codec->dev, "Invalid CLK Frequency Input : %d\n", freq); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_CLK_SOURCE_MASK, + clksrc << CS35L35_CLK_SOURCE_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set sysclk %d\n", ret); + return ret; + } + + return ret; +} + +static int cs35l35_codec_probe(struct snd_soc_codec *codec) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg; + int ret; + + /* Set Platform Data */ + if (cs35l35->pdata.bst_vctl) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl); + + if (cs35l35->pdata.bst_ipk) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I, + CS35L35_BST_IPK_MASK, + cs35l35->pdata.bst_ipk << + CS35L35_BST_IPK_SHIFT); + + if (cs35l35->pdata.gain_zc) + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_GAIN_ZC_MASK, + cs35l35->pdata.gain_zc << + CS35L35_AMP_GAIN_ZC_SHIFT); + + if (cs35l35->pdata.aud_channel) + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_RXLOC_CTL, + CS35L35_AUD_IN_LR_MASK, + cs35l35->pdata.aud_channel << + CS35L35_AUD_IN_LR_SHIFT); + + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_ADVIN_RXLOC_CTL, + CS35L35_ADV_IN_LR_MASK, + cs35l35->pdata.adv_channel << + CS35L35_ADV_IN_LR_SHIFT); + if (cs35l35->pdata.shared_bst) + regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL, + CS35L35_CH_STEREO_MASK, + 1 << CS35L35_CH_STEREO_SHIFT); + ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls, + ARRAY_SIZE(cs35l35_adv_controls)); + if (ret) + return ret; + } + + if (cs35l35->pdata.sp_drv_str) + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_SP_DRV_MASK, + cs35l35->pdata.sp_drv_str << + CS35L35_SP_DRV_SHIFT); + if (cs35l35->pdata.sp_drv_unused) + regmap_update_bits(cs35l35->regmap, CS35L35_SP_FMT_CTL3, + CS35L35_SP_I2S_DRV_MASK, + cs35l35->pdata.sp_drv_unused << + CS35L35_SP_I2S_DRV_SHIFT); + + if (classh->classh_algo_enable) { + if (classh->classh_bst_override) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_OVR_MASK, + classh->classh_bst_override << + CS35L35_CH_BST_OVR_SHIFT); + if (classh->classh_bst_max_limit) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_LIM_MASK, + classh->classh_bst_max_limit << + CS35L35_CH_BST_LIM_SHIFT); + if (classh->classh_mem_depth) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_MEM_DEPTH_MASK, + classh->classh_mem_depth << + CS35L35_CH_MEM_DEPTH_SHIFT); + if (classh->classh_headroom) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_HEADRM_CTL, + CS35L35_CH_HDRM_CTL_MASK, + classh->classh_headroom << + CS35L35_CH_HDRM_CTL_SHIFT); + if (classh->classh_release_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_RELEASE_RATE, + CS35L35_CH_REL_RATE_MASK, + classh->classh_release_rate << + CS35L35_CH_REL_RATE_SHIFT); + if (classh->classh_wk_fet_disable) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DIS_MASK, + classh->classh_wk_fet_disable << + CS35L35_CH_WKFET_DIS_SHIFT); + if (classh->classh_wk_fet_delay) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + classh->classh_wk_fet_delay << + CS35L35_CH_WKFET_DEL_SHIFT); + if (classh->classh_wk_fet_thld) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_THLD_MASK, + classh->classh_wk_fet_thld << + CS35L35_CH_WKFET_THLD_SHIFT); + if (classh->classh_vpch_auto) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_AUTO_MASK, + classh->classh_vpch_auto << + CS35L35_CH_VP_AUTO_SHIFT); + if (classh->classh_vpch_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_RATE_MASK, + classh->classh_vpch_rate << + CS35L35_CH_VP_RATE_SHIFT); + if (classh->classh_vpch_man) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_MAN_MASK, + classh->classh_vpch_man << + CS35L35_CH_VP_MAN_SHIFT); + } + + if (monitor_config->is_present) { + if (monitor_config->vmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_VMON_DEPTH_MASK, + monitor_config->vmon_dpth << + CS35L35_VMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->imon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_IMON_DEPTH_MASK, + monitor_config->imon_dpth << + CS35L35_IMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->imon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->imon_frm << + CS35L35_MON_FRM_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_SCALE_CTL, + CS35L35_IMON_SCALE_MASK, + monitor_config->imon_scale << + CS35L35_IMON_SCALE_SHIFT); + } + if (monitor_config->vpmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VPMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vbstmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VBSTMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VBSTMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vbstmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vbstmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vpbrstat_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPBRSTAT_DEPTH_MASK, + monitor_config->vpbrstat_dpth << + CS35L35_VPBRSTAT_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpbrstat_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpbrstat_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->zerofill_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_ZEROFILL_DEPTH_MASK, + monitor_config->zerofill_dpth << + CS35L35_ZEROFILL_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->zerofill_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->zerofill_frm << + CS35L35_MON_FRM_SHIFT); + } + } + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = { + .probe = cs35l35_codec_probe, + .set_sysclk = cs35l35_codec_set_sysclk, + .component_driver = { + .dapm_widgets = cs35l35_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets), + + .dapm_routes = cs35l35_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map), + + .controls = cs35l35_aud_controls, + .num_controls = ARRAY_SIZE(cs35l35_aud_controls), + }, + +}; + +static struct regmap_config cs35l35_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L35_MAX_REGISTER, + .reg_defaults = cs35l35_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l35_reg), + .volatile_reg = cs35l35_volatile_register, + .readable_reg = cs35l35_readable_register, + .precious_reg = cs35l35_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t cs35l35_irq(int irq, void *data) +{ + struct cs35l35_private *cs35l35 = data; + unsigned int sticky1, sticky2, sticky3, sticky4; + unsigned int mask1, mask2, mask3, mask4, current1; + + /* ack the irq by reading all status registers */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1); + + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1); + + /* Check to see if unmasked bits are active */ + if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3) + && !(sticky4 & ~mask4)) + return IRQ_NONE; + + if (sticky2 & CS35L35_PDN_DONE) + complete(&cs35l35->pdn_done); + + /* read the current values */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, ¤t1); + + /* handle the interrupts */ + if (sticky1 & CS35L35_CAL_ERR) { + dev_crit(cs35l35->dev, "Calibration Error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_CAL_ERR)) { + pr_debug("%s : Cal error release\n", __func__); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, + CS35L35_CAL_ERR_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + } + } + + if (sticky1 & CS35L35_AMP_SHORT) { + dev_crit(cs35l35->dev, "AMP Short Error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_AMP_SHORT)) { + dev_dbg(cs35l35->dev, "Amp short error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, + CS35L35_SHORT_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTW) { + dev_warn(cs35l35->dev, "Over temperature warning\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTW)) { + dev_dbg(cs35l35->dev, "Over temperature warn release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, + CS35L35_OTW_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTE) { + dev_crit(cs35l35->dev, "Over temperature error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTE)) { + dev_dbg(cs35l35->dev, "Over temperature error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, + CS35L35_OTE_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + } + } + + if (sticky3 & CS35L35_BST_HIGH) { + dev_crit(cs35l35->dev, "VBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky3 & CS35L35_LBST_SHORT) { + dev_crit(cs35l35->dev, "LBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky2 & CS35L35_VPBR_ERR) + dev_dbg(cs35l35->dev, "Error: Reactive Brownout\n"); + + if (sticky4 & CS35L35_VMON_OVFL) + dev_dbg(cs35l35->dev, "Error: VMON overflow\n"); + + if (sticky4 & CS35L35_IMON_OVFL) + dev_dbg(cs35l35->dev, "Error: IMON overflow\n"); + + return IRQ_HANDLED; +} + + +static int cs35l35_handle_of_data(struct i2c_client *i2c_client, + struct cs35l35_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + struct device_node *classh, *signal_format; + struct classh_cfg *classh_config = &pdata->classh_algo; + struct monitor_cfg *monitor_config = &pdata->mon_cfg; + unsigned int val32 = 0; + u8 monitor_array[4]; + const int imon_array_size = ARRAY_SIZE(monitor_array); + const int mon_array_size = imon_array_size - 1; + int ret = 0; + + if (!np) + return 0; + + pdata->bst_pdn_fet_on = of_property_read_bool(np, + "cirrus,boost-pdn-fet-on"); + + ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32); + if (ret >= 0) { + if (val32 < 2600 || val32 > 9000) { + dev_err(&i2c_client->dev, + "Invalid Boost Voltage %d mV\n", val32); + return -EINVAL; + } + pdata->bst_vctl = ((val32 - 2600) / 100) + 1; + } + + ret = of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val32); + if (ret >= 0) { + if (val32 < 1680 || val32 > 4480) { + dev_err(&i2c_client->dev, + "Invalid Boost Peak Current %u mA\n", val32); + return -EINVAL; + } + + pdata->bst_ipk = (val32 - 1680) / 110; + } + + if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0) + pdata->sp_drv_str = val32; + if (of_property_read_u32(np, "cirrus,sp-drv-unused", &val32) >= 0) + pdata->sp_drv_unused = val32 | CS35L35_VALID_PDATA; + + pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config"); + + if (pdata->stereo) { + ret = of_property_read_u32(np, "cirrus,audio-channel", &val32); + if (ret >= 0) + pdata->aud_channel = val32; + + ret = of_property_read_u32(np, "cirrus,advisory-channel", + &val32); + if (ret >= 0) + pdata->adv_channel = val32; + + pdata->shared_bst = of_property_read_bool(np, + "cirrus,shared-boost"); + } + + pdata->ext_bst = of_property_read_bool(np, "cirrus,external-boost"); + + pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc"); + + classh = of_get_child_by_name(np, "cirrus,classh-internal-algo"); + classh_config->classh_algo_enable = classh ? true : false; + + if (classh_config->classh_algo_enable) { + classh_config->classh_bst_override = + of_property_read_bool(np, "cirrus,classh-bst-overide"); + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-mem-depth", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_mem_depth = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-release-rate", + &val32); + if (ret >= 0) + classh_config->classh_release_rate = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-headroom", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_headroom = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-wk-fet-disable", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_disable = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-delay", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_wk_fet_delay = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-thld", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_thld = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-auto", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_auto = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-rate", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_rate = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-man", + &val32); + if (ret >= 0) + classh_config->classh_vpch_man = val32; + } + of_node_put(classh); + + /* frame depth location */ + signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format"); + monitor_config->is_present = signal_format ? true : false; + if (monitor_config->is_present) { + ret = of_property_read_u8_array(signal_format, "cirrus,imon", + monitor_array, imon_array_size); + if (!ret) { + monitor_config->imon_specs = true; + monitor_config->imon_dpth = monitor_array[0]; + monitor_config->imon_loc = monitor_array[1]; + monitor_config->imon_frm = monitor_array[2]; + monitor_config->imon_scale = monitor_array[3]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vmon_specs = true; + monitor_config->vmon_dpth = monitor_array[0]; + monitor_config->vmon_loc = monitor_array[1]; + monitor_config->vmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpmon_specs = true; + monitor_config->vpmon_dpth = monitor_array[0]; + monitor_config->vpmon_loc = monitor_array[1]; + monitor_config->vpmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vbstmon_specs = true; + monitor_config->vbstmon_dpth = monitor_array[0]; + monitor_config->vbstmon_loc = monitor_array[1]; + monitor_config->vbstmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpbrstat_specs = true; + monitor_config->vpbrstat_dpth = monitor_array[0]; + monitor_config->vpbrstat_loc = monitor_array[1]; + monitor_config->vpbrstat_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,zerofill", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->zerofill_specs = true; + monitor_config->zerofill_dpth = monitor_array[0]; + monitor_config->zerofill_loc = monitor_array[1]; + monitor_config->zerofill_frm = monitor_array[2]; + } + } + of_node_put(signal_format); + + return 0; +} + +/* Errata Rev A0 */ +static const struct reg_sequence cs35l35_errata_patch[] = { + + { 0x7F, 0x99 }, + { 0x00, 0x99 }, + { 0x52, 0x22 }, + { 0x04, 0x14 }, + { 0x6D, 0x44 }, + { 0x24, 0x10 }, + { 0x58, 0xC4 }, + { 0x00, 0x98 }, + { 0x18, 0x08 }, + { 0x00, 0x00 }, + { 0x7F, 0x00 }, +}; + +static int cs35l35_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l35_private *cs35l35; + struct device *dev = &i2c_client->dev; + struct cs35l35_platform_data *pdata = dev_get_platdata(dev); + int i; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs35l35 = devm_kzalloc(dev, sizeof(struct cs35l35_private), GFP_KERNEL); + if (!cs35l35) + return -ENOMEM; + + cs35l35->dev = dev; + + i2c_set_clientdata(i2c_client, cs35l35); + cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); + if (IS_ERR(cs35l35->regmap)) { + ret = PTR_ERR(cs35l35->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++) + cs35l35->supplies[i].supply = cs35l35_supplies[i]; + + cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); + + ret = devm_regulator_bulk_get(dev, cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request core supplies: %d\n", ret); + return ret; + } + + if (pdata) { + cs35l35->pdata = *pdata; + } else { + pdata = devm_kzalloc(dev, sizeof(struct cs35l35_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + if (i2c_client->dev.of_node) { + ret = cs35l35_handle_of_data(i2c_client, pdata); + if (ret != 0) + return ret; + + } + cs35l35->pdata = *pdata; + } + + ret = regulator_bulk_enable(cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + /* returning NULL can be valid if in stereo mode */ + cs35l35->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs35l35->reset_gpio)) { + ret = PTR_ERR(cs35l35->reset_gpio); + cs35l35->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(dev, + "Reset line busy, assuming shared reset\n"); + } else { + dev_err(dev, "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + + gpiod_set_value_cansleep(cs35l35->reset_gpio, 1); + + init_completion(&cs35l35->pdn_done); + + ret = devm_request_threaded_irq(dev, i2c_client->irq, NULL, cs35l35_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW | + IRQF_SHARED, "cs35l35", cs35l35); + if (ret != 0) { + dev_err(dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + /* initialize codec */ + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L35_CHIP_ID) { + dev_err(dev, "CS35L35 Device ID (%X). Expected ID %X\n", + devid, CS35L35_CHIP_ID); + ret = -ENODEV; + goto err; + } + + ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®); + if (ret < 0) { + dev_err(dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch, + ARRAY_SIZE(cs35l35_errata_patch)); + if (ret < 0) { + dev_err(dev, "Failed to apply errata patch: %d\n", ret); + goto err; + } + + dev_info(dev, "Cirrus Logic CS35L35 (%x), Revision: %02X\n", + devid, reg & 0xFF); + + /* Set the INT Masks for critical errors */ + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, + CS35L35_INT1_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, + CS35L35_INT2_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, + CS35L35_INT3_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, + CS35L35_INT4_CRIT_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PWR2_PDN_MASK, + CS35L35_PWR2_PDN_MASK); + + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3, + CS35L35_PWR3_PDN_MASK, + CS35L35_PWR3_PDN_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT); + + ret = snd_soc_register_codec(dev, &soc_codec_dev_cs35l35, cs35l35_dai, + ARRAY_SIZE(cs35l35_dai)); + if (ret < 0) { + dev_err(dev, "Failed to register codec: %d\n", ret); + goto err; + } + + return 0; + +err: + regulator_bulk_disable(cs35l35->num_supplies, + cs35l35->supplies); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + + return ret; +} + +static int cs35l35_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id cs35l35_of_match[] = { + {.compatible = "cirrus,cs35l35"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l35_of_match); + +static const struct i2c_device_id cs35l35_id[] = { + {"cs35l35", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l35_id); + +static struct i2c_driver cs35l35_i2c_driver = { + .driver = { + .name = "cs35l35", + .of_match_table = cs35l35_of_match, + }, + .id_table = cs35l35_id, + .probe = cs35l35_i2c_probe, + .remove = cs35l35_i2c_remove, +}; + +module_i2c_driver(cs35l35_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L35 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, <brian.austin@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h new file mode 100644 index 000000000000..5a6e43a87c4d --- /dev/null +++ b/sound/soc/codecs/cs35l35.h @@ -0,0 +1,294 @@ +/* + * cs35l35.h -- CS35L35 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Brian Austin <brian.austin@cirrus.com> + * + * 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 __CS35L35_H__ +#define __CS35L35_H__ + +#define CS35L35_FIRSTREG 0x01 +#define CS35L35_LASTREG 0x7E +#define CS35L35_CHIP_ID 0x00035A35 +#define CS35L35_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L35_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L35_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L35_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L35_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L35_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L35_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L35_PWRCTL3 0x08 /* Power Ctl 3 */ +#define CS35L35_CLK_CTL1 0x0A /* Clocking Ctl 1 */ +#define CS35L35_CLK_CTL2 0x0B /* Clocking Ctl 2 */ +#define CS35L35_CLK_CTL3 0x0C /* Clocking Ctl 3 */ +#define CS35L35_SP_FMT_CTL1 0x0D /* Serial Port Format CTL1 */ +#define CS35L35_SP_FMT_CTL2 0x0E /* Serial Port Format CTL2 */ +#define CS35L35_SP_FMT_CTL3 0x0F /* Serial Port Format CTL3 */ +#define CS35L35_MAG_COMP_CTL 0x13 /* Magnitude Comp CTL */ +#define CS35L35_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */ +#define CS35L35_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */ +#define CS35L35_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */ +#define CS35L35_ADV_DIG_VOL 0x17 /* Advisory Digital Volume */ +#define CS35L35_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */ +#define CS35L35_AMP_GAIN_AUD_CTL 0x19 /* Amp Serial Port Gain Ctl */ +#define CS35L35_AMP_GAIN_PDM_CTL 0x1A /* Amplifier Gain PDM Ctl */ +#define CS35L35_AMP_GAIN_ADV_CTL 0x1B /* Amplifier Gain Ctl */ +#define CS35L35_GPI_CTL 0x1C /* GPI Ctl */ +#define CS35L35_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */ +#define CS35L35_BST_PEAK_I 0x1E /* Boost Conv Peak Current */ +#define CS35L35_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */ +#define CS35L35_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */ +#define CS35L35_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */ +#define CS35L35_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */ +#define CS35L35_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */ +#define CS35L35_CLASS_H_CTL 0x30 /* CLS H Control */ +#define CS35L35_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */ +#define CS35L35_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */ +#define CS35L35_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */ +#define CS35L35_CLASS_H_VP_CTL 0x34 /* CLS H VP Ctl */ +#define CS35L35_CLASS_H_STATUS 0x38 /* CLS H Status */ +#define CS35L35_VPBR_CTL 0x3A /* VPBR Ctl */ +#define CS35L35_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */ +#define CS35L35_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */ +#define CS35L35_VPBR_MODE_VOL_CTL 0x3D /* VPBR Mode/Attack Vol Ctl */ +#define CS35L35_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */ +#define CS35L35_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */ +#define CS35L35_IMON_SCALE_CTL 0x51 /* IMON Scale Ctl */ +#define CS35L35_AUDIN_RXLOC_CTL 0x52 /* Audio Input RX Loc Ctl */ +#define CS35L35_ADVIN_RXLOC_CTL 0x53 /* Advisory Input RX Loc Ctl */ +#define CS35L35_VMON_TXLOC_CTL 0x54 /* VMON TX Loc Ctl */ +#define CS35L35_IMON_TXLOC_CTL 0x55 /* IMON TX Loc Ctl */ +#define CS35L35_VPMON_TXLOC_CTL 0x56 /* VPMON TX Loc Ctl */ +#define CS35L35_VBSTMON_TXLOC_CTL 0x57 /* VBSTMON TX Loc Ctl */ +#define CS35L35_VPBR_STATUS_TXLOC_CTL 0x58 /* VPBR Status TX Loc Ctl */ +#define CS35L35_ZERO_FILL_LOC_CTL 0x59 /* Zero Fill Loc Ctl */ +#define CS35L35_AUDIN_DEPTH_CTL 0x5A /* Audio Input Depth Ctl */ +#define CS35L35_SPKMON_DEPTH_CTL 0x5B /* SPK Mon Output Depth Ctl */ +#define CS35L35_SUPMON_DEPTH_CTL 0x5C /* Supply Mon Out Depth Ctl */ +#define CS35L35_ZEROFILL_DEPTH_CTL 0x5D /* Zero Fill Mon Output Ctl */ +#define CS35L35_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */ +#define CS35L35_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */ +#define CS35L35_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */ +#define CS35L35_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */ +#define CS35L35_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */ +#define CS35L35_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */ +#define CS35L35_INT_MASK_1 0x70 /* Interrupt Mask 1 */ +#define CS35L35_INT_MASK_2 0x71 /* Interrupt Mask 2 */ +#define CS35L35_INT_MASK_3 0x72 /* Interrupt Mask 3 */ +#define CS35L35_INT_MASK_4 0x73 /* Interrupt Mask 4 */ +#define CS35L35_INT_STATUS_1 0x74 /* Interrupt Status 1 */ +#define CS35L35_INT_STATUS_2 0x75 /* Interrupt Status 2 */ +#define CS35L35_INT_STATUS_3 0x76 /* Interrupt Status 3 */ +#define CS35L35_INT_STATUS_4 0x77 /* Interrupt Status 4 */ +#define CS35L35_PLL_STATUS 0x78 /* PLL Status */ +#define CS35L35_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */ + +#define CS35L35_MAX_REGISTER 0x7F + +/* CS35L35_PWRCTL1 */ +#define CS35L35_SFT_RST 0x80 +#define CS35L35_DISCHG_FLT 0x02 +#define CS35L35_PDN_ALL 0x01 + +/* CS35L35_PWRCTL2 */ +#define CS35L35_PDN_VMON 0x80 +#define CS35L35_PDN_IMON 0x40 +#define CS35L35_PDN_CLASSH 0x20 +#define CS35L35_PDN_VPBR 0x10 +#define CS35L35_PDN_BST 0x04 +#define CS35L35_PDN_AMP 0x01 + +/* CS35L35_PWRCTL3 */ +#define CS35L35_PDN_VBSTMON_OUT 0x10 +#define CS35L35_PDN_VMON_OUT 0x08 + +#define CS35L35_AUDIN_DEPTH_MASK 0x03 +#define CS35L35_AUDIN_DEPTH_SHIFT 0 +#define CS35L35_ADVIN_DEPTH_MASK 0x0C +#define CS35L35_ADVIN_DEPTH_SHIFT 2 +#define CS35L35_SDIN_DEPTH_8 0x01 +#define CS35L35_SDIN_DEPTH_16 0x02 +#define CS35L35_SDIN_DEPTH_24 0x03 + +#define CS35L35_SDOUT_DEPTH_8 0x01 +#define CS35L35_SDOUT_DEPTH_12 0x02 +#define CS35L35_SDOUT_DEPTH_16 0x03 + +#define CS35L35_AUD_IN_LR_MASK 0x80 +#define CS35L35_AUD_IN_LR_SHIFT 7 +#define CS35L35_ADV_IN_LR_MASK 0x80 +#define CS35L35_ADV_IN_LR_SHIFT 7 +#define CS35L35_AUD_IN_LOC_MASK 0x0F +#define CS35L35_AUD_IN_LOC_SHIFT 0 +#define CS35L35_ADV_IN_LOC_MASK 0x0F +#define CS35L35_ADV_IN_LOC_SHIFT 0 + +#define CS35L35_IMON_DEPTH_MASK 0x03 +#define CS35L35_IMON_DEPTH_SHIFT 0 +#define CS35L35_VMON_DEPTH_MASK 0x0C +#define CS35L35_VMON_DEPTH_SHIFT 2 +#define CS35L35_VBSTMON_DEPTH_MASK 0x03 +#define CS35L35_VBSTMON_DEPTH_SHIFT 0 +#define CS35L35_VPMON_DEPTH_MASK 0x0C +#define CS35L35_VPMON_DEPTH_SHIFT 2 +#define CS35L35_VPBRSTAT_DEPTH_MASK 0x30 +#define CS35L35_VPBRSTAT_DEPTH_SHIFT 4 +#define CS35L35_ZEROFILL_DEPTH_MASK 0x03 +#define CS35L35_ZEROFILL_DEPTH_SHIFT 0x00 + +#define CS35L35_MON_TXLOC_MASK 0x3F +#define CS35L35_MON_TXLOC_SHIFT 0 +#define CS35L35_MON_FRM_MASK 0x80 +#define CS35L35_MON_FRM_SHIFT 7 + +#define CS35L35_IMON_SCALE_MASK 0xF8 +#define CS35L35_IMON_SCALE_SHIFT 3 + +#define CS35L35_MS_MASK 0x80 +#define CS35L35_MS_SHIFT 7 +#define CS35L35_SPMODE_MASK 0x40 +#define CS35L35_SP_DRV_MASK 0x10 +#define CS35L35_SP_DRV_SHIFT 4 +#define CS35L35_CLK_CTL2_MASK 0xFF +#define CS35L35_PDM_MODE_MASK 0x40 +#define CS35L35_PDM_MODE_SHIFT 6 +#define CS35L35_CLK_SOURCE_MASK 0x03 +#define CS35L35_CLK_SOURCE_SHIFT 0 +#define CS35L35_CLK_SOURCE_MCLK 0 +#define CS35L35_CLK_SOURCE_SCLK 1 +#define CS35L35_CLK_SOURCE_PDM 2 + +#define CS35L35_SP_SCLKS_MASK 0x0F +#define CS35L35_SP_SCLKS_SHIFT 0x00 +#define CS35L35_SP_SCLKS_16FS 0x03 +#define CS35L35_SP_SCLKS_32FS 0x07 +#define CS35L35_SP_SCLKS_48FS 0x0B +#define CS35L35_SP_SCLKS_64FS 0x0F +#define CS35L35_SP_RATE_MASK 0xC0 + +#define CS35L35_PDN_BST_MASK 0x06 +#define CS35L35_PDN_BST_FETON_SHIFT 1 +#define CS35L35_PDN_BST_FETOFF_SHIFT 2 +#define CS35L35_PWR2_PDN_MASK 0xE0 +#define CS35L35_PWR3_PDN_MASK 0x1E +#define CS35L35_PDN_ALL_MASK 0x01 +#define CS35L35_DISCHG_FILT_MASK 0x02 +#define CS35L35_DISCHG_FILT_SHIFT 1 +#define CS35L35_MCLK_DIS_MASK 0x04 +#define CS35L35_MCLK_DIS_SHIFT 2 + +#define CS35L35_BST_CTL_MASK 0x7F +#define CS35L35_BST_CTL_SHIFT 0 +#define CS35L35_BST_IPK_MASK 0x1F +#define CS35L35_BST_IPK_SHIFT 0 +#define CS35L35_AMP_MUTE_MASK 0x20 +#define CS35L35_AMP_MUTE_SHIFT 5 +#define CS35L35_AMP_GAIN_ZC_MASK 0x10 +#define CS35L35_AMP_GAIN_ZC_SHIFT 4 + +#define CS35L35_AMP_DIGSFT_MASK 0x02 +#define CS35L35_AMP_DIGSFT_SHIFT 1 + +/* CS35L35_SP_FMT_CTL3 */ +#define CS35L35_SP_I2S_DRV_MASK 0x03 +#define CS35L35_SP_I2S_DRV_SHIFT 0 + +/* Class H Algorithm Control */ +#define CS35L35_CH_STEREO_MASK 0x40 +#define CS35L35_CH_STEREO_SHIFT 6 +#define CS35L35_CH_BST_OVR_MASK 0x04 +#define CS35L35_CH_BST_OVR_SHIFT 2 +#define CS35L35_CH_BST_LIM_MASK 0x08 +#define CS35L35_CH_BST_LIM_SHIFT 3 +#define CS35L35_CH_MEM_DEPTH_MASK 0x01 +#define CS35L35_CH_MEM_DEPTH_SHIFT 0 +#define CS35L35_CH_HDRM_CTL_MASK 0x3F +#define CS35L35_CH_HDRM_CTL_SHIFT 0 +#define CS35L35_CH_REL_RATE_MASK 0xFF +#define CS35L35_CH_REL_RATE_SHIFT 0 +#define CS35L35_CH_WKFET_DIS_MASK 0x80 +#define CS35L35_CH_WKFET_DIS_SHIFT 7 +#define CS35L35_CH_WKFET_DEL_MASK 0x70 +#define CS35L35_CH_WKFET_DEL_SHIFT 4 +#define CS35L35_CH_WKFET_THLD_MASK 0x0F +#define CS35L35_CH_WKFET_THLD_SHIFT 0 +#define CS35L35_CH_VP_AUTO_MASK 0x80 +#define CS35L35_CH_VP_AUTO_SHIFT 7 +#define CS35L35_CH_VP_RATE_MASK 0x60 +#define CS35L35_CH_VP_RATE_SHIFT 5 +#define CS35L35_CH_VP_MAN_MASK 0x1F +#define CS35L35_CH_VP_MAN_SHIFT 0 + +/* CS35L35_PROT_RELEASE_CTL */ +#define CS35L35_CAL_ERR_RLS 0x80 +#define CS35L35_SHORT_RLS 0x04 +#define CS35L35_OTW_RLS 0x02 +#define CS35L35_OTE_RLS 0x01 + +/* INT Mask Registers */ +#define CS35L35_INT1_CRIT_MASK 0x38 +#define CS35L35_INT2_CRIT_MASK 0xEF +#define CS35L35_INT3_CRIT_MASK 0xEE +#define CS35L35_INT4_CRIT_MASK 0xFF + +/* PDN DONE Masks */ +#define CS35L35_M_PDN_DONE_SHIFT 4 +#define CS35L35_M_PDN_DONE_MASK 0x10 + +/* CS35L35_INT_1 */ +#define CS35L35_CAL_ERR 0x80 +#define CS35L35_OTP_ERR 0x40 +#define CS35L35_LRCLK_ERR 0x20 +#define CS35L35_SPCLK_ERR 0x10 +#define CS35L35_MCLK_ERR 0x08 +#define CS35L35_AMP_SHORT 0x04 +#define CS35L35_OTW 0x02 +#define CS35L35_OTE 0x01 + +/* CS35L35_INT_2 */ +#define CS35L35_PDN_DONE 0x10 +#define CS35L35_VPBR_ERR 0x02 +#define CS35L35_VPBR_CLR 0x01 + +/* CS35L35_INT_3 */ +#define CS35L35_BST_HIGH 0x10 +#define CS35L35_BST_HIGH_FLAG 0x08 +#define CS35L35_BST_IPK_FLAG 0x04 +#define CS35L35_LBST_SHORT 0x01 + +/* CS35L35_INT_4 */ +#define CS35L35_VMON_OVFL 0x08 +#define CS35L35_IMON_OVFL 0x04 + +#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct cs35l35_private { + struct device *dev; + struct cs35l35_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[2]; + int num_supplies; + int sysclk; + int sclk; + bool pdm_mode; + bool i2s_mode; + bool slave_mode; + /* GPIO for /RST */ + struct gpio_desc *reset_gpio; + struct completion pdn_done; +}; + +static const char * const cs35l35_supplies[] = { + "VA", + "VP", +}; + +#endif diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index 8c0f3b89b5bc..e78b5f055f25 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -498,7 +498,7 @@ static int cs4271_reset(struct snd_soc_codec *codec) struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec); if (gpio_is_valid(cs4271->gpio_nreset)) { - gpio_set_value(cs4271->gpio_nreset, 0); + gpio_direction_output(cs4271->gpio_nreset, 0); mdelay(1); gpio_set_value(cs4271->gpio_nreset, 1); mdelay(1); diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index cb47fb595ff4..1e0d5973b758 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -1130,6 +1130,7 @@ MODULE_DEVICE_TABLE(i2c, cs53l30_id); static struct i2c_driver cs53l30_i2c_driver = { .driver = { .name = "cs53l30", + .of_match_table = cs53l30_of_match, .pm = &cs53l30_runtime_pm, }, .id_table = cs53l30_id, diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 12da55882c06..6dd7578f0bb8 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -12,6 +12,7 @@ * option) any later version. */ +#include <linux/acpi.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/i2c.h> @@ -1528,12 +1529,23 @@ static int da7213_set_bias_level(struct snd_soc_codec *codec, return 0; } +#if defined(CONFIG_OF) /* DT */ static const struct of_device_id da7213_of_match[] = { { .compatible = "dlg,da7213", }, { } }; MODULE_DEVICE_TABLE(of, da7213_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id da7213_acpi_match[] = { + { "DLGS7212", 0}, + { "DLGS7213", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, da7213_acpi_match); +#endif static enum da7213_micbias_voltage da7213_of_micbias_lvl(struct snd_soc_codec *codec, u32 val) @@ -1844,6 +1856,7 @@ static struct i2c_driver da7213_i2c_driver = { .driver = { .name = "da7213", .of_match_table = of_match_ptr(da7213_of_match), + .acpi_match_table = ACPI_PTR(da7213_acpi_match), }, .probe = da7213_i2c_probe, .remove = da7213_remove, diff --git a/sound/soc/codecs/dio2125.c b/sound/soc/codecs/dio2125.c new file mode 100644 index 000000000000..09451cd44f9b --- /dev/null +++ b/sound/soc/codecs/dio2125.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <sound/soc.h> + +#define DRV_NAME "dio2125" + +struct dio2125 { + struct gpio_desc *gpiod_enable; +}; + +static int drv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct dio2125 *priv = snd_soc_component_get_drvdata(c); + int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + default: + WARN(1, "Unexpected event"); + return -EINVAL; + } + + gpiod_set_value_cansleep(priv->gpiod_enable, val); + + return 0; +} + +static const struct snd_soc_dapm_widget dio2125_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, drv_event, + (SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route dio2125_dapm_routes[] = { + { "DRV", NULL, "INL" }, + { "DRV", NULL, "INR" }, + { "OUTL", NULL, "DRV" }, + { "OUTR", NULL, "DRV" }, +}; + +static const struct snd_soc_component_driver dio2125_component_driver = { + .dapm_widgets = dio2125_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dio2125_dapm_widgets), + .dapm_routes = dio2125_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(dio2125_dapm_routes), +}; + +static int dio2125_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dio2125 *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_enable)) { + err = PTR_ERR(priv->gpiod_enable); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'enable' gpio: %d", err); + return err; + } + + return devm_snd_soc_register_component(dev, &dio2125_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id dio2125_ids[] = { + { .compatible = "dioo,dio2125", }, + { } +}; +MODULE_DEVICE_TABLE(of, dio2125_ids); +#endif + +static struct platform_driver dio2125_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(dio2125_ids), + }, + .probe = dio2125_probe, +}; + +module_platform_driver(dio2125_driver); + +MODULE_DESCRIPTION("ASoC DIO2125 output driver"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es7134.c b/sound/soc/codecs/es7134.c new file mode 100644 index 000000000000..25ede825d349 --- /dev/null +++ b/sound/soc/codecs/es7134.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include <linux/module.h> +#include <sound/soc.h> + +/* + * The everest 7134 is a very simple DA converter with no register + */ + +static int es7134_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | + SND_SOC_DAIFMT_MASTER_MASK); + + if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS)) { + dev_err(codec_dai->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops es7134_dai_ops = { + .set_fmt = es7134_set_fmt, +}; + +static struct snd_soc_dai_driver es7134_dai = { + .name = "es7134-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &es7134_dai_ops, +}; + +static const struct snd_soc_dapm_widget es7134_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route es7134_dapm_routes[] = { + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +static struct snd_soc_codec_driver es7134_codec_driver = { + .component_driver = { + .dapm_widgets = es7134_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es7134_dapm_widgets), + .dapm_routes = es7134_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es7134_dapm_routes), + }, +}; + +static int es7134_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &es7134_codec_driver, + &es7134_dai, 1); +} + +static int es7134_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id es7134_ids[] = { + { .compatible = "everest,es7134", }, + { .compatible = "everest,es7144", }, + { } +}; +MODULE_DEVICE_TABLE(of, es7134_ids); +#endif + +static struct platform_driver es7134_driver = { + .driver = { + .name = "es7134", + .of_match_table = of_match_ptr(es7134_ids), + }, + .probe = es7134_probe, + .remove = es7134_remove, +}; + +module_platform_driver(es7134_driver); + +MODULE_DESCRIPTION("ASoC ES7134 audio codec driver"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index 3f84fbd071e2..ed7cc42d1ee2 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -69,14 +69,10 @@ static const char * const supply_names[ES8328_SUPPLY_NUM] = { "HPVDD", }; -#define ES8328_RATES (SNDRV_PCM_RATE_96000 | \ - SNDRV_PCM_RATE_48000 | \ - SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_32000 | \ - SNDRV_PCM_RATE_22050 | \ - SNDRV_PCM_RATE_16000 | \ - SNDRV_PCM_RATE_11025 | \ - SNDRV_PCM_RATE_8000) +#define ES8328_RATES (SNDRV_PCM_RATE_192000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_8000_48000) #define ES8328_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S18_3LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -91,6 +87,7 @@ struct es8328_priv { int mclkdiv2; const struct snd_pcm_hw_constraint_list *sysclk_constraints; const int *mclk_ratios; + bool master; struct regulator_bulk_data supplies[ES8328_SUPPLY_NUM]; }; @@ -469,7 +466,7 @@ static int es8328_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = dai->codec; struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); - if (es8328->sysclk_constraints) + if (es8328->master && es8328->sysclk_constraints) snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, es8328->sysclk_constraints); @@ -488,27 +485,34 @@ static int es8328_hw_params(struct snd_pcm_substream *substream, int wl; int ratio; - if (!es8328->sysclk_constraints) { - dev_err(codec->dev, "No MCLK configured\n"); - return -EINVAL; - } - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) reg = ES8328_DACCONTROL2; else reg = ES8328_ADCCONTROL5; - for (i = 0; i < es8328->sysclk_constraints->count; i++) - if (es8328->sysclk_constraints->list[i] == params_rate(params)) - break; + if (es8328->master) { + if (!es8328->sysclk_constraints) { + dev_err(codec->dev, "No MCLK configured\n"); + return -EINVAL; + } - if (i == es8328->sysclk_constraints->count) { - dev_err(codec->dev, "LRCLK %d unsupported with current clock\n", - params_rate(params)); - return -EINVAL; + for (i = 0; i < es8328->sysclk_constraints->count; i++) + if (es8328->sysclk_constraints->list[i] == + params_rate(params)) + break; + + if (i == es8328->sysclk_constraints->count) { + dev_err(codec->dev, + "LRCLK %d unsupported with current clock\n", + params_rate(params)); + return -EINVAL; + } + ratio = es8328->mclk_ratios[i]; + } else { + ratio = 0; + es8328->mclkdiv2 = 0; } - ratio = es8328->mclk_ratios[i]; snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MCLKDIV2, es8328->mclkdiv2 ? ES8328_MASTERMODE_MCLKDIV2 : 0); @@ -586,6 +590,7 @@ static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; + struct es8328_priv *es8328 = snd_soc_codec_get_drvdata(codec); u8 dac_mode = 0; u8 adc_mode = 0; @@ -595,11 +600,13 @@ static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MSC, ES8328_MASTERMODE_MSC); + es8328->master = true; break; case SND_SOC_DAIFMT_CBS_CFS: /* Slave serial port mode */ snd_soc_update_bits(codec, ES8328_MASTERMODE, ES8328_MASTERMODE_MSC, 0); + es8328->master = false; break; default: return -EINVAL; diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index fd272a40485b..bc2e74ff3b2d 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -469,7 +469,7 @@ static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream, format = snd_hdac_calc_stream_format(params_rate(hparams), params_channels(hparams), params_format(hparams), - 24, 0); + dai->driver->playback.sig_bits, 0); pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); if (!pcm) @@ -1419,8 +1419,8 @@ static int hdac_hdmi_create_dais(struct hdac_device *hdac, hdmi_dais[i].playback.rate_min = rate_min; hdmi_dais[i].playback.channels_min = 2; hdmi_dais[i].playback.channels_max = 2; + hdmi_dais[i].playback.sig_bits = bps; hdmi_dais[i].ops = &hdmi_dai_ops; - i++; } diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 6cdf15ab46de..0247edc9c84e 100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c @@ -516,13 +516,13 @@ static const struct i2c_device_id max9867_i2c_id[] = { { "max9867", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, max9867_i2c_id); static const struct of_device_id max9867_of_match[] = { { .compatible = "maxim,max9867", }, { } }; - -MODULE_DEVICE_TABLE(i2c, max9867_i2c_id); +MODULE_DEVICE_TABLE(of, max9867_of_match); static const struct dev_pm_ops max9867_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(max9867_suspend, max9867_resume) diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c new file mode 100644 index 000000000000..b5ee29499e16 --- /dev/null +++ b/sound/soc/codecs/max98927.c @@ -0,0 +1,841 @@ +/* + * max98927.c -- MAX98927 ALSA Soc Audio driver + * + * Copyright (C) 2016 Maxim Integrated Products + * Author: Ryan Lee <ryans.lee@maximintegrated.com> + * + * 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 <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/cdev.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <sound/tlv.h> +#include "max98927.h" + +static struct reg_default max98927_reg[] = { + {MAX98927_R0001_INT_RAW1, 0x00}, + {MAX98927_R0002_INT_RAW2, 0x00}, + {MAX98927_R0003_INT_RAW3, 0x00}, + {MAX98927_R0004_INT_STATE1, 0x00}, + {MAX98927_R0005_INT_STATE2, 0x00}, + {MAX98927_R0006_INT_STATE3, 0x00}, + {MAX98927_R0007_INT_FLAG1, 0x00}, + {MAX98927_R0008_INT_FLAG2, 0x00}, + {MAX98927_R0009_INT_FLAG3, 0x00}, + {MAX98927_R000A_INT_EN1, 0x00}, + {MAX98927_R000B_INT_EN2, 0x00}, + {MAX98927_R000C_INT_EN3, 0x00}, + {MAX98927_R000D_INT_FLAG_CLR1, 0x00}, + {MAX98927_R000E_INT_FLAG_CLR2, 0x00}, + {MAX98927_R000F_INT_FLAG_CLR3, 0x00}, + {MAX98927_R0010_IRQ_CTRL, 0x00}, + {MAX98927_R0011_CLK_MON, 0x00}, + {MAX98927_R0012_WDOG_CTRL, 0x00}, + {MAX98927_R0013_WDOG_RST, 0x00}, + {MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH, 0x00}, + {MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH, 0x00}, + {MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS, 0x00}, + {MAX98927_R0017_PIN_CFG, 0x55}, + {MAX98927_R0018_PCM_RX_EN_A, 0x00}, + {MAX98927_R0019_PCM_RX_EN_B, 0x00}, + {MAX98927_R001A_PCM_TX_EN_A, 0x00}, + {MAX98927_R001B_PCM_TX_EN_B, 0x00}, + {MAX98927_R001C_PCM_TX_HIZ_CTRL_A, 0x00}, + {MAX98927_R001D_PCM_TX_HIZ_CTRL_B, 0x00}, + {MAX98927_R001E_PCM_TX_CH_SRC_A, 0x00}, + {MAX98927_R001F_PCM_TX_CH_SRC_B, 0x00}, + {MAX98927_R0020_PCM_MODE_CFG, 0x40}, + {MAX98927_R0021_PCM_MASTER_MODE, 0x00}, + {MAX98927_R0022_PCM_CLK_SETUP, 0x22}, + {MAX98927_R0023_PCM_SR_SETUP1, 0x00}, + {MAX98927_R0024_PCM_SR_SETUP2, 0x00}, + {MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, 0x00}, + {MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x00}, + {MAX98927_R0027_ICC_RX_EN_A, 0x00}, + {MAX98927_R0028_ICC_RX_EN_B, 0x00}, + {MAX98927_R002B_ICC_TX_EN_A, 0x00}, + {MAX98927_R002C_ICC_TX_EN_B, 0x00}, + {MAX98927_R002E_ICC_HIZ_MANUAL_MODE, 0x00}, + {MAX98927_R002F_ICC_TX_HIZ_EN_A, 0x00}, + {MAX98927_R0030_ICC_TX_HIZ_EN_B, 0x00}, + {MAX98927_R0031_ICC_LNK_EN, 0x00}, + {MAX98927_R0032_PDM_TX_EN, 0x00}, + {MAX98927_R0033_PDM_TX_HIZ_CTRL, 0x00}, + {MAX98927_R0034_PDM_TX_CTRL, 0x00}, + {MAX98927_R0035_PDM_RX_CTRL, 0x00}, + {MAX98927_R0036_AMP_VOL_CTRL, 0x00}, + {MAX98927_R0037_AMP_DSP_CFG, 0x02}, + {MAX98927_R0038_TONE_GEN_DC_CFG, 0x00}, + {MAX98927_R0039_DRE_CTRL, 0x01}, + {MAX98927_R003A_AMP_EN, 0x00}, + {MAX98927_R003B_SPK_SRC_SEL, 0x00}, + {MAX98927_R003C_SPK_GAIN, 0x00}, + {MAX98927_R003D_SSM_CFG, 0x01}, + {MAX98927_R003E_MEAS_EN, 0x00}, + {MAX98927_R003F_MEAS_DSP_CFG, 0x04}, + {MAX98927_R0040_BOOST_CTRL0, 0x00}, + {MAX98927_R0041_BOOST_CTRL3, 0x00}, + {MAX98927_R0042_BOOST_CTRL1, 0x00}, + {MAX98927_R0043_MEAS_ADC_CFG, 0x00}, + {MAX98927_R0044_MEAS_ADC_BASE_MSB, 0x00}, + {MAX98927_R0045_MEAS_ADC_BASE_LSB, 0x00}, + {MAX98927_R0046_ADC_CH0_DIVIDE, 0x00}, + {MAX98927_R0047_ADC_CH1_DIVIDE, 0x00}, + {MAX98927_R0048_ADC_CH2_DIVIDE, 0x00}, + {MAX98927_R0049_ADC_CH0_FILT_CFG, 0x00}, + {MAX98927_R004A_ADC_CH1_FILT_CFG, 0x00}, + {MAX98927_R004B_ADC_CH2_FILT_CFG, 0x00}, + {MAX98927_R004C_MEAS_ADC_CH0_READ, 0x00}, + {MAX98927_R004D_MEAS_ADC_CH1_READ, 0x00}, + {MAX98927_R004E_MEAS_ADC_CH2_READ, 0x00}, + {MAX98927_R0051_BROWNOUT_STATUS, 0x00}, + {MAX98927_R0052_BROWNOUT_EN, 0x00}, + {MAX98927_R0053_BROWNOUT_INFINITE_HOLD, 0x00}, + {MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR, 0x00}, + {MAX98927_R0055_BROWNOUT_LVL_HOLD, 0x00}, + {MAX98927_R005A_BROWNOUT_LVL1_THRESH, 0x00}, + {MAX98927_R005B_BROWNOUT_LVL2_THRESH, 0x00}, + {MAX98927_R005C_BROWNOUT_LVL3_THRESH, 0x00}, + {MAX98927_R005D_BROWNOUT_LVL4_THRESH, 0x00}, + {MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS, 0x00}, + {MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL, 0x00}, + {MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL, 0x00}, + {MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE, 0x00}, + {MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT, 0x00}, + {MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1, 0x00}, + {MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2, 0x00}, + {MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3, 0x00}, + {MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT, 0x00}, + {MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1, 0x00}, + {MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2, 0x00}, + {MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3, 0x00}, + {MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT, 0x00}, + {MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1, 0x00}, + {MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2, 0x00}, + {MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3, 0x00}, + {MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT, 0x00}, + {MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, 0x00}, + {MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2, 0x00}, + {MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3, 0x00}, + {MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, 0x00}, + {MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY, 0x00}, + {MAX98927_R0084_ENV_TRACK_REL_RATE, 0x00}, + {MAX98927_R0085_ENV_TRACK_HOLD_RATE, 0x00}, + {MAX98927_R0086_ENV_TRACK_CTRL, 0x00}, + {MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, 0x00}, + {MAX98927_R00FF_GLOBAL_SHDN, 0x00}, + {MAX98927_R0100_SOFT_RESET, 0x00}, + {MAX98927_R01FF_REV_ID, 0x40}, +}; + +static int max98927_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + unsigned int mode = 0; + unsigned int format = 0; + unsigned int invert = 0; + + dev_dbg(codec->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + mode = MAX98927_PCM_MASTER_MODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + max98927->master = true; + mode = MAX98927_PCM_MASTER_MODE_MASTER; + break; + default: + dev_err(codec->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MASK, + mode); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(codec->dev, "DAI invert mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + max98927->iface |= SND_SOC_DAIFMT_I2S; + format = MAX98927_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + max98927->iface |= SND_SOC_DAIFMT_LEFT_J; + format = MAX98927_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_PDM: + max98927->iface |= SND_SOC_DAIFMT_PDM; + break; + default: + return -EINVAL; + } + + /* pcm channel configuration */ + if (max98927->iface & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98927_PCM_MODE_CFG_FORMAT_SHIFT); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 0); + + } else + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, 0); + + /* pdm channel configuration */ + if (max98927->iface & SND_SOC_DAIFMT_PDM) { + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 1); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 3); + } else + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 0); + return 0; +} + +/* codec MCLK rate in master mode */ +static const int rate_table[] = { + 5644800, 6000000, 6144000, 6500000, + 9600000, 11289600, 12000000, 12288000, + 13000000, 19200000, +}; + +static int max98927_set_clock(struct max98927_priv *max98927, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_codec *codec = max98927->codec; + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98927->ch_size; + int value; + + if (max98927->master) { + int i; + /* match rate to closest value */ + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i] >= max98927->sysclk) + break; + } + if (i == ARRAY_SIZE(rate_table)) { + dev_err(codec->dev, "failed to find proper clock rate.\n"); + return -EINVAL; + } + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MCLK_MASK, + i << MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT); + } + + switch (blr_clk_ratio) { + case 32: + value = 2; + break; + case 48: + value = 3; + break; + case 64: + value = 4; + break; + default: + return -EINVAL; + } + regmap_update_bits(max98927->regmap, + MAX98927_R0022_PCM_CLK_SETUP, + MAX98927_PCM_CLK_SETUP_BSEL_MASK, + value); + return 0; +} + +static int max98927_dai_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 max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(codec->dev, "format unsupported %d", + params_format(params)); + goto err; + } + + max98927->ch_size = snd_pcm_format_width(params_format(params)); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + dev_dbg(codec->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_8000; + break; + case 11025: + sampling_rate = MAX98927_PCM_SR_SET1_SR_11025; + break; + case 12000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_12000; + break; + case 16000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_16000; + break; + case 22050: + sampling_rate = MAX98927_PCM_SR_SET1_SR_22050; + break; + case 24000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_24000; + break; + case 32000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_32000; + break; + case 44100: + sampling_rate = MAX98927_PCM_SR_SET1_SR_44100; + break; + case 48000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_48000; + break; + default: + dev_err(codec->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98927->regmap, + MAX98927_R0023_PCM_SR_SETUP1, + MAX98927_PCM_SR_SET1_SR_MASK, + sampling_rate); + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_SR_MASK, + sampling_rate << MAX98927_PCM_SR_SET2_SR_SHIFT); + + /* set sampling rate of IV */ + if (max98927->interleave_mode && + sampling_rate > MAX98927_PCM_SR_SET1_SR_16000) + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate - 3); + else + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate); + return max98927_set_clock(max98927, params); +err: + return -EINVAL; +} + +#define MAX98927_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX98927_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int max98927_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + max98927->sysclk = freq; + return 0; +} + +static const struct snd_soc_dai_ops max98927_dai_ops = { + .set_sysclk = max98927_dai_set_sysclk, + .set_fmt = max98927_dai_set_fmt, + .hw_params = max98927_dai_hw_params, +}; + +static int max98927_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 1); + /* enable VMON and IMON */ + regmap_update_bits(max98927->regmap, + MAX98927_R003E_MEAS_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN); + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 1); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 0); + /* disable VMON and IMON */ + regmap_update_bits(max98927->regmap, + MAX98927_R003E_MEAS_EN, + MAX98927_MEAS_V_EN | MAX98927_MEAS_I_EN, 0); + break; + default: + return 0; + } + return 0; +} + +static const char * const max98927_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT, + 3, max98927_switch_text); + +static const struct snd_kcontrol_new max98927_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_soc_dapm_widget max98927_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", MAX98927_R003A_AMP_EN, + 0, 0, max98927_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, + &max98927_dai_controls), + SND_SOC_DAPM_OUTPUT("BE_OUT"), +}; + +static DECLARE_TLV_DB_SCALE(max98927_spk_tlv, 300, 300, 0); +static DECLARE_TLV_DB_SCALE(max98927_digital_tlv, -1600, 25, 0); + +static bool max98927_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0028_ICC_RX_EN_B: + case MAX98927_R002B_ICC_TX_EN_A ... MAX98927_R002C_ICC_TX_EN_B: + case MAX98927_R002E_ICC_HIZ_MANUAL_MODE + ... MAX98927_R004E_MEAS_ADC_CH2_READ: + case MAX98927_R0051_BROWNOUT_STATUS + ... MAX98927_R0055_BROWNOUT_LVL_HOLD: + case MAX98927_R005A_BROWNOUT_LVL1_THRESH + ... MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE: + case MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT + ... MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ: + case MAX98927_R00FF_GLOBAL_SHDN: + case MAX98927_R0100_SOFT_RESET: + case MAX98927_R01FF_REV_ID: + return true; + default: + return false; + } +}; + +static bool max98927_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0009_INT_FLAG3: + return true; + default: + return false; + } +} + +static const char * const max98927_boost_voltage_text[] = { + "6.5V", "6.625V", "6.75V", "6.875V", "7V", "7.125V", "7.25V", "7.375V", + "7.5V", "7.625V", "7.75V", "7.875V", "8V", "8.125V", "8.25V", "8.375V", + "8.5V", "8.625V", "8.75V", "8.875V", "9V", "9.125V", "9.25V", "9.375V", + "9.5V", "9.625V", "9.75V", "9.875V", "10V" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_boost_voltage, + MAX98927_R0040_BOOST_CTRL0, 0, + max98927_boost_voltage_text); + +static const char * const max98927_current_limit_text[] = { + "1.00A", "1.10A", "1.20A", "1.30A", "1.40A", "1.50A", "1.60A", "1.70A", + "1.80A", "1.90A", "2.00A", "2.10A", "2.20A", "2.30A", "2.40A", "2.50A", + "2.60A", "2.70A", "2.80A", "2.90A", "3.00A", "3.10A", "3.20A", "3.30A", + "3.40A", "3.50A", "3.60A", "3.70A", "3.80A", "3.90A", "4.00A", "4.10A" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_current_limit, + MAX98927_R0042_BOOST_CTRL1, 1, + max98927_current_limit_text); + +static const struct snd_kcontrol_new max98927_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98927_R003C_SPK_GAIN, + 0, 6, 0, + max98927_spk_tlv), + SOC_SINGLE_TLV("Digital Volume", MAX98927_R0036_AMP_VOL_CTRL, + 0, (1<<MAX98927_AMP_VOL_WIDTH)-1, 0, + max98927_digital_tlv), + SOC_SINGLE("Amp DSP Switch", MAX98927_R0052_BROWNOUT_EN, + MAX98927_BROWNOUT_DSP_SHIFT, 1, 0), + SOC_SINGLE("Ramp Switch", MAX98927_R0037_AMP_DSP_CFG, + MAX98927_AMP_DSP_CFG_RMP_SHIFT, 1, 0), + SOC_SINGLE("DRE Switch", MAX98927_R0039_DRE_CTRL, + MAX98927_DRE_EN_SHIFT, 1, 0), + SOC_SINGLE("Volume Location Switch", MAX98927_R0036_AMP_VOL_CTRL, + MAX98927_AMP_VOL_SEL_SHIFT, 1, 0), + SOC_ENUM("Boost Output Voltage", max98927_boost_voltage), + SOC_ENUM("Current Limit", max98927_current_limit), +}; + +static const struct snd_soc_dapm_route max98927_audio_map[] = { + {"Amp Enable", NULL, "DAI_OUT"}, + {"DAI Sel Mux", "Left", "Amp Enable"}, + {"DAI Sel Mux", "Right", "Amp Enable"}, + {"DAI Sel Mux", "LeftRight", "Amp Enable"}, + {"BE_OUT", NULL, "DAI Sel Mux"}, +}; + +static struct snd_soc_dai_driver max98927_dai[] = { + { + .name = "max98927-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98927_RATES, + .formats = MAX98927_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98927_RATES, + .formats = MAX98927_FORMATS, + }, + .ops = &max98927_dai_ops, + } +}; + +static int max98927_probe(struct snd_soc_codec *codec) +{ + struct max98927_priv *max98927 = snd_soc_codec_get_drvdata(codec); + + max98927->codec = codec; + codec->control_data = max98927->regmap; + codec->cache_bypass = 1; + + /* Software Reset */ + regmap_write(max98927->regmap, + MAX98927_R0100_SOFT_RESET, MAX98927_SOFT_RESET); + + /* IV default slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + 0x80); + regmap_write(max98927->regmap, + MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, + 0x1); + /* Set inital volume (+13dB) */ + regmap_write(max98927->regmap, + MAX98927_R0036_AMP_VOL_CTRL, + 0x38); + regmap_write(max98927->regmap, + MAX98927_R003C_SPK_GAIN, + 0x05); + /* Enable DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R0037_AMP_DSP_CFG, + 0x03); + /* Enable IMON VMON DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R003F_MEAS_DSP_CFG, + 0xF7); + /* Boost Output Voltage & Current limit */ + regmap_write(max98927->regmap, + MAX98927_R0040_BOOST_CTRL0, + 0x1C); + regmap_write(max98927->regmap, + MAX98927_R0042_BOOST_CTRL1, + 0x3E); + /* Measurement ADC config */ + regmap_write(max98927->regmap, + MAX98927_R0043_MEAS_ADC_CFG, + 0x04); + regmap_write(max98927->regmap, + MAX98927_R0044_MEAS_ADC_BASE_MSB, + 0x00); + regmap_write(max98927->regmap, + MAX98927_R0045_MEAS_ADC_BASE_LSB, + 0x24); + /* Brownout Level */ + regmap_write(max98927->regmap, + MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, + 0x06); + /* Envelope Tracking configuration */ + regmap_write(max98927->regmap, + MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, + 0x08); + regmap_write(max98927->regmap, + MAX98927_R0086_ENV_TRACK_CTRL, + 0x01); + regmap_write(max98927->regmap, + MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, + 0x10); + + /* voltage, current slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001E_PCM_TX_CH_SRC_A, + (max98927->i_l_slot<<MAX98927_PCM_TX_CH_SRC_A_I_SHIFT| + max98927->v_l_slot)&0xFF); + + if (max98927->v_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->v_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->v_l_slot, + 1 << max98927->v_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->v_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->v_l_slot - 8), + 1 << (max98927->v_l_slot - 8)); + } + + if (max98927->i_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->i_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->i_l_slot, + 1 << max98927->i_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->i_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->i_l_slot - 8), + 1 << (max98927->i_l_slot - 8)); + } + + /* Set interleave mode */ + if (max98927->interleave_mode) + regmap_update_bits(max98927->regmap, + MAX98927_R001F_PCM_TX_CH_SRC_B, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK); + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_dev_max98927 = { + .probe = max98927_probe, + .component_driver = { + .controls = max98927_snd_controls, + .num_controls = ARRAY_SIZE(max98927_snd_controls), + .dapm_widgets = max98927_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98927_dapm_widgets), + .dapm_routes = max98927_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98927_audio_map), + }, +}; + +static const struct regmap_config max98927_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98927_R01FF_REV_ID, + .reg_defaults = max98927_reg, + .num_reg_defaults = ARRAY_SIZE(max98927_reg), + .readable_reg = max98927_readable_register, + .volatile_reg = max98927_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void max98927_slot_config(struct i2c_client *i2c, + struct max98927_priv *max98927) +{ + int value; + + if (!of_property_read_u32(i2c->dev.of_node, + "vmon-slot-no", &value)) + max98927->v_l_slot = value & 0xF; + else + max98927->v_l_slot = 0; + if (!of_property_read_u32(i2c->dev.of_node, + "imon-slot-no", &value)) + max98927->i_l_slot = value & 0xF; + else + max98927->i_l_slot = 1; +} + +static int max98927_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + + int ret = 0, value; + int reg = 0; + struct max98927_priv *max98927 = NULL; + + max98927 = devm_kzalloc(&i2c->dev, + sizeof(*max98927), GFP_KERNEL); + + if (!max98927) { + ret = -ENOMEM; + return ret; + } + i2c_set_clientdata(i2c, max98927); + + /* update interleave mode info */ + if (!of_property_read_u32(i2c->dev.of_node, + "interleave_mode", &value)) { + if (value > 0) + max98927->interleave_mode = 1; + else + max98927->interleave_mode = 0; + } else + max98927->interleave_mode = 0; + + /* regmap initialization */ + max98927->regmap + = devm_regmap_init_i2c(i2c, &max98927_regmap); + if (IS_ERR(max98927->regmap)) { + ret = PTR_ERR(max98927->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + /* Check Revision ID */ + ret = regmap_read(max98927->regmap, + MAX98927_R01FF_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, + "Failed to read: 0x%02X\n", MAX98927_R01FF_REV_ID); + return ret; + } + dev_info(&i2c->dev, "MAX98927 revisionID: 0x%02X\n", reg); + + /* voltage/current slot configuration */ + max98927_slot_config(i2c, max98927); + + /* codec registeration */ + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98927, + max98927_dai, ARRAY_SIZE(max98927_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static int max98927_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id max98927_i2c_id[] = { + { "max98927", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98927_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max98927_of_match[] = { + { .compatible = "maxim,max98927", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98927_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98927_acpi_match[] = { + { "MX98927", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98927_acpi_match); +#endif + +static struct i2c_driver max98927_i2c_driver = { + .driver = { + .name = "max98927", + .of_match_table = of_match_ptr(max98927_of_match), + .acpi_match_table = ACPI_PTR(max98927_acpi_match), + .pm = NULL, + }, + .probe = max98927_i2c_probe, + .remove = max98927_i2c_remove, + .id_table = max98927_i2c_id, +}; + +module_i2c_driver(max98927_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98927 driver"); +MODULE_AUTHOR("Ryan Lee <ryans.lee@maximintegrated.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98927.h b/sound/soc/codecs/max98927.h new file mode 100644 index 000000000000..ece6a608cbe1 --- /dev/null +++ b/sound/soc/codecs/max98927.h @@ -0,0 +1,272 @@ +/* + * max98927.h -- MAX98927 ALSA Soc Audio driver + * + * Copyright 2013-15 Maxim Integrated Products + * Author: Ryan Lee <ryans.lee@maximintegrated.com> + * + * 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. + * + */ +#ifndef _MAX98927_H +#define _MAX98927_H + +/* Register Values */ +#define MAX98927_R0001_INT_RAW1 0x0001 +#define MAX98927_R0002_INT_RAW2 0x0002 +#define MAX98927_R0003_INT_RAW3 0x0003 +#define MAX98927_R0004_INT_STATE1 0x0004 +#define MAX98927_R0005_INT_STATE2 0x0005 +#define MAX98927_R0006_INT_STATE3 0x0006 +#define MAX98927_R0007_INT_FLAG1 0x0007 +#define MAX98927_R0008_INT_FLAG2 0x0008 +#define MAX98927_R0009_INT_FLAG3 0x0009 +#define MAX98927_R000A_INT_EN1 0x000A +#define MAX98927_R000B_INT_EN2 0x000B +#define MAX98927_R000C_INT_EN3 0x000C +#define MAX98927_R000D_INT_FLAG_CLR1 0x000D +#define MAX98927_R000E_INT_FLAG_CLR2 0x000E +#define MAX98927_R000F_INT_FLAG_CLR3 0x000F +#define MAX98927_R0010_IRQ_CTRL 0x0010 +#define MAX98927_R0011_CLK_MON 0x0011 +#define MAX98927_R0012_WDOG_CTRL 0x0012 +#define MAX98927_R0013_WDOG_RST 0x0013 +#define MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014 +#define MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015 +#define MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016 +#define MAX98927_R0017_PIN_CFG 0x0017 +#define MAX98927_R0018_PCM_RX_EN_A 0x0018 +#define MAX98927_R0019_PCM_RX_EN_B 0x0019 +#define MAX98927_R001A_PCM_TX_EN_A 0x001A +#define MAX98927_R001B_PCM_TX_EN_B 0x001B +#define MAX98927_R001C_PCM_TX_HIZ_CTRL_A 0x001C +#define MAX98927_R001D_PCM_TX_HIZ_CTRL_B 0x001D +#define MAX98927_R001E_PCM_TX_CH_SRC_A 0x001E +#define MAX98927_R001F_PCM_TX_CH_SRC_B 0x001F +#define MAX98927_R0020_PCM_MODE_CFG 0x0020 +#define MAX98927_R0021_PCM_MASTER_MODE 0x0021 +#define MAX98927_R0022_PCM_CLK_SETUP 0x0022 +#define MAX98927_R0023_PCM_SR_SETUP1 0x0023 +#define MAX98927_R0024_PCM_SR_SETUP2 0x0024 +#define MAX98927_R0025_PCM_TO_SPK_MONOMIX_A 0x0025 +#define MAX98927_R0026_PCM_TO_SPK_MONOMIX_B 0x0026 +#define MAX98927_R0027_ICC_RX_EN_A 0x0027 +#define MAX98927_R0028_ICC_RX_EN_B 0x0028 +#define MAX98927_R002B_ICC_TX_EN_A 0x002B +#define MAX98927_R002C_ICC_TX_EN_B 0x002C +#define MAX98927_R002E_ICC_HIZ_MANUAL_MODE 0x002E +#define MAX98927_R002F_ICC_TX_HIZ_EN_A 0x002F +#define MAX98927_R0030_ICC_TX_HIZ_EN_B 0x0030 +#define MAX98927_R0031_ICC_LNK_EN 0x0031 +#define MAX98927_R0032_PDM_TX_EN 0x0032 +#define MAX98927_R0033_PDM_TX_HIZ_CTRL 0x0033 +#define MAX98927_R0034_PDM_TX_CTRL 0x0034 +#define MAX98927_R0035_PDM_RX_CTRL 0x0035 +#define MAX98927_R0036_AMP_VOL_CTRL 0x0036 +#define MAX98927_R0037_AMP_DSP_CFG 0x0037 +#define MAX98927_R0038_TONE_GEN_DC_CFG 0x0038 +#define MAX98927_R0039_DRE_CTRL 0x0039 +#define MAX98927_R003A_AMP_EN 0x003A +#define MAX98927_R003B_SPK_SRC_SEL 0x003B +#define MAX98927_R003C_SPK_GAIN 0x003C +#define MAX98927_R003D_SSM_CFG 0x003D +#define MAX98927_R003E_MEAS_EN 0x003E +#define MAX98927_R003F_MEAS_DSP_CFG 0x003F +#define MAX98927_R0040_BOOST_CTRL0 0x0040 +#define MAX98927_R0041_BOOST_CTRL3 0x0041 +#define MAX98927_R0042_BOOST_CTRL1 0x0042 +#define MAX98927_R0043_MEAS_ADC_CFG 0x0043 +#define MAX98927_R0044_MEAS_ADC_BASE_MSB 0x0044 +#define MAX98927_R0045_MEAS_ADC_BASE_LSB 0x0045 +#define MAX98927_R0046_ADC_CH0_DIVIDE 0x0046 +#define MAX98927_R0047_ADC_CH1_DIVIDE 0x0047 +#define MAX98927_R0048_ADC_CH2_DIVIDE 0x0048 +#define MAX98927_R0049_ADC_CH0_FILT_CFG 0x0049 +#define MAX98927_R004A_ADC_CH1_FILT_CFG 0x004A +#define MAX98927_R004B_ADC_CH2_FILT_CFG 0x004B +#define MAX98927_R004C_MEAS_ADC_CH0_READ 0x004C +#define MAX98927_R004D_MEAS_ADC_CH1_READ 0x004D +#define MAX98927_R004E_MEAS_ADC_CH2_READ 0x004E +#define MAX98927_R0051_BROWNOUT_STATUS 0x0051 +#define MAX98927_R0052_BROWNOUT_EN 0x0052 +#define MAX98927_R0053_BROWNOUT_INFINITE_HOLD 0x0053 +#define MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR 0x0054 +#define MAX98927_R0055_BROWNOUT_LVL_HOLD 0x0055 +#define MAX98927_R005A_BROWNOUT_LVL1_THRESH 0x005A +#define MAX98927_R005B_BROWNOUT_LVL2_THRESH 0x005B +#define MAX98927_R005C_BROWNOUT_LVL3_THRESH 0x005C +#define MAX98927_R005D_BROWNOUT_LVL4_THRESH 0x005D +#define MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS 0x005E +#define MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL 0x005F +#define MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL 0x0060 +#define MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE 0x0061 +#define MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT 0x0072 +#define MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1 0x0073 +#define MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2 0x0074 +#define MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3 0x0075 +#define MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT 0x0076 +#define MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1 0x0077 +#define MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2 0x0078 +#define MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3 0x0079 +#define MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT 0x007A +#define MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1 0x007B +#define MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2 0x007C +#define MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3 0x007D +#define MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT 0x007E +#define MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1 0x007F +#define MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2 0x0080 +#define MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3 0x0081 +#define MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM 0x0082 +#define MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY 0x0083 +#define MAX98927_R0084_ENV_TRACK_REL_RATE 0x0084 +#define MAX98927_R0085_ENV_TRACK_HOLD_RATE 0x0085 +#define MAX98927_R0086_ENV_TRACK_CTRL 0x0086 +#define MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ 0x0087 +#define MAX98927_R00FF_GLOBAL_SHDN 0x00FF +#define MAX98927_R0100_SOFT_RESET 0x0100 +#define MAX98927_R01FF_REV_ID 0x01FF + +/* MAX98927_R0018_PCM_RX_EN_A */ +#define MAX98927_PCM_RX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_RX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_RX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_RX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_RX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_RX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_RX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_RX_CH7_EN (0x1 << 7) + +/* MAX98927_R001A_PCM_TX_EN_A */ +#define MAX98927_PCM_TX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_TX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_TX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_TX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_TX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_TX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_TX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_TX_CH7_EN (0x1 << 7) + +/* MAX98927_R001E_PCM_TX_CH_SRC_A */ +#define MAX98927_PCM_TX_CH_SRC_A_V_SHIFT (0) +#define MAX98927_PCM_TX_CH_SRC_A_I_SHIFT (4) + +/* MAX98927_R001F_PCM_TX_CH_SRC_B */ +#define MAX98927_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 5) + +/* MAX98927_R0020_PCM_MODE_CFG */ +#define MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 2) +#define MAX98927_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98927_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98927_PCM_FORMAT_I2S (0x0 << 0) +#define MAX98927_PCM_FORMAT_LJ (0x1 << 0) + +#define MAX98927_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98927_R0021_PCM_MASTER_MODE */ +#define MAX98927_PCM_MASTER_MODE_MASK (0x3 << 0) +#define MAX98927_PCM_MASTER_MODE_SLAVE (0x0 << 0) +#define MAX98927_PCM_MASTER_MODE_MASTER (0x3 << 0) + +#define MAX98927_PCM_MASTER_MODE_MCLK_MASK (0xF << 2) +#define MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT (2) + +/* MAX98927_R0022_PCM_CLK_SETUP */ +#define MAX98927_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98927_R0023_PCM_SR_SETUP1 */ +#define MAX98927_PCM_SR_SET1_SR_MASK (0xF << 0) + +#define MAX98927_PCM_SR_SET1_SR_8000 (0x0 << 0) +#define MAX98927_PCM_SR_SET1_SR_11025 (0x1 << 0) +#define MAX98927_PCM_SR_SET1_SR_12000 (0x2 << 0) +#define MAX98927_PCM_SR_SET1_SR_16000 (0x3 << 0) +#define MAX98927_PCM_SR_SET1_SR_22050 (0x4 << 0) +#define MAX98927_PCM_SR_SET1_SR_24000 (0x5 << 0) +#define MAX98927_PCM_SR_SET1_SR_32000 (0x6 << 0) +#define MAX98927_PCM_SR_SET1_SR_44100 (0x7 << 0) +#define MAX98927_PCM_SR_SET1_SR_48000 (0x8 << 0) + +/* MAX98927_R0024_PCM_SR_SETUP2 */ +#define MAX98927_PCM_SR_SET2_SR_MASK (0xF << 4) +#define MAX98927_PCM_SR_SET2_SR_SHIFT (4) +#define MAX98927_PCM_SR_SET2_IVADC_SR_MASK (0xf << 0) + +/* MAX98927_R0025_PCM_TO_SPK_MONOMIX_A */ +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6) +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6) + +/* MAX98927_R0035_PDM_RX_CTRL */ +#define MAX98927_PDM_RX_EN_MASK (0x1 << 0) + +/* MAX98927_R0036_AMP_VOL_CTRL */ +#define MAX98927_AMP_VOL_SEL (0x1 << 7) +#define MAX98927_AMP_VOL_SEL_WIDTH (1) +#define MAX98927_AMP_VOL_SEL_SHIFT (7) +#define MAX98927_AMP_VOL_MASK (0x7f << 0) +#define MAX98927_AMP_VOL_WIDTH (7) +#define MAX98927_AMP_VOL_SHIFT (0) + +/* MAX98927_R0037_AMP_DSP_CFG */ +#define MAX98927_AMP_DSP_CFG_DCBLK_EN (0x1 << 0) +#define MAX98927_AMP_DSP_CFG_DITH_EN (0x1 << 1) +#define MAX98927_AMP_DSP_CFG_RMP_BYPASS (0x1 << 4) +#define MAX98927_AMP_DSP_CFG_DAC_INV (0x1 << 5) +#define MAX98927_AMP_DSP_CFG_RMP_SHIFT (4) + +/* MAX98927_R0039_DRE_CTRL */ +#define MAX98927_DRE_CTRL_DRE_EN (0x1 << 0) +#define MAX98927_DRE_EN_SHIFT 0x1 + +/* MAX98927_R003A_AMP_EN */ +#define MAX98927_AMP_EN_MASK (0x1 << 0) + +/* MAX98927_R003B_SPK_SRC_SEL */ +#define MAX98927_SPK_SRC_MASK (0x3 << 0) + +/* MAX98927_R003C_SPK_GAIN */ +#define MAX98927_SPK_PCM_GAIN_MASK (0x7 << 0) +#define MAX98927_SPK_PDM_GAIN_MASK (0x7 << 4) +#define MAX98927_SPK_GAIN_WIDTH (3) + +/* MAX98927_R003E_MEAS_EN */ +#define MAX98927_MEAS_V_EN (0x1 << 0) +#define MAX98927_MEAS_I_EN (0x1 << 1) + +/* MAX98927_R0040_BOOST_CTRL0 */ +#define MAX98927_BOOST_CTRL0_VOUT_MASK (0x1f << 0) +#define MAX98927_BOOST_CTRL0_PVDD_MASK (0x1 << 7) +#define MAX98927_BOOST_CTRL0_PVDD_EN_SHIFT (7) + +/* MAX98927_R0052_BROWNOUT_EN */ +#define MAX98927_BROWNOUT_BDE_EN (0x1 << 0) +#define MAX98927_BROWNOUT_AMP_EN (0x1 << 1) +#define MAX98927_BROWNOUT_DSP_EN (0x1 << 2) +#define MAX98927_BROWNOUT_DSP_SHIFT (2) + +/* MAX98927_R0100_SOFT_RESET */ +#define MAX98927_SOFT_RESET (0x1 << 0) + +/* MAX98927_R00FF_GLOBAL_SHDN */ +#define MAX98927_GLOBAL_EN_MASK (0x1 << 0) + +struct max98927_priv { + struct regmap *regmap; + struct snd_soc_codec *codec; + struct max98927_pdata *pdata; + unsigned int spk_gain; + unsigned int sysclk; + unsigned int v_l_slot; + unsigned int i_l_slot; + bool interleave_mode; + unsigned int ch_size; + unsigned int rate; + unsigned int iface; + unsigned int master; + unsigned int digital_gain; +}; +#endif diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index 9e8f0f4aa51a..c8bcb1db966d 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -39,147 +39,147 @@ /* scaling for mclk from sysclk_src output */ static const struct nau8540_fll_attr mclk_src_scaling[] = { - { 1, 0x0 }, - { 2, 0x2 }, - { 4, 0x3 }, - { 8, 0x4 }, - { 16, 0x5 }, - { 32, 0x6 }, - { 3, 0x7 }, - { 6, 0xa }, - { 12, 0xb }, - { 24, 0xc }, + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, }; /* ratio for input clk freq */ static const struct nau8540_fll_attr fll_ratio[] = { - { 512000, 0x01 }, - { 256000, 0x02 }, - { 128000, 0x04 }, - { 64000, 0x08 }, - { 32000, 0x10 }, - { 8000, 0x20 }, - { 4000, 0x40 }, + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, }; static const struct nau8540_fll_attr fll_pre_scalar[] = { - { 1, 0x0 }, - { 2, 0x1 }, - { 4, 0x2 }, - { 8, 0x3 }, + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, }; /* over sampling rate */ static const struct nau8540_osr_attr osr_adc_sel[] = { - { 32, 3 }, /* OSR 32, SRC 1/8 */ - { 64, 2 }, /* OSR 64, SRC 1/4 */ - { 128, 1 }, /* OSR 128, SRC 1/2 */ - { 256, 0 }, /* OSR 256, SRC 1 */ + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ }; static const struct reg_default nau8540_reg_defaults[] = { - {NAU8540_REG_POWER_MANAGEMENT, 0x0000}, - {NAU8540_REG_CLOCK_CTRL, 0x0000}, - {NAU8540_REG_CLOCK_SRC, 0x0000}, - {NAU8540_REG_FLL1, 0x0001}, - {NAU8540_REG_FLL2, 0x3126}, - {NAU8540_REG_FLL3, 0x0008}, - {NAU8540_REG_FLL4, 0x0010}, - {NAU8540_REG_FLL5, 0xC000}, - {NAU8540_REG_FLL6, 0x6000}, - {NAU8540_REG_FLL_VCO_RSV, 0xF13C}, - {NAU8540_REG_PCM_CTRL0, 0x000B}, - {NAU8540_REG_PCM_CTRL1, 0x3010}, - {NAU8540_REG_PCM_CTRL2, 0x0800}, - {NAU8540_REG_PCM_CTRL3, 0x0000}, - {NAU8540_REG_PCM_CTRL4, 0x000F}, - {NAU8540_REG_ALC_CONTROL_1, 0x0000}, - {NAU8540_REG_ALC_CONTROL_2, 0x700B}, - {NAU8540_REG_ALC_CONTROL_3, 0x0022}, - {NAU8540_REG_ALC_CONTROL_4, 0x1010}, - {NAU8540_REG_ALC_CONTROL_5, 0x1010}, - {NAU8540_REG_NOTCH_FIL1_CH1, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH1, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH2, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH2, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH3, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH3, 0x0000}, - {NAU8540_REG_NOTCH_FIL1_CH4, 0x0000}, - {NAU8540_REG_NOTCH_FIL2_CH4, 0x0000}, - {NAU8540_REG_HPF_FILTER_CH12, 0x0000}, - {NAU8540_REG_HPF_FILTER_CH34, 0x0000}, - {NAU8540_REG_ADC_SAMPLE_RATE, 0x0002}, - {NAU8540_REG_DIGITAL_GAIN_CH1, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH2, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH3, 0x0400}, - {NAU8540_REG_DIGITAL_GAIN_CH4, 0x0400}, - {NAU8540_REG_DIGITAL_MUX, 0x00E4}, - {NAU8540_REG_GPIO_CTRL, 0x0000}, - {NAU8540_REG_MISC_CTRL, 0x0000}, - {NAU8540_REG_I2C_CTRL, 0xEFFF}, - {NAU8540_REG_VMID_CTRL, 0x0000}, - {NAU8540_REG_MUTE, 0x0000}, - {NAU8540_REG_ANALOG_ADC1, 0x0011}, - {NAU8540_REG_ANALOG_ADC2, 0x0020}, - {NAU8540_REG_ANALOG_PWR, 0x0000}, - {NAU8540_REG_MIC_BIAS, 0x0004}, - {NAU8540_REG_REFERENCE, 0x0000}, - {NAU8540_REG_FEPGA1, 0x0000}, - {NAU8540_REG_FEPGA2, 0x0000}, - {NAU8540_REG_FEPGA3, 0x0101}, - {NAU8540_REG_FEPGA4, 0x0101}, - {NAU8540_REG_PWR, 0x0000}, + {NAU8540_REG_POWER_MANAGEMENT, 0x0000}, + {NAU8540_REG_CLOCK_CTRL, 0x0000}, + {NAU8540_REG_CLOCK_SRC, 0x0000}, + {NAU8540_REG_FLL1, 0x0001}, + {NAU8540_REG_FLL2, 0x3126}, + {NAU8540_REG_FLL3, 0x0008}, + {NAU8540_REG_FLL4, 0x0010}, + {NAU8540_REG_FLL5, 0xC000}, + {NAU8540_REG_FLL6, 0x6000}, + {NAU8540_REG_FLL_VCO_RSV, 0xF13C}, + {NAU8540_REG_PCM_CTRL0, 0x000B}, + {NAU8540_REG_PCM_CTRL1, 0x3010}, + {NAU8540_REG_PCM_CTRL2, 0x0800}, + {NAU8540_REG_PCM_CTRL3, 0x0000}, + {NAU8540_REG_PCM_CTRL4, 0x000F}, + {NAU8540_REG_ALC_CONTROL_1, 0x0000}, + {NAU8540_REG_ALC_CONTROL_2, 0x700B}, + {NAU8540_REG_ALC_CONTROL_3, 0x0022}, + {NAU8540_REG_ALC_CONTROL_4, 0x1010}, + {NAU8540_REG_ALC_CONTROL_5, 0x1010}, + {NAU8540_REG_NOTCH_FIL1_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH4, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH4, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH12, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH34, 0x0000}, + {NAU8540_REG_ADC_SAMPLE_RATE, 0x0002}, + {NAU8540_REG_DIGITAL_GAIN_CH1, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH2, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH3, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH4, 0x0400}, + {NAU8540_REG_DIGITAL_MUX, 0x00E4}, + {NAU8540_REG_GPIO_CTRL, 0x0000}, + {NAU8540_REG_MISC_CTRL, 0x0000}, + {NAU8540_REG_I2C_CTRL, 0xEFFF}, + {NAU8540_REG_VMID_CTRL, 0x0000}, + {NAU8540_REG_MUTE, 0x0000}, + {NAU8540_REG_ANALOG_ADC1, 0x0011}, + {NAU8540_REG_ANALOG_ADC2, 0x0020}, + {NAU8540_REG_ANALOG_PWR, 0x0000}, + {NAU8540_REG_MIC_BIAS, 0x0004}, + {NAU8540_REG_REFERENCE, 0x0000}, + {NAU8540_REG_FEPGA1, 0x0000}, + {NAU8540_REG_FEPGA2, 0x0000}, + {NAU8540_REG_FEPGA3, 0x0101}, + {NAU8540_REG_FEPGA4, 0x0101}, + {NAU8540_REG_PWR, 0x0000}, }; static bool nau8540_readable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_POWER_MANAGEMENT ... NAU8540_REG_FLL_VCO_RSV: - case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: - case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: - case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ADC_SAMPLE_RATE: - case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: - case NAU8540_REG_P2P_CH1 ... NAU8540_REG_I2C_CTRL: - case NAU8540_REG_I2C_DEVICE_ID: - case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: - case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_POWER_MANAGEMENT ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } } static bool nau8540_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_SW_RESET ... NAU8540_REG_FLL_VCO_RSV: - case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: - case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: - case NAU8540_REG_NOTCH_FIL1_CH1 ... NAU8540_REG_ADC_SAMPLE_RATE: - case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: - case NAU8540_REG_GPIO_CTRL ... NAU8540_REG_I2C_CTRL: - case NAU8540_REG_RST: - case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: - case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_SW_RESET ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_NOTCH_FIL1_CH1 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_GPIO_CTRL ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_RST: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } } static bool nau8540_volatile_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case NAU8540_REG_SW_RESET: - case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ALC_STATUS: - case NAU8540_REG_P2P_CH1 ... NAU8540_REG_PEAK_CH4: - case NAU8540_REG_I2C_DEVICE_ID: - case NAU8540_REG_RST: - return true; - default: - return false; - } + switch (reg) { + case NAU8540_REG_SW_RESET: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ALC_STATUS: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_PEAK_CH4: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_RST: + return true; + default: + return false; + } } @@ -187,255 +187,255 @@ static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, -12800, 3600); static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); static const struct snd_kcontrol_new nau8540_snd_controls[] = { - SOC_SINGLE_TLV("Mic1 Volume", NAU8540_REG_DIGITAL_GAIN_CH1, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic2 Volume", NAU8540_REG_DIGITAL_GAIN_CH2, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic3 Volume", NAU8540_REG_DIGITAL_GAIN_CH3, - 0, 0x520, 0, adc_vol_tlv), - SOC_SINGLE_TLV("Mic4 Volume", NAU8540_REG_DIGITAL_GAIN_CH4, - 0, 0x520, 0, adc_vol_tlv), - - SOC_SINGLE_TLV("Frontend PGA1 Volume", NAU8540_REG_FEPGA3, - 0, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA2 Volume", NAU8540_REG_FEPGA3, - 8, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA3 Volume", NAU8540_REG_FEPGA4, - 0, 0x25, 0, fepga_gain_tlv), - SOC_SINGLE_TLV("Frontend PGA4 Volume", NAU8540_REG_FEPGA4, - 8, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Mic1 Volume", NAU8540_REG_DIGITAL_GAIN_CH1, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic2 Volume", NAU8540_REG_DIGITAL_GAIN_CH2, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic3 Volume", NAU8540_REG_DIGITAL_GAIN_CH3, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic4 Volume", NAU8540_REG_DIGITAL_GAIN_CH4, + 0, 0x520, 0, adc_vol_tlv), + + SOC_SINGLE_TLV("Frontend PGA1 Volume", NAU8540_REG_FEPGA3, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA2 Volume", NAU8540_REG_FEPGA3, + 8, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA3 Volume", NAU8540_REG_FEPGA4, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA4 Volume", NAU8540_REG_FEPGA4, + 8, 0x25, 0, fepga_gain_tlv), }; static const char * const adc_channel[] = { - "ADC channel 1", "ADC channel 2", "ADC channel 3", "ADC channel 4" + "ADC channel 1", "ADC channel 2", "ADC channel 3", "ADC channel 4" }; static SOC_ENUM_SINGLE_DECL( - digital_ch4_enum, NAU8540_REG_DIGITAL_MUX, 6, adc_channel); + digital_ch4_enum, NAU8540_REG_DIGITAL_MUX, 6, adc_channel); static const struct snd_kcontrol_new digital_ch4_mux = - SOC_DAPM_ENUM("Digital CH4 Select", digital_ch4_enum); + SOC_DAPM_ENUM("Digital CH4 Select", digital_ch4_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch3_enum, NAU8540_REG_DIGITAL_MUX, 4, adc_channel); + digital_ch3_enum, NAU8540_REG_DIGITAL_MUX, 4, adc_channel); static const struct snd_kcontrol_new digital_ch3_mux = - SOC_DAPM_ENUM("Digital CH3 Select", digital_ch3_enum); + SOC_DAPM_ENUM("Digital CH3 Select", digital_ch3_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch2_enum, NAU8540_REG_DIGITAL_MUX, 2, adc_channel); + digital_ch2_enum, NAU8540_REG_DIGITAL_MUX, 2, adc_channel); static const struct snd_kcontrol_new digital_ch2_mux = - SOC_DAPM_ENUM("Digital CH2 Select", digital_ch2_enum); + SOC_DAPM_ENUM("Digital CH2 Select", digital_ch2_enum); static SOC_ENUM_SINGLE_DECL( - digital_ch1_enum, NAU8540_REG_DIGITAL_MUX, 0, adc_channel); + digital_ch1_enum, NAU8540_REG_DIGITAL_MUX, 0, adc_channel); static const struct snd_kcontrol_new digital_ch1_mux = - SOC_DAPM_ENUM("Digital CH1 Select", digital_ch1_enum); + SOC_DAPM_ENUM("Digital CH1 Select", digital_ch1_enum); static const struct snd_soc_dapm_widget nau8540_dapm_widgets[] = { - SND_SOC_DAPM_SUPPLY("MICBIAS2", NAU8540_REG_MIC_BIAS, 11, 0, NULL, 0), - SND_SOC_DAPM_SUPPLY("MICBIAS1", NAU8540_REG_MIC_BIAS, 10, 0, NULL, 0), - - SND_SOC_DAPM_INPUT("MIC1"), - SND_SOC_DAPM_INPUT("MIC2"), - SND_SOC_DAPM_INPUT("MIC3"), - SND_SOC_DAPM_INPUT("MIC4"), - - SND_SOC_DAPM_PGA("Frontend PGA1", NAU8540_REG_PWR, 12, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA2", NAU8540_REG_PWR, 13, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA3", NAU8540_REG_PWR, 14, 0, NULL, 0), - SND_SOC_DAPM_PGA("Frontend PGA4", NAU8540_REG_PWR, 15, 0, NULL, 0), - - SND_SOC_DAPM_ADC("ADC1", NULL, - NAU8540_REG_POWER_MANAGEMENT, 0, 0), - SND_SOC_DAPM_ADC("ADC2", NULL, - NAU8540_REG_POWER_MANAGEMENT, 1, 0), - SND_SOC_DAPM_ADC("ADC3", NULL, - NAU8540_REG_POWER_MANAGEMENT, 2, 0), - SND_SOC_DAPM_ADC("ADC4", NULL, - NAU8540_REG_POWER_MANAGEMENT, 3, 0), - - SND_SOC_DAPM_PGA("ADC CH1", NAU8540_REG_ANALOG_PWR, 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH2", NAU8540_REG_ANALOG_PWR, 1, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH3", NAU8540_REG_ANALOG_PWR, 2, 0, NULL, 0), - SND_SOC_DAPM_PGA("ADC CH4", NAU8540_REG_ANALOG_PWR, 3, 0, NULL, 0), - - SND_SOC_DAPM_MUX("Digital CH4 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch4_mux), - SND_SOC_DAPM_MUX("Digital CH3 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch3_mux), - SND_SOC_DAPM_MUX("Digital CH2 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch2_mux), - SND_SOC_DAPM_MUX("Digital CH1 Mux", - SND_SOC_NOPM, 0, 0, &digital_ch1_mux), - - SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", NAU8540_REG_MIC_BIAS, 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", NAU8540_REG_MIC_BIAS, 10, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + SND_SOC_DAPM_INPUT("MIC4"), + + SND_SOC_DAPM_PGA("Frontend PGA1", NAU8540_REG_PWR, 12, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA2", NAU8540_REG_PWR, 13, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA3", NAU8540_REG_PWR, 14, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA4", NAU8540_REG_PWR, 15, 0, NULL, 0), + + SND_SOC_DAPM_ADC("ADC1", NULL, + NAU8540_REG_POWER_MANAGEMENT, 0, 0), + SND_SOC_DAPM_ADC("ADC2", NULL, + NAU8540_REG_POWER_MANAGEMENT, 1, 0), + SND_SOC_DAPM_ADC("ADC3", NULL, + NAU8540_REG_POWER_MANAGEMENT, 2, 0), + SND_SOC_DAPM_ADC("ADC4", NULL, + NAU8540_REG_POWER_MANAGEMENT, 3, 0), + + SND_SOC_DAPM_PGA("ADC CH1", NAU8540_REG_ANALOG_PWR, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH2", NAU8540_REG_ANALOG_PWR, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH3", NAU8540_REG_ANALOG_PWR, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH4", NAU8540_REG_ANALOG_PWR, 3, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Digital CH4 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch4_mux), + SND_SOC_DAPM_MUX("Digital CH3 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch3_mux), + SND_SOC_DAPM_MUX("Digital CH2 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch2_mux), + SND_SOC_DAPM_MUX("Digital CH1 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch1_mux), + + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), }; static const struct snd_soc_dapm_route nau8540_dapm_routes[] = { - {"Frontend PGA1", NULL, "MIC1"}, - {"Frontend PGA2", NULL, "MIC2"}, - {"Frontend PGA3", NULL, "MIC3"}, - {"Frontend PGA4", NULL, "MIC4"}, - - {"ADC1", NULL, "Frontend PGA1"}, - {"ADC2", NULL, "Frontend PGA2"}, - {"ADC3", NULL, "Frontend PGA3"}, - {"ADC4", NULL, "Frontend PGA4"}, - - {"ADC CH1", NULL, "ADC1"}, - {"ADC CH2", NULL, "ADC2"}, - {"ADC CH3", NULL, "ADC3"}, - {"ADC CH4", NULL, "ADC4"}, - - {"ADC1", NULL, "MICBIAS1"}, - {"ADC2", NULL, "MICBIAS1"}, - {"ADC3", NULL, "MICBIAS2"}, - {"ADC4", NULL, "MICBIAS2"}, - - {"Digital CH1 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH1 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH1 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH1 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH2 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH2 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH2 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH2 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH3 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH3 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH3 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH3 Mux", "ADC channel 4", "ADC CH4"}, - - {"Digital CH4 Mux", "ADC channel 1", "ADC CH1"}, - {"Digital CH4 Mux", "ADC channel 2", "ADC CH2"}, - {"Digital CH4 Mux", "ADC channel 3", "ADC CH3"}, - {"Digital CH4 Mux", "ADC channel 4", "ADC CH4"}, - - {"AIFTX", NULL, "Digital CH1 Mux"}, - {"AIFTX", NULL, "Digital CH2 Mux"}, - {"AIFTX", NULL, "Digital CH3 Mux"}, - {"AIFTX", NULL, "Digital CH4 Mux"}, + {"Frontend PGA1", NULL, "MIC1"}, + {"Frontend PGA2", NULL, "MIC2"}, + {"Frontend PGA3", NULL, "MIC3"}, + {"Frontend PGA4", NULL, "MIC4"}, + + {"ADC1", NULL, "Frontend PGA1"}, + {"ADC2", NULL, "Frontend PGA2"}, + {"ADC3", NULL, "Frontend PGA3"}, + {"ADC4", NULL, "Frontend PGA4"}, + + {"ADC CH1", NULL, "ADC1"}, + {"ADC CH2", NULL, "ADC2"}, + {"ADC CH3", NULL, "ADC3"}, + {"ADC CH4", NULL, "ADC4"}, + + {"ADC1", NULL, "MICBIAS1"}, + {"ADC2", NULL, "MICBIAS1"}, + {"ADC3", NULL, "MICBIAS2"}, + {"ADC4", NULL, "MICBIAS2"}, + + {"Digital CH1 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH1 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH1 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH1 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH2 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH2 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH2 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH2 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH3 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH3 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH3 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH3 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH4 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH4 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH4 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH4 Mux", "ADC channel 4", "ADC CH4"}, + + {"AIFTX", NULL, "Digital CH1 Mux"}, + {"AIFTX", NULL, "Digital CH2 Mux"}, + {"AIFTX", NULL, "Digital CH3 Mux"}, + {"AIFTX", NULL, "Digital CH4 Mux"}, }; static int nau8540_clock_check(struct nau8540 *nau8540, int rate, int osr) { - int osrate; + int osrate; - if (osr >= ARRAY_SIZE(osr_adc_sel)) - return -EINVAL; - osrate = osr_adc_sel[osr].osr; + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; - if (rate * osr > CLK_ADC_MAX) { - dev_err(nau8540->dev, "exceed the maximum frequency of CLK_ADC\n"); - return -EINVAL; - } + if (rate * osr > CLK_ADC_MAX) { + dev_err(nau8540->dev, "exceed the maximum frequency of CLK_ADC\n"); + return -EINVAL; + } - return 0; + return 0; } static int nau8540_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int val_len = 0, osr; - - /* CLK_ADC = OSR * FS - * ADC clock frequency is defined as Over Sampling Rate (OSR) - * multiplied by the audio sample rate (Fs). Note that the OSR and Fs - * values must be selected such that the maximum frequency is less - * than 6.144 MHz. - */ - regmap_read(nau8540->regmap, NAU8540_REG_ADC_SAMPLE_RATE, &osr); - osr &= NAU8540_ADC_OSR_MASK; - if (nau8540_clock_check(nau8540, params_rate(params), osr)) - return -EINVAL; - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_ADC_SRC_MASK, - osr_adc_sel[osr].clk_src << NAU8540_CLK_ADC_SRC_SFT); - - switch (params_width(params)) { - case 16: - val_len |= NAU8540_I2S_DL_16; - break; - case 20: - val_len |= NAU8540_I2S_DL_20; - break; - case 24: - val_len |= NAU8540_I2S_DL_24; - break; - case 32: - val_len |= NAU8540_I2S_DL_32; - break; - default: - return -EINVAL; - } - - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, - NAU8540_I2S_DL_MASK, val_len); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int val_len = 0, osr; + + /* CLK_ADC = OSR * FS + * ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + regmap_read(nau8540->regmap, NAU8540_REG_ADC_SAMPLE_RATE, &osr); + osr &= NAU8540_ADC_OSR_MASK; + if (nau8540_clock_check(nau8540, params_rate(params), osr)) + return -EINVAL; + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8540_CLK_ADC_SRC_SFT); + + switch (params_width(params)) { + case 16: + val_len |= NAU8540_I2S_DL_16; + break; + case 20: + val_len |= NAU8540_I2S_DL_20; + break; + case 24: + val_len |= NAU8540_I2S_DL_24; + break; + case 32: + val_len |= NAU8540_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK, val_len); + + return 0; } static int nau8540_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int ctrl1_val = 0, ctrl2_val = 0; - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - ctrl2_val |= NAU8540_I2S_MS_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFS: - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - break; - case SND_SOC_DAIFMT_IB_NF: - ctrl1_val |= NAU8540_I2S_BP_INV; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - ctrl1_val |= NAU8540_I2S_DF_I2S; - break; - case SND_SOC_DAIFMT_LEFT_J: - ctrl1_val |= NAU8540_I2S_DF_LEFT; - break; - case SND_SOC_DAIFMT_RIGHT_J: - ctrl1_val |= NAU8540_I2S_DF_RIGTH; - break; - case SND_SOC_DAIFMT_DSP_A: - ctrl1_val |= NAU8540_I2S_DF_PCM_AB; - break; - case SND_SOC_DAIFMT_DSP_B: - ctrl1_val |= NAU8540_I2S_DF_PCM_AB; - ctrl1_val |= NAU8540_I2S_PCMB_EN; - break; - default: - return -EINVAL; - } - - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, - NAU8540_I2S_DL_MASK | NAU8540_I2S_DF_MASK | - NAU8540_I2S_BP_INV | NAU8540_I2S_PCMB_EN, ctrl1_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, - NAU8540_I2S_MS_MASK | NAU8540_I2S_DO12_OE, ctrl2_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, - NAU8540_I2S_DO34_OE, 0); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8540_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8540_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8540_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8540_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8540_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + ctrl1_val |= NAU8540_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK | NAU8540_I2S_DF_MASK | + NAU8540_I2S_BP_INV | NAU8540_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_MS_MASK | NAU8540_I2S_DO12_OE, ctrl2_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE, 0); + + return 0; } /** @@ -451,55 +451,55 @@ static int nau8540_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) * Configures a DAI for TDM operation. Only support 4 slots TDM. */ static int nau8540_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { - struct snd_soc_codec *codec = dai->codec; - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - unsigned int ctrl2_val = 0, ctrl4_val = 0; - - if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf))) - return -EINVAL; - - ctrl4_val |= (NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN); - if (tx_mask & 0xf0) { - ctrl2_val = 4 * slot_width; - ctrl4_val |= (tx_mask >> 4); - } else { - ctrl4_val |= tx_mask; - } - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL4, - NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN | - NAU8540_TDM_TX_MASK, ctrl4_val); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, - NAU8540_I2S_DO12_OE, NAU8540_I2S_DO12_OE); - regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, - NAU8540_I2S_DO34_OE | NAU8540_I2S_TSLOT_L_MASK, - NAU8540_I2S_DO34_OE | ctrl2_val); - - return 0; + struct snd_soc_codec *codec = dai->codec; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl2_val = 0, ctrl4_val = 0; + + if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf))) + return -EINVAL; + + ctrl4_val |= (NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN); + if (tx_mask & 0xf0) { + ctrl2_val = 4 * slot_width; + ctrl4_val |= (tx_mask >> 4); + } else { + ctrl4_val |= tx_mask; + } + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL4, + NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN | + NAU8540_TDM_TX_MASK, ctrl4_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_OE, NAU8540_I2S_DO12_OE); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE | NAU8540_I2S_TSLOT_L_MASK, + NAU8540_I2S_DO34_OE | ctrl2_val); + + return 0; } static const struct snd_soc_dai_ops nau8540_dai_ops = { - .hw_params = nau8540_hw_params, - .set_fmt = nau8540_set_fmt, - .set_tdm_slot = nau8540_set_tdm_slot, + .hw_params = nau8540_hw_params, + .set_fmt = nau8540_set_fmt, + .set_tdm_slot = nau8540_set_tdm_slot, }; #define NAU8540_RATES SNDRV_PCM_RATE_8000_48000 #define NAU8540_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ - | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_driver nau8540_dai = { - .name = "nau8540-hifi", - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 4, - .rates = NAU8540_RATES, - .formats = NAU8540_FORMATS, - }, - .ops = &nau8540_dai_ops, + .name = "nau8540-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = NAU8540_RATES, + .formats = NAU8540_FORMATS, + }, + .ops = &nau8540_dai_ops, }; /** @@ -513,320 +513,320 @@ static struct snd_soc_dai_driver nau8540_dai = { * Returns 0 for success or negative error code. */ static int nau8540_calc_fll_param(unsigned int fll_in, - unsigned int fs, struct nau8540_fll *fll_param) + unsigned int fs, struct nau8540_fll *fll_param) { - u64 fvco, fvco_max; - unsigned int fref, i, fvco_sel; - - /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing - * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. - * FREF = freq_in / NAU8540_FLL_REF_DIV_MASK - */ - for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { - fref = fll_in / fll_pre_scalar[i].param; - if (fref <= NAU_FREF_MAX) - break; - } - if (i == ARRAY_SIZE(fll_pre_scalar)) - return -EINVAL; - fll_param->clk_ref_div = fll_pre_scalar[i].val; - - /* Choose the FLL ratio based on FREF */ - for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { - if (fref >= fll_ratio[i].param) - break; - } - if (i == ARRAY_SIZE(fll_ratio)) - return -EINVAL; - fll_param->ratio = fll_ratio[i].val; - - /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. - * FDCO must be within the 90MHz - 124MHz or the FFL cannot be - * guaranteed across the full range of operation. - * FDCO = freq_out * 2 * mclk_src_scaling - */ - fvco_max = 0; - fvco_sel = ARRAY_SIZE(mclk_src_scaling); - for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { - fvco = 256 * fs * 2 * mclk_src_scaling[i].param; - if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && - fvco_max < fvco) { - fvco_max = fvco; - fvco_sel = i; - } - } - if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) - return -EINVAL; - fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; - - /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional - * input based on FDCO, FREF and FLL ratio. - */ - fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); - fll_param->fll_int = (fvco >> 16) & 0x3FF; - fll_param->fll_frac = fvco & 0xFFFF; - return 0; + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8540_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256 * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; } static void nau8540_fll_apply(struct regmap *regmap, - struct nau8540_fll *fll_param) + struct nau8540_fll *fll_param) { - regmap_update_bits(regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK | NAU8540_CLK_MCLK_SRC_MASK, - NAU8540_CLK_SRC_MCLK | fll_param->mclk_src); - regmap_update_bits(regmap, NAU8540_REG_FLL1, - NAU8540_FLL_RATIO_MASK, fll_param->ratio); - /* FLL 16-bit fractional input */ - regmap_write(regmap, NAU8540_REG_FLL2, fll_param->fll_frac); - /* FLL 10-bit integer input */ - regmap_update_bits(regmap, NAU8540_REG_FLL3, - NAU8540_FLL_INTEGER_MASK, fll_param->fll_int); - /* FLL pre-scaler */ - regmap_update_bits(regmap, NAU8540_REG_FLL4, - NAU8540_FLL_REF_DIV_MASK, - fll_param->clk_ref_div << NAU8540_FLL_REF_DIV_SFT); - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_CLK_SW_MASK, NAU8540_FLL_CLK_SW_REF); - regmap_update_bits(regmap, - NAU8540_REG_FLL6, NAU8540_DCO_EN, 0); - if (fll_param->fll_frac) { - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_MASK, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_FILTER); - regmap_update_bits(regmap, NAU8540_REG_FLL6, - NAU8540_SDM_EN, NAU8540_SDM_EN); - } else { - regmap_update_bits(regmap, NAU8540_REG_FLL5, - NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | - NAU8540_FLL_FTR_SW_MASK, NAU8540_FLL_FTR_SW_ACCU); - regmap_update_bits(regmap, - NAU8540_REG_FLL6, NAU8540_SDM_EN, 0); - } + regmap_update_bits(regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK | NAU8540_CLK_MCLK_SRC_MASK, + NAU8540_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8540_REG_FLL1, + NAU8540_FLL_RATIO_MASK, fll_param->ratio); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8540_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8540_REG_FLL3, + NAU8540_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8540_REG_FLL4, + NAU8540_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8540_FLL_REF_DIV_SFT); + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_CLK_SW_MASK, NAU8540_FLL_CLK_SW_REF); + regmap_update_bits(regmap, + NAU8540_REG_FLL6, NAU8540_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8540_REG_FLL6, + NAU8540_SDM_EN, NAU8540_SDM_EN); + } else { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, NAU8540_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, + NAU8540_REG_FLL6, NAU8540_SDM_EN, 0); + } } /* freq_out must be 256*Fs in order to achieve the best performance */ static int nau8540_set_pll(struct snd_soc_codec *codec, int pll_id, int source, - unsigned int freq_in, unsigned int freq_out) + unsigned int freq_in, unsigned int freq_out) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - struct nau8540_fll fll_param; - int ret, fs; - - switch (pll_id) { - case NAU8540_CLK_FLL_MCLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_MCLK); - break; - - case NAU8540_CLK_FLL_BLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_BLK); - break; - - case NAU8540_CLK_FLL_FS: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, - NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_FS); - break; - - default: - dev_err(nau8540->dev, "Invalid clock id (%d)\n", pll_id); - return -EINVAL; - } - dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", - freq_out, pll_id); - - fs = freq_out / 256; - ret = nau8540_calc_fll_param(freq_in, fs, &fll_param); - if (ret < 0) { - dev_err(nau8540->dev, "Unsupported input clock %d\n", freq_in); - return ret; - } - dev_dbg(nau8540->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", - fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, - fll_param.fll_int, fll_param.clk_ref_div); - - nau8540_fll_apply(nau8540->regmap, &fll_param); - mdelay(2); - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); - - return 0; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540_fll fll_param; + int ret, fs; + + switch (pll_id) { + case NAU8540_CLK_FLL_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_MCLK); + break; + + case NAU8540_CLK_FLL_BLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_BLK); + break; + + case NAU8540_CLK_FLL_FS: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK, NAU8540_FLL_CLK_SRC_FS); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", pll_id); + return -EINVAL; + } + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq_out, pll_id); + + fs = freq_out / 256; + ret = nau8540_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8540->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8540->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8540_fll_apply(nau8540->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + + return 0; } static int nau8540_set_sysclk(struct snd_soc_codec *codec, - int clk_id, int source, unsigned int freq, int dir) + int clk_id, int source, unsigned int freq, int dir) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - - switch (clk_id) { - case NAU8540_CLK_DIS: - case NAU8540_CLK_MCLK: - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_MCLK); - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, - NAU8540_DCO_EN, 0); - break; - - case NAU8540_CLK_INTERNAL: - regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, - NAU8540_DCO_EN, NAU8540_DCO_EN); - regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, - NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); - break; - - default: - dev_err(nau8540->dev, "Invalid clock id (%d)\n", clk_id); - return -EINVAL; - } - - dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", - freq, clk_id); - - return 0; + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + + switch (clk_id) { + case NAU8540_CLK_DIS: + case NAU8540_CLK_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_MCLK); + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, 0); + break; + + case NAU8540_CLK_INTERNAL: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, NAU8540_DCO_EN); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; } static void nau8540_reset_chip(struct regmap *regmap) { - regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); - regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); } static void nau8540_init_regs(struct nau8540 *nau8540) { - struct regmap *regmap = nau8540->regmap; - - /* Enable Bias/VMID/VMID Tieoff */ - regmap_update_bits(regmap, NAU8540_REG_VMID_CTRL, - NAU8540_VMID_EN | NAU8540_VMID_SEL_MASK, - NAU8540_VMID_EN | (0x2 << NAU8540_VMID_SEL_SFT)); - regmap_update_bits(regmap, NAU8540_REG_REFERENCE, - NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN, - NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN); - mdelay(2); - regmap_update_bits(regmap, NAU8540_REG_MIC_BIAS, - NAU8540_PU_PRE, NAU8540_PU_PRE); - regmap_update_bits(regmap, NAU8540_REG_CLOCK_CTRL, - NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN, - NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN); - /* ADC OSR selection, CLK_ADC = Fs * OSR */ - regmap_update_bits(regmap, NAU8540_REG_ADC_SAMPLE_RATE, - NAU8540_ADC_OSR_MASK, NAU8540_ADC_OSR_64); + struct regmap *regmap = nau8540->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8540_REG_VMID_CTRL, + NAU8540_VMID_EN | NAU8540_VMID_SEL_MASK, + NAU8540_VMID_EN | (0x2 << NAU8540_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8540_REG_REFERENCE, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8540_REG_MIC_BIAS, + NAU8540_PU_PRE, NAU8540_PU_PRE); + regmap_update_bits(regmap, NAU8540_REG_CLOCK_CTRL, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN); + /* ADC OSR selection, CLK_ADC = Fs * OSR */ + regmap_update_bits(regmap, NAU8540_REG_ADC_SAMPLE_RATE, + NAU8540_ADC_OSR_MASK, NAU8540_ADC_OSR_64); } static int __maybe_unused nau8540_suspend(struct snd_soc_codec *codec) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - regcache_cache_only(nau8540->regmap, true); - regcache_mark_dirty(nau8540->regmap); + regcache_cache_only(nau8540->regmap, true); + regcache_mark_dirty(nau8540->regmap); - return 0; + return 0; } static int __maybe_unused nau8540_resume(struct snd_soc_codec *codec) { - struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); + struct nau8540 *nau8540 = snd_soc_codec_get_drvdata(codec); - regcache_cache_only(nau8540->regmap, false); - regcache_sync(nau8540->regmap); + regcache_cache_only(nau8540->regmap, false); + regcache_sync(nau8540->regmap); - return 0; + return 0; } static struct snd_soc_codec_driver nau8540_codec_driver = { - .set_sysclk = nau8540_set_sysclk, - .set_pll = nau8540_set_pll, - .suspend = nau8540_suspend, - .resume = nau8540_resume, - .suspend_bias_off = true, - - .component_driver = { - .controls = nau8540_snd_controls, - .num_controls = ARRAY_SIZE(nau8540_snd_controls), - .dapm_widgets = nau8540_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(nau8540_dapm_widgets), - .dapm_routes = nau8540_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(nau8540_dapm_routes), - }, + .set_sysclk = nau8540_set_sysclk, + .set_pll = nau8540_set_pll, + .suspend = nau8540_suspend, + .resume = nau8540_resume, + .suspend_bias_off = true, + + .component_driver = { + .controls = nau8540_snd_controls, + .num_controls = ARRAY_SIZE(nau8540_snd_controls), + .dapm_widgets = nau8540_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8540_dapm_widgets), + .dapm_routes = nau8540_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8540_dapm_routes), + }, }; static const struct regmap_config nau8540_regmap_config = { - .val_bits = 16, - .reg_bits = 16, + .val_bits = 16, + .reg_bits = 16, - .max_register = NAU8540_REG_MAX, - .readable_reg = nau8540_readable_reg, - .writeable_reg = nau8540_writeable_reg, - .volatile_reg = nau8540_volatile_reg, + .max_register = NAU8540_REG_MAX, + .readable_reg = nau8540_readable_reg, + .writeable_reg = nau8540_writeable_reg, + .volatile_reg = nau8540_volatile_reg, - .cache_type = REGCACHE_RBTREE, - .reg_defaults = nau8540_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(nau8540_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8540_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8540_reg_defaults), }; static int nau8540_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { - struct device *dev = &i2c->dev; - struct nau8540 *nau8540 = dev_get_platdata(dev); - int ret, value; - - if (!nau8540) { - nau8540 = devm_kzalloc(dev, sizeof(*nau8540), GFP_KERNEL); - if (!nau8540) - return -ENOMEM; - } - i2c_set_clientdata(i2c, nau8540); - - nau8540->regmap = devm_regmap_init_i2c(i2c, &nau8540_regmap_config); - if (IS_ERR(nau8540->regmap)) - return PTR_ERR(nau8540->regmap); - ret = regmap_read(nau8540->regmap, NAU8540_REG_I2C_DEVICE_ID, &value); - if (ret < 0) { - dev_err(dev, "Failed to read device id from the NAU85L40: %d\n", - ret); - return ret; - } - - nau8540->dev = dev; - nau8540_reset_chip(nau8540->regmap); - nau8540_init_regs(nau8540); - - return snd_soc_register_codec(dev, - &nau8540_codec_driver, &nau8540_dai, 1); + struct device *dev = &i2c->dev; + struct nau8540 *nau8540 = dev_get_platdata(dev); + int ret, value; + + if (!nau8540) { + nau8540 = devm_kzalloc(dev, sizeof(*nau8540), GFP_KERNEL); + if (!nau8540) + return -ENOMEM; + } + i2c_set_clientdata(i2c, nau8540); + + nau8540->regmap = devm_regmap_init_i2c(i2c, &nau8540_regmap_config); + if (IS_ERR(nau8540->regmap)) + return PTR_ERR(nau8540->regmap); + ret = regmap_read(nau8540->regmap, NAU8540_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU85L40: %d\n", + ret); + return ret; + } + + nau8540->dev = dev; + nau8540_reset_chip(nau8540->regmap); + nau8540_init_regs(nau8540); + + return snd_soc_register_codec(dev, + &nau8540_codec_driver, &nau8540_dai, 1); } static int nau8540_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); - return 0; + snd_soc_unregister_codec(&client->dev); + return 0; } static const struct i2c_device_id nau8540_i2c_ids[] = { - { "nau8540", 0 }, - { } + { "nau8540", 0 }, + { } }; MODULE_DEVICE_TABLE(i2c, nau8540_i2c_ids); #ifdef CONFIG_OF static const struct of_device_id nau8540_of_ids[] = { - { .compatible = "nuvoton,nau8540", }, - {} + { .compatible = "nuvoton,nau8540", }, + {} }; MODULE_DEVICE_TABLE(of, nau8540_of_ids); #endif static struct i2c_driver nau8540_i2c_driver = { - .driver = { - .name = "nau8540", - .of_match_table = of_match_ptr(nau8540_of_ids), - }, - .probe = nau8540_i2c_probe, - .remove = nau8540_i2c_remove, - .id_table = nau8540_i2c_ids, + .driver = { + .name = "nau8540", + .of_match_table = of_match_ptr(nau8540_of_ids), + }, + .probe = nau8540_i2c_probe, + .remove = nau8540_i2c_remove, + .id_table = nau8540_i2c_ids, }; module_i2c_driver(nau8540_i2c_driver); diff --git a/sound/soc/codecs/nau8540.h b/sound/soc/codecs/nau8540.h index d06e65188cd5..5db5b224944d 100644 --- a/sound/soc/codecs/nau8540.h +++ b/sound/soc/codecs/nau8540.h @@ -12,211 +12,211 @@ #ifndef __NAU8540_H__ #define __NAU8540_H__ -#define NAU8540_REG_SW_RESET 0x00 -#define NAU8540_REG_POWER_MANAGEMENT 0x01 -#define NAU8540_REG_CLOCK_CTRL 0x02 -#define NAU8540_REG_CLOCK_SRC 0x03 -#define NAU8540_REG_FLL1 0x04 -#define NAU8540_REG_FLL2 0x05 -#define NAU8540_REG_FLL3 0x06 -#define NAU8540_REG_FLL4 0x07 -#define NAU8540_REG_FLL5 0x08 -#define NAU8540_REG_FLL6 0x09 -#define NAU8540_REG_FLL_VCO_RSV 0x0A -#define NAU8540_REG_PCM_CTRL0 0x10 -#define NAU8540_REG_PCM_CTRL1 0x11 -#define NAU8540_REG_PCM_CTRL2 0x12 -#define NAU8540_REG_PCM_CTRL3 0x13 -#define NAU8540_REG_PCM_CTRL4 0x14 -#define NAU8540_REG_ALC_CONTROL_1 0x20 -#define NAU8540_REG_ALC_CONTROL_2 0x21 -#define NAU8540_REG_ALC_CONTROL_3 0x22 -#define NAU8540_REG_ALC_CONTROL_4 0x23 -#define NAU8540_REG_ALC_CONTROL_5 0x24 -#define NAU8540_REG_ALC_GAIN_CH12 0x2D -#define NAU8540_REG_ALC_GAIN_CH34 0x2E -#define NAU8540_REG_ALC_STATUS 0x2F -#define NAU8540_REG_NOTCH_FIL1_CH1 0x30 -#define NAU8540_REG_NOTCH_FIL2_CH1 0x31 -#define NAU8540_REG_NOTCH_FIL1_CH2 0x32 -#define NAU8540_REG_NOTCH_FIL2_CH2 0x33 -#define NAU8540_REG_NOTCH_FIL1_CH3 0x34 -#define NAU8540_REG_NOTCH_FIL2_CH3 0x35 -#define NAU8540_REG_NOTCH_FIL1_CH4 0x36 -#define NAU8540_REG_NOTCH_FIL2_CH4 0x37 -#define NAU8540_REG_HPF_FILTER_CH12 0x38 -#define NAU8540_REG_HPF_FILTER_CH34 0x39 -#define NAU8540_REG_ADC_SAMPLE_RATE 0x3A -#define NAU8540_REG_DIGITAL_GAIN_CH1 0x40 -#define NAU8540_REG_DIGITAL_GAIN_CH2 0x41 -#define NAU8540_REG_DIGITAL_GAIN_CH3 0x42 -#define NAU8540_REG_DIGITAL_GAIN_CH4 0x43 -#define NAU8540_REG_DIGITAL_MUX 0x44 -#define NAU8540_REG_P2P_CH1 0x48 -#define NAU8540_REG_P2P_CH2 0x49 -#define NAU8540_REG_P2P_CH3 0x4A -#define NAU8540_REG_P2P_CH4 0x4B -#define NAU8540_REG_PEAK_CH1 0x4C -#define NAU8540_REG_PEAK_CH2 0x4D -#define NAU8540_REG_PEAK_CH3 0x4E -#define NAU8540_REG_PEAK_CH4 0x4F -#define NAU8540_REG_GPIO_CTRL 0x50 -#define NAU8540_REG_MISC_CTRL 0x51 -#define NAU8540_REG_I2C_CTRL 0x52 -#define NAU8540_REG_I2C_DEVICE_ID 0x58 -#define NAU8540_REG_RST 0x5A -#define NAU8540_REG_VMID_CTRL 0x60 -#define NAU8540_REG_MUTE 0x61 -#define NAU8540_REG_ANALOG_ADC1 0x64 -#define NAU8540_REG_ANALOG_ADC2 0x65 -#define NAU8540_REG_ANALOG_PWR 0x66 -#define NAU8540_REG_MIC_BIAS 0x67 -#define NAU8540_REG_REFERENCE 0x68 -#define NAU8540_REG_FEPGA1 0x69 -#define NAU8540_REG_FEPGA2 0x6A -#define NAU8540_REG_FEPGA3 0x6B -#define NAU8540_REG_FEPGA4 0x6C -#define NAU8540_REG_PWR 0x6D -#define NAU8540_REG_MAX NAU8540_REG_PWR +#define NAU8540_REG_SW_RESET 0x00 +#define NAU8540_REG_POWER_MANAGEMENT 0x01 +#define NAU8540_REG_CLOCK_CTRL 0x02 +#define NAU8540_REG_CLOCK_SRC 0x03 +#define NAU8540_REG_FLL1 0x04 +#define NAU8540_REG_FLL2 0x05 +#define NAU8540_REG_FLL3 0x06 +#define NAU8540_REG_FLL4 0x07 +#define NAU8540_REG_FLL5 0x08 +#define NAU8540_REG_FLL6 0x09 +#define NAU8540_REG_FLL_VCO_RSV 0x0A +#define NAU8540_REG_PCM_CTRL0 0x10 +#define NAU8540_REG_PCM_CTRL1 0x11 +#define NAU8540_REG_PCM_CTRL2 0x12 +#define NAU8540_REG_PCM_CTRL3 0x13 +#define NAU8540_REG_PCM_CTRL4 0x14 +#define NAU8540_REG_ALC_CONTROL_1 0x20 +#define NAU8540_REG_ALC_CONTROL_2 0x21 +#define NAU8540_REG_ALC_CONTROL_3 0x22 +#define NAU8540_REG_ALC_CONTROL_4 0x23 +#define NAU8540_REG_ALC_CONTROL_5 0x24 +#define NAU8540_REG_ALC_GAIN_CH12 0x2D +#define NAU8540_REG_ALC_GAIN_CH34 0x2E +#define NAU8540_REG_ALC_STATUS 0x2F +#define NAU8540_REG_NOTCH_FIL1_CH1 0x30 +#define NAU8540_REG_NOTCH_FIL2_CH1 0x31 +#define NAU8540_REG_NOTCH_FIL1_CH2 0x32 +#define NAU8540_REG_NOTCH_FIL2_CH2 0x33 +#define NAU8540_REG_NOTCH_FIL1_CH3 0x34 +#define NAU8540_REG_NOTCH_FIL2_CH3 0x35 +#define NAU8540_REG_NOTCH_FIL1_CH4 0x36 +#define NAU8540_REG_NOTCH_FIL2_CH4 0x37 +#define NAU8540_REG_HPF_FILTER_CH12 0x38 +#define NAU8540_REG_HPF_FILTER_CH34 0x39 +#define NAU8540_REG_ADC_SAMPLE_RATE 0x3A +#define NAU8540_REG_DIGITAL_GAIN_CH1 0x40 +#define NAU8540_REG_DIGITAL_GAIN_CH2 0x41 +#define NAU8540_REG_DIGITAL_GAIN_CH3 0x42 +#define NAU8540_REG_DIGITAL_GAIN_CH4 0x43 +#define NAU8540_REG_DIGITAL_MUX 0x44 +#define NAU8540_REG_P2P_CH1 0x48 +#define NAU8540_REG_P2P_CH2 0x49 +#define NAU8540_REG_P2P_CH3 0x4A +#define NAU8540_REG_P2P_CH4 0x4B +#define NAU8540_REG_PEAK_CH1 0x4C +#define NAU8540_REG_PEAK_CH2 0x4D +#define NAU8540_REG_PEAK_CH3 0x4E +#define NAU8540_REG_PEAK_CH4 0x4F +#define NAU8540_REG_GPIO_CTRL 0x50 +#define NAU8540_REG_MISC_CTRL 0x51 +#define NAU8540_REG_I2C_CTRL 0x52 +#define NAU8540_REG_I2C_DEVICE_ID 0x58 +#define NAU8540_REG_RST 0x5A +#define NAU8540_REG_VMID_CTRL 0x60 +#define NAU8540_REG_MUTE 0x61 +#define NAU8540_REG_ANALOG_ADC1 0x64 +#define NAU8540_REG_ANALOG_ADC2 0x65 +#define NAU8540_REG_ANALOG_PWR 0x66 +#define NAU8540_REG_MIC_BIAS 0x67 +#define NAU8540_REG_REFERENCE 0x68 +#define NAU8540_REG_FEPGA1 0x69 +#define NAU8540_REG_FEPGA2 0x6A +#define NAU8540_REG_FEPGA3 0x6B +#define NAU8540_REG_FEPGA4 0x6C +#define NAU8540_REG_PWR 0x6D +#define NAU8540_REG_MAX NAU8540_REG_PWR /* POWER_MANAGEMENT (0x01) */ -#define NAU8540_ADC4_EN (0x1 << 3) -#define NAU8540_ADC3_EN (0x1 << 2) -#define NAU8540_ADC2_EN (0x1 << 1) -#define NAU8540_ADC1_EN 0x1 +#define NAU8540_ADC4_EN (0x1 << 3) +#define NAU8540_ADC3_EN (0x1 << 2) +#define NAU8540_ADC2_EN (0x1 << 1) +#define NAU8540_ADC1_EN 0x1 /* CLOCK_CTRL (0x02) */ -#define NAU8540_CLK_ADC_EN (0x1 << 15) -#define NAU8540_CLK_I2S_EN (0x1 << 1) +#define NAU8540_CLK_ADC_EN (0x1 << 15) +#define NAU8540_CLK_I2S_EN (0x1 << 1) /* CLOCK_SRC (0x03) */ -#define NAU8540_CLK_SRC_SFT 15 -#define NAU8540_CLK_SRC_MASK (1 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_SRC_VCO (1 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_SRC_MCLK (0 << NAU8540_CLK_SRC_SFT) -#define NAU8540_CLK_ADC_SRC_SFT 6 -#define NAU8540_CLK_ADC_SRC_MASK (0x3 << NAU8540_CLK_ADC_SRC_SFT) -#define NAU8540_CLK_MCLK_SRC_MASK 0xf +#define NAU8540_CLK_SRC_SFT 15 +#define NAU8540_CLK_SRC_MASK (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_VCO (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_MCLK (0 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_ADC_SRC_SFT 6 +#define NAU8540_CLK_ADC_SRC_MASK (0x3 << NAU8540_CLK_ADC_SRC_SFT) +#define NAU8540_CLK_MCLK_SRC_MASK 0xf /* FLL1 (0x04) */ -#define NAU8540_FLL_RATIO_MASK 0x7f +#define NAU8540_FLL_RATIO_MASK 0x7f /* FLL3 (0x06) */ -#define NAU8540_FLL_CLK_SRC_SFT 10 -#define NAU8540_FLL_CLK_SRC_MASK (0x3 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_MCLK (0 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_BLK (0x2 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_CLK_SRC_FS (0x3 << NAU8540_FLL_CLK_SRC_SFT) -#define NAU8540_FLL_INTEGER_MASK 0x3ff +#define NAU8540_FLL_CLK_SRC_SFT 10 +#define NAU8540_FLL_CLK_SRC_MASK (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_MCLK (0 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_BLK (0x2 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_FS (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_INTEGER_MASK 0x3ff /* FLL4 (0x07) */ -#define NAU8540_FLL_REF_DIV_SFT 10 -#define NAU8540_FLL_REF_DIV_MASK (0x3 << NAU8540_FLL_REF_DIV_SFT) +#define NAU8540_FLL_REF_DIV_SFT 10 +#define NAU8540_FLL_REF_DIV_MASK (0x3 << NAU8540_FLL_REF_DIV_SFT) /* FLL5 (0x08) */ -#define NAU8540_FLL_PDB_DAC_EN (0x1 << 15) -#define NAU8540_FLL_LOOP_FTR_EN (0x1 << 14) -#define NAU8540_FLL_CLK_SW_MASK (0x1 << 13) -#define NAU8540_FLL_CLK_SW_N2 (0x1 << 13) -#define NAU8540_FLL_CLK_SW_REF (0x0 << 13) -#define NAU8540_FLL_FTR_SW_MASK (0x1 << 12) -#define NAU8540_FLL_FTR_SW_ACCU (0x1 << 12) -#define NAU8540_FLL_FTR_SW_FILTER (0x0 << 12) +#define NAU8540_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8540_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8540_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8540_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8540_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8540_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8540_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8540_FLL_FTR_SW_FILTER (0x0 << 12) /* FLL6 (0x9) */ -#define NAU8540_DCO_EN (0x1 << 15) -#define NAU8540_SDM_EN (0x1 << 14) +#define NAU8540_DCO_EN (0x1 << 15) +#define NAU8540_SDM_EN (0x1 << 14) /* PCM_CTRL0 (0x10) */ -#define NAU8540_I2S_BP_SFT 7 -#define NAU8540_I2S_BP_INV (0x1 << NAU8540_I2S_BP_SFT) -#define NAU8540_I2S_PCMB_SFT 6 -#define NAU8540_I2S_PCMB_EN (0x1 << NAU8540_I2S_PCMB_SFT) -#define NAU8540_I2S_DL_SFT 2 -#define NAU8540_I2S_DL_MASK (0x3 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_16 (0 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_20 (0x1 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_24 (0x2 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DL_32 (0x3 << NAU8540_I2S_DL_SFT) -#define NAU8540_I2S_DF_MASK 0x3 -#define NAU8540_I2S_DF_RIGTH 0 -#define NAU8540_I2S_DF_LEFT 0x1 -#define NAU8540_I2S_DF_I2S 0x2 -#define NAU8540_I2S_DF_PCM_AB 0x3 +#define NAU8540_I2S_BP_SFT 7 +#define NAU8540_I2S_BP_INV (0x1 << NAU8540_I2S_BP_SFT) +#define NAU8540_I2S_PCMB_SFT 6 +#define NAU8540_I2S_PCMB_EN (0x1 << NAU8540_I2S_PCMB_SFT) +#define NAU8540_I2S_DL_SFT 2 +#define NAU8540_I2S_DL_MASK (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_16 (0 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_20 (0x1 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_24 (0x2 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_32 (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DF_MASK 0x3 +#define NAU8540_I2S_DF_RIGTH 0 +#define NAU8540_I2S_DF_LEFT 0x1 +#define NAU8540_I2S_DF_I2S 0x2 +#define NAU8540_I2S_DF_PCM_AB 0x3 /* PCM_CTRL1 (0x11) */ -#define NAU8540_I2S_LRC_DIV_SFT 12 -#define NAU8540_I2S_LRC_DIV_MASK (0x3 << NAU8540_I2S_LRC_DIV_SFT) -#define NAU8540_I2S_DO12_OE (0x1 << 4) -#define NAU8540_I2S_MS_SFT 3 -#define NAU8540_I2S_MS_MASK (0x1 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_MS_MASTER (0x1 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_MS_SLAVE (0x0 << NAU8540_I2S_MS_SFT) -#define NAU8540_I2S_BLK_DIV_MASK 0x7 +#define NAU8540_I2S_LRC_DIV_SFT 12 +#define NAU8540_I2S_LRC_DIV_MASK (0x3 << NAU8540_I2S_LRC_DIV_SFT) +#define NAU8540_I2S_DO12_OE (0x1 << 4) +#define NAU8540_I2S_MS_SFT 3 +#define NAU8540_I2S_MS_MASK (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_MASTER (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_SLAVE (0x0 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_BLK_DIV_MASK 0x7 /* PCM_CTRL1 (0x12) */ -#define NAU8540_I2S_DO34_OE (0x1 << 11) -#define NAU8540_I2S_TSLOT_L_MASK 0x3ff +#define NAU8540_I2S_DO34_OE (0x1 << 11) +#define NAU8540_I2S_TSLOT_L_MASK 0x3ff /* PCM_CTRL4 (0x14) */ -#define NAU8540_TDM_MODE (0x1 << 15) -#define NAU8540_TDM_OFFSET_EN (0x1 << 14) -#define NAU8540_TDM_TX_MASK 0xf +#define NAU8540_TDM_MODE (0x1 << 15) +#define NAU8540_TDM_OFFSET_EN (0x1 << 14) +#define NAU8540_TDM_TX_MASK 0xf /* ADC_SAMPLE_RATE (0x3A) */ -#define NAU8540_ADC_OSR_MASK 0x3 -#define NAU8540_ADC_OSR_256 0x3 -#define NAU8540_ADC_OSR_128 0x2 -#define NAU8540_ADC_OSR_64 0x1 -#define NAU8540_ADC_OSR_32 0x0 +#define NAU8540_ADC_OSR_MASK 0x3 +#define NAU8540_ADC_OSR_256 0x3 +#define NAU8540_ADC_OSR_128 0x2 +#define NAU8540_ADC_OSR_64 0x1 +#define NAU8540_ADC_OSR_32 0x0 /* VMID_CTRL (0x60) */ -#define NAU8540_VMID_EN (1 << 6) -#define NAU8540_VMID_SEL_SFT 4 -#define NAU8540_VMID_SEL_MASK (0x3 << NAU8540_VMID_SEL_SFT) +#define NAU8540_VMID_EN (1 << 6) +#define NAU8540_VMID_SEL_SFT 4 +#define NAU8540_VMID_SEL_MASK (0x3 << NAU8540_VMID_SEL_SFT) /* MIC_BIAS (0x67) */ -#define NAU8540_PU_PRE (0x1 << 8) +#define NAU8540_PU_PRE (0x1 << 8) /* REFERENCE (0x68) */ -#define NAU8540_PRECHARGE_DIS (0x1 << 13) -#define NAU8540_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8540_PRECHARGE_DIS (0x1 << 13) +#define NAU8540_GLOBAL_BIAS_EN (0x1 << 12) /* System Clock Source */ enum { - NAU8540_CLK_DIS, - NAU8540_CLK_MCLK, - NAU8540_CLK_INTERNAL, - NAU8540_CLK_FLL_MCLK, - NAU8540_CLK_FLL_BLK, - NAU8540_CLK_FLL_FS, + NAU8540_CLK_DIS, + NAU8540_CLK_MCLK, + NAU8540_CLK_INTERNAL, + NAU8540_CLK_FLL_MCLK, + NAU8540_CLK_FLL_BLK, + NAU8540_CLK_FLL_FS, }; struct nau8540 { - struct device *dev; - struct regmap *regmap; + struct device *dev; + struct regmap *regmap; }; struct nau8540_fll { - int mclk_src; - int ratio; - int fll_frac; - int fll_int; - int clk_ref_div; + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; }; struct nau8540_fll_attr { - unsigned int param; - unsigned int val; + unsigned int param; + unsigned int val; }; /* over sampling rate */ struct nau8540_osr_attr { - unsigned int osr; - unsigned int clk_src; + unsigned int osr; + unsigned int clk_src; }; -#endif /* __NAU8540_H__ */ +#endif /* __NAU8540_H__ */ diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c new file mode 100644 index 000000000000..cca974d26136 --- /dev/null +++ b/sound/soc/codecs/nau8824.c @@ -0,0 +1,1831 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu <KCHSU0@nuvoton.com> + * + * 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 <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/acpi.h> +#include <linux/math64.h> +#include <linux/semaphore.h> + +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "nau8824.h" + + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq); +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824); + +/* the ADC threshold of headset */ +#define DMIC_CLK 3072000 + +/* the ADC threshold of headset */ +#define HEADSET_SARADC_THD 0x80 + +/* the parameter threshold of FLL */ +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 124000000 +#define NAU_FVCO_MIN 90000000 + +/* scaling for mclk from sysclk_src output */ +static const struct nau8824_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, +}; + +/* ratio for input clk freq */ +static const struct nau8824_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8824_fll_attr fll_pre_scalar[] = { + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, +}; + +/* the maximum frequency of CLK_ADC and CLK_DAC */ +#define CLK_DA_AD_MAX 6144000 + +/* over sampling rate */ +static const struct nau8824_osr_attr osr_dac_sel[] = { + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 256, 0 }, /* OSR 256, SRC 1 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 0, 0 }, + { 32, 3 }, /* OSR 32, SRC 1/8 */ +}; + +static const struct nau8824_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +static const struct reg_default nau8824_reg_defaults[] = { + { NAU8824_REG_ENA_CTRL, 0x0000 }, + { NAU8824_REG_CLK_GATING_ENA, 0x0000 }, + { NAU8824_REG_CLK_DIVIDER, 0x0000 }, + { NAU8824_REG_FLL1, 0x0000 }, + { NAU8824_REG_FLL2, 0x3126 }, + { NAU8824_REG_FLL3, 0x0008 }, + { NAU8824_REG_FLL4, 0x0010 }, + { NAU8824_REG_FLL5, 0xC000 }, + { NAU8824_REG_FLL6, 0x6000 }, + { NAU8824_REG_FLL_VCO_RSV, 0xF13C }, + { NAU8824_REG_JACK_DET_CTRL, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING_1, 0x0000 }, + { NAU8824_REG_IRQ, 0x0000 }, + { NAU8824_REG_CLEAR_INT_REG, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING, 0x1000 }, + { NAU8824_REG_SAR_ADC, 0x0015 }, + { NAU8824_REG_VDET_COEFFICIENT, 0x0110 }, + { NAU8824_REG_VDET_THRESHOLD_1, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_2, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_3, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_4, 0x0000 }, + { NAU8824_REG_GPIO_SEL, 0x0000 }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 0x000B }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_2, 0x0010 }, + { NAU8824_REG_PORT0_LEFT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_PORT0_RIGHT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_TDM_CTRL, 0x0000 }, + { NAU8824_REG_ADC_HPF_FILTER, 0x0000 }, + { NAU8824_REG_ADC_FILTER_CTRL, 0x0002 }, + { NAU8824_REG_DAC_FILTER_CTRL_1, 0x0000 }, + { NAU8824_REG_DAC_FILTER_CTRL_2, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_1, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_2, 0x0000 }, + { NAU8824_REG_EQ1_LOW, 0x112C }, + { NAU8824_REG_EQ2_EQ3, 0x2C2C }, + { NAU8824_REG_EQ4_EQ5, 0x2C2C }, + { NAU8824_REG_ADC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH2_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH3_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_MUTE_CTRL, 0x0000 }, + { NAU8824_REG_DAC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_TO_DAC_ST, 0x0000 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH01, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH01, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH01, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH01, 0x3457 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH23, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH23, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH23, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH23, 0x3457 }, + { NAU8824_REG_DRC_GAINL_ADC0, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC1, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC2, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC3, 0x0200 }, + { NAU8824_REG_DRC_KNEE_IP12_DAC, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_DAC, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_DAC, 0x25F9 }, + { NAU8824_REG_DRC_ATKDCY_DAC, 0x3457 }, + { NAU8824_REG_DRC_GAIN_DAC_CH0, 0x0200 }, + { NAU8824_REG_DRC_GAIN_DAC_CH1, 0x0200 }, + { NAU8824_REG_MODE, 0x0000 }, + { NAU8824_REG_MODE1, 0x0000 }, + { NAU8824_REG_MODE2, 0x0000 }, + { NAU8824_REG_CLASSG, 0x0000 }, + { NAU8824_REG_OTP_EFUSE, 0x0000 }, + { NAU8824_REG_OTPDOUT_1, 0x0000 }, + { NAU8824_REG_OTPDOUT_2, 0x0000 }, + { NAU8824_REG_MISC_CTRL, 0x0000 }, + { NAU8824_REG_I2C_TIMEOUT, 0xEFFF }, + { NAU8824_REG_TEST_MODE, 0x0000 }, + { NAU8824_REG_I2C_DEVICE_ID, 0x1AF1 }, + { NAU8824_REG_SAR_ADC_DATA_OUT, 0x00FF }, + { NAU8824_REG_BIAS_ADJ, 0x0000 }, + { NAU8824_REG_PGA_GAIN, 0x0000 }, + { NAU8824_REG_TRIM_SETTINGS, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_1, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_2, 0x0000 }, + { NAU8824_REG_ENABLE_LO, 0x0000 }, + { NAU8824_REG_GAIN_LO, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_1, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_2, 0x0000 }, + { NAU8824_REG_ANALOG_ADC_1, 0x0011 }, + { NAU8824_REG_ANALOG_ADC_2, 0x0020 }, + { NAU8824_REG_RDAC, 0x0008 }, + { NAU8824_REG_MIC_BIAS, 0x0006 }, + { NAU8824_REG_HS_VOLUME_CONTROL, 0x0000 }, + { NAU8824_REG_BOOST, 0x0000 }, + { NAU8824_REG_FEPGA, 0x0000 }, + { NAU8824_REG_FEPGA_II, 0x0000 }, + { NAU8824_REG_FEPGA_SE, 0x0000 }, + { NAU8824_REG_FEPGA_ATTENUATION, 0x0000 }, + { NAU8824_REG_ATT_PORT0, 0x0000 }, + { NAU8824_REG_ATT_PORT1, 0x0000 }, + { NAU8824_REG_POWER_UP_CONTROL, 0x0000 }, + { NAU8824_REG_CHARGE_PUMP_CONTROL, 0x0300 }, + { NAU8824_REG_CHARGE_PUMP_INPUT, 0x0013 }, +}; + +static int nau8824_sema_acquire(struct nau8824 *nau8824, long timeout) +{ + int ret; + + if (timeout) { + ret = down_timeout(&nau8824->jd_sem, timeout); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphone timeout\n"); + } else { + ret = down_interruptible(&nau8824->jd_sem); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphone fail\n"); + } + + return ret; +} + +static inline void nau8824_sema_release(struct nau8824 *nau8824) +{ + up(&nau8824->jd_sem); +} + +static bool nau8824_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_ENA_CTRL ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_IRQ: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } + +} + +static bool nau8824_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH01: + case NAU8824_REG_DRC_SLOPE_ADC_CH01: + case NAU8824_REG_DRC_ATKDCY_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH23: + case NAU8824_REG_DRC_SLOPE_ADC_CH23: + case NAU8824_REG_DRC_ATKDCY_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_ATKDCY_DAC: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_CONTROL: + return true; + default: + return false; + } +} + +static bool nau8824_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET: + case NAU8824_REG_IRQ ... NAU8824_REG_CLEAR_INT_REG: + case NAU8824_REG_DRC_GAINL_ADC0 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_GAIN_DAC_CH0 ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } +} + +static const char * const nau8824_companding[] = { + "Off", "NC", "u-law", "A-law" }; + +static const struct soc_enum nau8824_companding_adc_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 12, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const struct soc_enum nau8824_companding_dac_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 14, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const char * const nau8824_adc_decimation[] = { + "32", "64", "128", "256" }; + +static const struct soc_enum nau8824_adc_decimation_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_FILTER_CTRL, 0, + ARRAY_SIZE(nau8824_adc_decimation), nau8824_adc_decimation); + +static const char * const nau8824_dac_oversampl[] = { + "64", "256", "128", "", "32" }; + +static const struct soc_enum nau8824_dac_oversampl_enum = + SOC_ENUM_SINGLE(NAU8824_REG_DAC_FILTER_CTRL_1, 0, + ARRAY_SIZE(nau8824_dac_oversampl), nau8824_dac_oversampl); + +static const char * const nau8824_input_channel[] = { + "Input CH0", "Input CH1", "Input CH2", "Input CH3" }; + +static const struct soc_enum nau8824_adc_ch0_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH0_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch1_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH1_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch2_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH2_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch3_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH3_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const char * const nau8824_tdm_slot[] = { + "Slot 0", "Slot 1", "Slot 2", "Slot 3" }; + +static const struct soc_enum nau8824_dac_left_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 6, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const struct soc_enum nau8824_dac_right_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 4, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const DECLARE_TLV_DB_MINMAX_MUTE(spk_vol_tlv, 0, 2400); +static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -3000, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 200, 0); +static const DECLARE_TLV_DB_SCALE(dmic_vol_tlv, -12800, 50, 0); + +static const struct snd_kcontrol_new nau8824_snd_controls[] = { + SOC_ENUM("ADC Companding", nau8824_companding_adc_enum), + SOC_ENUM("DAC Companding", nau8824_companding_dac_enum), + + SOC_ENUM("ADC Decimation Rate", nau8824_adc_decimation_enum), + SOC_ENUM("DAC Oversampling Rate", nau8824_dac_oversampl_enum), + + SOC_SINGLE_TLV("Speaker Right DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 8, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Right DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 8, 0x1f, 0, spk_vol_tlv), + + SOC_SINGLE_TLV("Headphone Right DACR Volume", + NAU8824_REG_ATT_PORT0, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACL Volume", + NAU8824_REG_ATT_PORT0, 0, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Right DACL Volume", + NAU8824_REG_ATT_PORT1, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACR Volume", + NAU8824_REG_ATT_PORT1, 0, 0x1f, 0, hp_vol_tlv), + + SOC_SINGLE_TLV("MIC1 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINL_SFT, 0x12, 0, mic_vol_tlv), + SOC_SINGLE_TLV("MIC2 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINR_SFT, 0x12, 0, mic_vol_tlv), + + SOC_SINGLE_TLV("DMIC1 Volume", NAU8824_REG_ADC_CH0_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC2 Volume", NAU8824_REG_ADC_CH1_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC3 Volume", NAU8824_REG_ADC_CH2_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC4 Volume", NAU8824_REG_ADC_CH3_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + + SOC_ENUM("ADC CH0 Select", nau8824_adc_ch0_enum), + SOC_ENUM("ADC CH1 Select", nau8824_adc_ch1_enum), + SOC_ENUM("ADC CH2 Select", nau8824_adc_ch2_enum), + SOC_ENUM("ADC CH3 Select", nau8824_adc_ch3_enum), + + SOC_SINGLE("ADC CH0 TX Switch", NAU8824_REG_TDM_CTRL, 0, 1, 0), + SOC_SINGLE("ADC CH1 TX Switch", NAU8824_REG_TDM_CTRL, 1, 1, 0), + SOC_SINGLE("ADC CH2 TX Switch", NAU8824_REG_TDM_CTRL, 2, 1, 0), + SOC_SINGLE("ADC CH3 TX Switch", NAU8824_REG_TDM_CTRL, 3, 1, 0), + + SOC_ENUM("DACL Channel Source", nau8824_dac_left_sel_enum), + SOC_ENUM("DACR Channel Source", nau8824_dac_right_sel_enum), + + SOC_SINGLE("DACL LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 0, 1, 0), + SOC_SINGLE("DACR LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 1, 1, 0), +}; + +static int nau8824_output_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Disables the TESTDAC to let DAC signal pass through. */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, NAU8824_CLASSD_CLAMP_DIS); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Prevent startup click by letting charge pump to ramp up */ + msleep(10); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, NAU8824_JAMNODCLOW); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int system_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + /* Set clock source to disable or internal clock before the + * playback or capture end. Codec needs clock for Jack + * detection and button press if jack inserted; otherwise, + * the clock should be closed. + */ + if (nau8824_is_jack_inserted(nau8824)) { + nau8824_config_sysclk(nau8824, + NAU8824_CLK_INTERNAL, 0); + } else { + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + } + } + return 0; +} + +static int dmic_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + int src; + + /* The DMIC clock is gotten from system clock (256fs) divided by + * DMIC_SRC (1, 2, 4, 8, 16, 32). The clock has to be equal or + * less than 3.072 MHz. + */ + for (src = 0; src < 5; src++) { + if ((0x1 << (8 - src)) * nau8824->fs <= DMIC_CLK) + break; + } + dev_dbg(nau8824->dev, "dmic src %d for mclk %d\n", src, nau8824->fs * 256); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DMIC_SRC_MASK, (src << NAU8824_CLK_DMIC_SRC_SFT)); + + return 0; +} + +static const struct snd_kcontrol_new nau8824_adc_ch0_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH0_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch1_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH1_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch2_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH2_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch3_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH3_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_left_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_MIC1_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_adc_right_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_MIC2_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_left_mixer[] = { + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPL_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPL_EN_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_right_mixer[] = { + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPR_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN_SFT, 1, 0), +}; + +static const char * const nau8824_dac_src[] = { "DACL", "DACR" }; + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacl_enum, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_SFT, nau8824_dac_src); + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacr_enum, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_SFT, nau8824_dac_src); + +static const struct snd_kcontrol_new nau8824_dacl_mux = + SOC_DAPM_ENUM("DACL Source", nau8824_dacl_enum); + +static const struct snd_kcontrol_new nau8824_dacr_mux = + SOC_DAPM_ENUM("DACR Source", nau8824_dacr_enum); + + +static const struct snd_soc_dapm_widget nau8824_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("System Clock", SND_SOC_NOPM, 0, 0, + system_clock_control, SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("HSMIC1"), + SND_SOC_DAPM_INPUT("HSMIC2"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + + SND_SOC_DAPM_SUPPLY("SAR", NAU8824_REG_SAR_ADC, + NAU8824_SAR_ADC_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_POWERUP_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC12 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC34 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC2_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Clock", SND_SOC_NOPM, 0, 0, + dmic_clock_control, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SWITCH("DMIC1 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch0_dmic), + SND_SOC_DAPM_SWITCH("DMIC2 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch1_dmic), + SND_SOC_DAPM_SWITCH("DMIC3 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch2_dmic), + SND_SOC_DAPM_SWITCH("DMIC4 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch3_dmic), + + SND_SOC_DAPM_MIXER("Left ADC", NAU8824_REG_POWER_UP_CONTROL, + 12, 0, nau8824_adc_left_mixer, + ARRAY_SIZE(nau8824_adc_left_mixer)), + SND_SOC_DAPM_MIXER("Right ADC", NAU8824_REG_POWER_UP_CONTROL, + 13, 0, nau8824_adc_right_mixer, + ARRAY_SIZE(nau8824_adc_right_mixer)), + + SND_SOC_DAPM_ADC("ADCL", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCL_EN_SFT, 0), + SND_SOC_DAPM_ADC("ADCR", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCR_EN_SFT, 0), + + SND_SOC_DAPM_AIF_OUT("AIFTX", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFRX", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DACL", NULL, NAU8824_REG_RDAC, + NAU8824_DACL_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACL Clock", NAU8824_REG_RDAC, + NAU8824_DACL_CLK_SFT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DACR", NULL, NAU8824_REG_RDAC, + NAU8824_DACR_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACR Clock", NAU8824_REG_RDAC, + NAU8824_DACR_CLK_SFT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacl_mux), + SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacr_mux), + + SND_SOC_DAPM_PGA_S("Output DACL", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 8, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Output DACR", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 9, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("ClassD", 0, NAU8824_REG_CLASSD_GAIN_1, + NAU8824_CLASSD_EN_SFT, 0, nau8824_spk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("Left Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_LDAC_EN_SFT, 0, nau8824_hp_left_mixer, + ARRAY_SIZE(nau8824_hp_left_mixer)), + SND_SOC_DAPM_MIXER("Right Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_RDAC_EN_SFT, 0, nau8824_hp_right_mixer, + ARRAY_SIZE(nau8824_hp_right_mixer)), + SND_SOC_DAPM_PGA_S("Charge Pump", 1, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_CHARGE_PUMP_EN_SFT, 0, nau8824_pump_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA("Output Driver L", + NAU8824_REG_POWER_UP_CONTROL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Output Driver R", + NAU8824_REG_POWER_UP_CONTROL, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver L", + NAU8824_REG_POWER_UP_CONTROL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver R", + NAU8824_REG_POWER_UP_CONTROL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Boost Driver", NAU8824_REG_BOOST, + NAU8824_HP_BOOST_DIS_SFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Class G", NAU8824_REG_CLASSG, + NAU8824_CLASSG_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("SPKOUTL"), + SND_SOC_DAPM_OUTPUT("SPKOUTR"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8824_dapm_routes[] = { + {"DMIC1 Enable", "Switch", "DMIC1"}, + {"DMIC2 Enable", "Switch", "DMIC2"}, + {"DMIC3 Enable", "Switch", "DMIC3"}, + {"DMIC4 Enable", "Switch", "DMIC4"}, + + {"DMIC1", NULL, "DMIC12 Power"}, + {"DMIC2", NULL, "DMIC12 Power"}, + {"DMIC3", NULL, "DMIC34 Power"}, + {"DMIC4", NULL, "DMIC34 Power"}, + {"DMIC12 Power", NULL, "DMIC Clock"}, + {"DMIC34 Power", NULL, "DMIC Clock"}, + + {"Left ADC", "MIC Switch", "MIC1"}, + {"Left ADC", "HSMIC Switch", "HSMIC1"}, + {"Right ADC", "MIC Switch", "MIC2"}, + {"Right ADC", "HSMIC Switch", "HSMIC2"}, + + {"ADCL", NULL, "Left ADC"}, + {"ADCR", NULL, "Right ADC"}, + + {"AIFTX", NULL, "MICBIAS"}, + {"AIFTX", NULL, "ADCL"}, + {"AIFTX", NULL, "ADCR"}, + {"AIFTX", NULL, "DMIC1 Enable"}, + {"AIFTX", NULL, "DMIC2 Enable"}, + {"AIFTX", NULL, "DMIC3 Enable"}, + {"AIFTX", NULL, "DMIC4 Enable"}, + + {"AIFTX", NULL, "System Clock"}, + {"AIFRX", NULL, "System Clock"}, + + {"DACL", NULL, "AIFRX"}, + {"DACL", NULL, "DACL Clock"}, + {"DACR", NULL, "AIFRX"}, + {"DACR", NULL, "DACR Clock"}, + + {"DACL Mux", "DACL", "DACL"}, + {"DACL Mux", "DACR", "DACR"}, + {"DACR Mux", "DACL", "DACL"}, + {"DACR Mux", "DACR", "DACR"}, + + {"Output DACL", NULL, "DACL Mux"}, + {"Output DACR", NULL, "DACR Mux"}, + + {"ClassD", NULL, "Output DACL"}, + {"ClassD", NULL, "Output DACR"}, + + {"Left Headphone", "DAC Left Switch", "Output DACL"}, + {"Left Headphone", "DAC Right Switch", "Output DACR"}, + {"Right Headphone", "DAC Left Switch", "Output DACL"}, + {"Right Headphone", "DAC Right Switch", "Output DACR"}, + + {"Charge Pump", NULL, "Left Headphone"}, + {"Charge Pump", NULL, "Right Headphone"}, + {"Output Driver L", NULL, "Charge Pump"}, + {"Output Driver R", NULL, "Charge Pump"}, + {"Main Driver L", NULL, "Output Driver L"}, + {"Main Driver R", NULL, "Output Driver R"}, + {"Class G", NULL, "Main Driver L"}, + {"Class G", NULL, "Main Driver R"}, + {"HP Boost Driver", NULL, "Class G"}, + + {"SPKOUTL", NULL, "ClassD"}, + {"SPKOUTR", NULL, "ClassD"}, + {"HPOL", NULL, "HP Boost Driver"}, + {"HPOR", NULL, "HP Boost Driver"}, +}; + +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824) +{ + struct snd_soc_jack *jack = nau8824->jack; + bool insert = FALSE; + + if (nau8824->irq && jack) + insert = jack->status & SND_JACK_HEADPHONE; + + return insert; +} + +static void nau8824_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8824_REG_IRQ, &active_irq); + for (i = 0; i < NAU8824_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8824_REG_CLEAR_INT_REG, clear_irq); + } +} + +static void nau8824_eject_jack(struct nau8824 *nau8824) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + + /* Clear all interruption status */ + nau8824_int_status_clear_all(regmap); + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_sync(dapm); + + /* Enable the insertion interruption, disable the ejection + * interruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_INSERT_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + + /* Close clock for jack type detection at manual mode */ + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); +} + +static void nau8824_jdet_work(struct work_struct *work) +{ + struct nau8824 *nau8824 = container_of( + work, struct nau8824, jdet_work); + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + int adc_value, event = 0, event_mask = 0; + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + + msleep(100); + + regmap_read(regmap, NAU8824_REG_SAR_ADC_DATA_OUT, &adc_value); + adc_value = adc_value & NAU8824_SAR_ADC_DATA_MASK; + dev_dbg(nau8824->dev, "SAR ADC data 0x%02x\n", adc_value); + if (adc_value < HEADSET_SARADC_THD) { + event |= SND_JACK_HEADPHONE; + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_sync(dapm); + } else { + event |= SND_JACK_HEADSET; + } + event_mask |= SND_JACK_HEADSET; + snd_soc_jack_report(nau8824->jack, event, event_mask); + + nau8824_sema_release(nau8824); +} + +static void nau8824_setup_auto_irq(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable jack ejection, short key press and release interruption. */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_EJECT_EN); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_KEY_RELEASE_DIS | + NAU8824_IRQ_KEY_SHORT_PRESS_DIS, 0); + /* Enable internal VCO needed for interruptions */ + nau8824_config_sysclk(nau8824, NAU8824_CLK_INTERNAL, 0); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, 0); +} + +static int nau8824_button_decode(int value) +{ + int buttons = 0; + + /* The chip supports up to 8 buttons, but ALSA defines + * only 6 buttons. + */ + if (value & BIT(0)) + buttons |= SND_JACK_BTN_0; + if (value & BIT(1)) + buttons |= SND_JACK_BTN_1; + if (value & BIT(2)) + buttons |= SND_JACK_BTN_2; + if (value & BIT(3)) + buttons |= SND_JACK_BTN_3; + if (value & BIT(4)) + buttons |= SND_JACK_BTN_4; + if (value & BIT(5)) + buttons |= SND_JACK_BTN_5; + + return buttons; +} + +#define NAU8824_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +static irqreturn_t nau8824_interrupt(int irq, void *data) +{ + struct nau8824 *nau8824 = (struct nau8824 *)data; + struct regmap *regmap = nau8824->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + if (regmap_read(regmap, NAU8824_REG_IRQ, &active_irq)) { + dev_err(nau8824->dev, "failed to read irq status\n"); + return IRQ_NONE; + } + dev_dbg(nau8824->dev, "IRQ %x\n", active_irq); + + if (active_irq & NAU8824_JACK_EJECTION_DETECTED) { + nau8824_eject_jack(nau8824); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8824_JACK_EJECTION_DETECTED; + /* release semaphore held after resume, + * and cancel jack detection + */ + nau8824_sema_release(nau8824); + cancel_work_sync(&nau8824->jdet_work); + } else if (active_irq & NAU8824_KEY_SHORT_PRESS_IRQ) { + int key_status, button_pressed; + + regmap_read(regmap, NAU8824_REG_CLEAR_INT_REG, + &key_status); + + /* lower 8 bits of the register are for pressed keys */ + button_pressed = nau8824_button_decode(key_status); + + event |= button_pressed; + dev_dbg(nau8824->dev, "button %x pressed\n", event); + event_mask |= NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_SHORT_PRESS_IRQ; + } else if (active_irq & NAU8824_KEY_RELEASE_IRQ) { + event_mask = NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_RELEASE_IRQ; + } else if (active_irq & NAU8824_JACK_INSERTION_DETECTED) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_INSERT_DIS); + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN, 0); + /* detect microphone and jack type */ + cancel_work_sync(&nau8824->jdet_work); + schedule_work(&nau8824->jdet_work); + + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8824_setup_auto_irq(nau8824); + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8824_REG_CLEAR_INT_REG, clear_irq); + + if (event_mask) + snd_soc_jack_report(nau8824->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static int nau8824_clock_check(struct nau8824 *nau8824, + int stream, int rate, int osr) +{ + int osrate; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (osr >= ARRAY_SIZE(osr_dac_sel)) + return -EINVAL; + osrate = osr_dac_sel[osr].osr; + } else { + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; + } + + if (!osrate || rate * osr > CLK_DA_AD_MAX) { + dev_err(nau8824->dev, "exceed the maximum frequency of CLK_ADC or CLK_DAC\n"); + return -EINVAL; + } + + return 0; +} + +static int nau8824_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 nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + unsigned int val_len = 0, osr, ctrl_val, bclk_fs, bclk_div; + + nau8824_sema_acquire(nau8824, HZ); + + /* CLK_DAC or CLK_ADC = OSR * FS + * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + nau8824->fs = params_rate(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8824->regmap, + NAU8824_REG_DAC_FILTER_CTRL_1, &osr); + osr &= NAU8824_DAC_OVERSAMPLE_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DAC_SRC_MASK, + osr_dac_sel[osr].clk_src << NAU8824_CLK_DAC_SRC_SFT); + } else { + regmap_read(nau8824->regmap, + NAU8824_REG_ADC_FILTER_CTRL, &osr); + osr &= NAU8824_ADC_SYNC_DOWN_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8824_CLK_ADC_SRC_SFT); + } + + /* make BCLK and LRC divde configuration if the codec as master. */ + regmap_read(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, &ctrl_val); + if (ctrl_val & NAU8824_I2S_MS_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / nau8824->fs; + if (bclk_fs <= 32) + bclk_div = 0x3; + else if (bclk_fs <= 64) + bclk_div = 0x2; + else if (bclk_fs <= 128) + bclk_div = 0x1; + else if (bclk_fs <= 256) + bclk_div = 0; + else + return -EINVAL; + regmap_update_bits(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_LRC_DIV_MASK | NAU8824_I2S_BLK_DIV_MASK, + (bclk_div << NAU8824_I2S_LRC_DIV_SFT) | bclk_div); + } + + switch (params_width(params)) { + case 16: + val_len |= NAU8824_I2S_DL_16; + break; + case 20: + val_len |= NAU8824_I2S_DL_20; + break; + case 24: + val_len |= NAU8824_I2S_DL_24; + break; + case 32: + val_len |= NAU8824_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DL_MASK, val_len); + + nau8824_sema_release(nau8824); + + return 0; +} + +static int nau8824_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + nau8824_sema_acquire(nau8824, HZ); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8824_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8824_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8824_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8824_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8824_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + ctrl1_val |= NAU8824_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DF_MASK | NAU8824_I2S_BP_MASK | + NAU8824_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_MS_MASK, ctrl2_val); + + nau8824_sema_release(nau8824); + + return 0; +} + +/** + * nau8824_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8824_calc_fll_param(unsigned int fll_in, + unsigned int fs, struct nau8824_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8824_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256 * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; +} + +static void nau8824_fll_apply(struct regmap *regmap, + struct nau8824_fll *fll_param) +{ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK | NAU8824_CLK_MCLK_SRC_MASK, + NAU8824_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8824_REG_FLL1, + NAU8824_FLL_RATIO_MASK, fll_param->ratio); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8824_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8824_REG_FLL4, + NAU8824_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8824_FLL_REF_DIV_SFT); + /* select divided VCO input */ + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_CLK_SW_MASK, NAU8824_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_SDM_EN, NAU8824_SDM_EN); + } else { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, NAU8824_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_SDM_EN, 0); + } +} + +/* freq_out must be 256*Fs in order to achieve the best performance */ +static int nau8824_set_pll(struct snd_soc_codec *codec, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + struct nau8824_fll fll_param; + int ret, fs; + + fs = freq_out / 256; + ret = nau8824_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8824->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8824->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8824_fll_apply(nau8824->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + + return 0; +} + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq) +{ + struct regmap *regmap = nau8824->regmap; + + switch (clk_id) { + case NAU8824_CLK_DIS: + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + break; + + case NAU8824_CLK_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_INTERNAL: + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, NAU8824_DCO_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + break; + + case NAU8824_CLK_FLL_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_MCLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_BLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_BLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_FS: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_FS); + nau8824_sema_release(nau8824); + break; + + default: + dev_err(nau8824->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8824->dev, "Sysclk is %dHz and clock id is %d\n", freq, + clk_id); + + return 0; +} + +static int nau8824_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + return nau8824_config_sysclk(nau8824, clk_id, freq); +} + +static void nau8824_resume_setup(struct nau8824 *nau8824) +{ + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + if (nau8824->irq) { + /* Clear all interruption status */ + nau8824_int_status_clear_all(nau8824->regmap); + /* Enable jack detection at sleep mode, insertion detection, + * and ejection detection. + */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, 0); + } +} + +static int nau8824_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + /* Setup codec configuration after resume */ + nau8824_resume_setup(nau8824); + } + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + break; + } + + return 0; +} + +static int nau8824_codec_probe(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + nau8824->dapm = dapm; + + return 0; +} + +static int __maybe_unused nau8824_suspend(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + if (nau8824->irq) { + disable_irq(nau8824->irq); + snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF); + } + regcache_cache_only(nau8824->regmap, true); + regcache_mark_dirty(nau8824->regmap); + + return 0; +} + +static int __maybe_unused nau8824_resume(struct snd_soc_codec *codec) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + + regcache_cache_only(nau8824->regmap, false); + regcache_sync(nau8824->regmap); + if (nau8824->irq) { + /* Hold semaphore to postpone playback happening + * until jack detection done. + */ + nau8824_sema_acquire(nau8824, 0); + enable_irq(nau8824->irq); + } + + return 0; +} + +static struct snd_soc_codec_driver nau8824_codec_driver = { + .probe = nau8824_codec_probe, + .set_sysclk = nau8824_set_sysclk, + .set_pll = nau8824_set_pll, + .set_bias_level = nau8824_set_bias_level, + .suspend = nau8824_suspend, + .resume = nau8824_resume, + .suspend_bias_off = true, + + .component_driver = { + .controls = nau8824_snd_controls, + .num_controls = ARRAY_SIZE(nau8824_snd_controls), + .dapm_widgets = nau8824_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8824_dapm_widgets), + .dapm_routes = nau8824_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8824_dapm_routes), + }, +}; + +static const struct snd_soc_dai_ops nau8824_dai_ops = { + .hw_params = nau8824_hw_params, + .set_fmt = nau8824_set_fmt, +}; + +#define NAU8824_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8824_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8824_dai = { + .name = NAU8824_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .ops = &nau8824_dai_ops, +}; + +static const struct regmap_config nau8824_regmap_config = { + .val_bits = NAU8824_REG_ADDR_LEN, + .reg_bits = NAU8824_REG_DATA_LEN, + + .max_register = NAU8824_REG_MAX, + .readable_reg = nau8824_readable_reg, + .writeable_reg = nau8824_writeable_reg, + .volatile_reg = nau8824_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8824_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8824_reg_defaults), +}; + +/** + * nau8824_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8824_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack) +{ + struct nau8824 *nau8824 = snd_soc_codec_get_drvdata(codec); + int ret; + + nau8824->jack = jack; + /* Initiate jack detection work queue */ + INIT_WORK(&nau8824->jdet_work, nau8824_jdet_work); + ret = devm_request_threaded_irq(nau8824->dev, nau8824->irq, NULL, + nau8824_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8824", nau8824); + if (ret) { + dev_err(nau8824->dev, "Cannot request irq %d (%d)\n", + nau8824->irq, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(nau8824_enable_jack_detect); + +static void nau8824_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8824_REG_RESET, 0x00); + regmap_write(regmap, NAU8824_REG_RESET, 0x00); +} + +static void nau8824_setup_buttons(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_TRACKING_GAIN_MASK, + nau8824->sar_voltage << NAU8824_SAR_TRACKING_GAIN_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_COMPARE_TIME_MASK, + nau8824->sar_compare_time << NAU8824_SAR_COMPARE_TIME_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_SAMPLING_TIME_MASK, + nau8824->sar_sampling_time << NAU8824_SAR_SAMPLING_TIME_SFT); + + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_LEVELS_NR_MASK, + (nau8824->sar_threshold_num - 1) << NAU8824_LEVELS_NR_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_HYSTERESIS_MASK, + nau8824->sar_hysteresis << NAU8824_HYSTERESIS_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_SHORTKEY_DEBOUNCE_MASK, + nau8824->key_debounce << NAU8824_SHORTKEY_DEBOUNCE_SFT); + + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_1, + (nau8824->sar_threshold[0] << 8) | nau8824->sar_threshold[1]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_2, + (nau8824->sar_threshold[2] << 8) | nau8824->sar_threshold[3]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_3, + (nau8824->sar_threshold[4] << 8) | nau8824->sar_threshold[5]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_4, + (nau8824->sar_threshold[6] << 8) | nau8824->sar_threshold[7]); +} + +static void nau8824_init_regs(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8824_REG_BIAS_ADJ, + NAU8824_VMID | NAU8824_VMID_SEL_MASK, NAU8824_VMID | + (nau8824->vref_impedance << NAU8824_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_GLOBAL_BIAS_EN, NAU8824_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_VOLTAGE_MASK, nau8824->micbias_voltage); + /* Disable Boost Driver, Automatic Short circuit protection enable */ + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN); + /* Scaling for ADC and DAC clock */ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK | NAU8824_CLK_DAC_SRC_MASK, + (0x1 << NAU8824_CLK_ADC_SRC_SFT) | + (0x1 << NAU8824_CLK_DAC_SRC_SFT)); + regmap_update_bits(regmap, NAU8824_REG_DAC_MUTE_CTRL, + NAU8824_DAC_ZC_EN, NAU8824_DAC_ZC_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_GATING_ENA, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN); + /* Class G timer 64ms */ + regmap_update_bits(regmap, NAU8824_REG_CLASSG, + NAU8824_CLASSG_TIMER_MASK, + 0x20 << NAU8824_CLASSG_TIMER_SFT); + regmap_update_bits(regmap, NAU8824_REG_TRIM_SETTINGS, + NAU8824_DRV_CURR_INC, NAU8824_DRV_CURR_INC); + /* Disable DACR/L power */ + regmap_update_bits(regmap, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL); + /* Enable TESTDAC. This sets the analog DAC inputs to a '0' input + * signal to avoid any glitches due to power up transients in both + * the analog and digital DAC circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + /* Config L/R channel */ + regmap_update_bits(regmap, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_MASK, NAU8824_DAC_CH0_SEL_I2S0); + regmap_update_bits(regmap, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_MASK, NAU8824_DAC_CH1_SEL_I2S1); + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN); + /* Default oversampling/decimations settings are unusable + * (audible hiss). Set it to something better. + */ + regmap_update_bits(regmap, NAU8824_REG_ADC_FILTER_CTRL, + NAU8824_ADC_SYNC_DOWN_MASK, NAU8824_ADC_SYNC_DOWN_64); + regmap_update_bits(regmap, NAU8824_REG_DAC_FILTER_CTRL_1, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_MASK, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_64); + /* DAC clock delay 2ns, VREF */ + regmap_update_bits(regmap, NAU8824_REG_RDAC, + NAU8824_RDAC_CLK_DELAY_MASK | NAU8824_RDAC_VREF_MASK, + (0x2 << NAU8824_RDAC_CLK_DELAY_SFT) | + (0x3 << NAU8824_RDAC_VREF_SFT)); + /* PGA input mode selection */ + regmap_update_bits(regmap, NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN); + /* Digital microphone control */ + regmap_update_bits(regmap, NAU8824_REG_ANALOG_CONTROL_1, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST); + regmap_update_bits(regmap, NAU8824_REG_JACK_DET_CTRL, + NAU8824_JACK_LOGIC, + /* jkdet_polarity - 1 is for active-low */ + nau8824->jkdet_polarity ? 0 : NAU8824_JACK_LOGIC); + regmap_update_bits(regmap, + NAU8824_REG_JACK_DET_CTRL, NAU8824_JACK_EJECT_DT_MASK, + (nau8824->jack_eject_debounce << NAU8824_JACK_EJECT_DT_SFT)); + if (nau8824->sar_threshold_num) + nau8824_setup_buttons(nau8824); +} + +static int nau8824_setup_irq(struct nau8824 *nau8824) +{ + /* Disable interruption before codec initiation done */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + + return 0; +} + +static void nau8824_print_device_properties(struct nau8824 *nau8824) +{ + struct device *dev = nau8824->dev; + int i; + + dev_dbg(dev, "jkdet-polarity: %d\n", nau8824->jkdet_polarity); + dev_dbg(dev, "micbias-voltage: %d\n", nau8824->micbias_voltage); + dev_dbg(dev, "vref-impedance: %d\n", nau8824->vref_impedance); + + dev_dbg(dev, "sar-threshold-num: %d\n", nau8824->sar_threshold_num); + for (i = 0; i < nau8824->sar_threshold_num; i++) + dev_dbg(dev, "sar-threshold[%d]=%x\n", i, + nau8824->sar_threshold[i]); + + dev_dbg(dev, "sar-hysteresis: %d\n", nau8824->sar_hysteresis); + dev_dbg(dev, "sar-voltage: %d\n", nau8824->sar_voltage); + dev_dbg(dev, "sar-compare-time: %d\n", nau8824->sar_compare_time); + dev_dbg(dev, "sar-sampling-time: %d\n", nau8824->sar_sampling_time); + dev_dbg(dev, "short-key-debounce: %d\n", nau8824->key_debounce); + dev_dbg(dev, "jack-eject-debounce: %d\n", + nau8824->jack_eject_debounce); +} + +static int nau8824_read_device_properties(struct device *dev, + struct nau8824 *nau8824) { + int ret; + + ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity", + &nau8824->jkdet_polarity); + if (ret) + nau8824->jkdet_polarity = 1; + ret = device_property_read_u32(dev, "nuvoton,micbias-voltage", + &nau8824->micbias_voltage); + if (ret) + nau8824->micbias_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,vref-impedance", + &nau8824->vref_impedance); + if (ret) + nau8824->vref_impedance = 2; + ret = device_property_read_u32(dev, "nuvoton,sar-threshold-num", + &nau8824->sar_threshold_num); + if (ret) + nau8824->sar_threshold_num = 4; + ret = device_property_read_u32_array(dev, "nuvoton,sar-threshold", + nau8824->sar_threshold, nau8824->sar_threshold_num); + if (ret) { + nau8824->sar_threshold[0] = 0x0a; + nau8824->sar_threshold[1] = 0x14; + nau8824->sar_threshold[2] = 0x26; + nau8824->sar_threshold[3] = 0x73; + } + ret = device_property_read_u32(dev, "nuvoton,sar-hysteresis", + &nau8824->sar_hysteresis); + if (ret) + nau8824->sar_hysteresis = 0; + ret = device_property_read_u32(dev, "nuvoton,sar-voltage", + &nau8824->sar_voltage); + if (ret) + nau8824->sar_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,sar-compare-time", + &nau8824->sar_compare_time); + if (ret) + nau8824->sar_compare_time = 1; + ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time", + &nau8824->sar_sampling_time); + if (ret) + nau8824->sar_sampling_time = 1; + ret = device_property_read_u32(dev, "nuvoton,short-key-debounce", + &nau8824->key_debounce); + if (ret) + nau8824->key_debounce = 0; + ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce", + &nau8824->jack_eject_debounce); + if (ret) + nau8824->jack_eject_debounce = 1; + + return 0; +} + +static int nau8824_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8824 *nau8824 = dev_get_platdata(dev); + int ret, value; + + if (!nau8824) { + nau8824 = devm_kzalloc(dev, sizeof(*nau8824), GFP_KERNEL); + if (!nau8824) + return -ENOMEM; + ret = nau8824_read_device_properties(dev, nau8824); + if (ret) + return ret; + } + i2c_set_clientdata(i2c, nau8824); + + nau8824->regmap = devm_regmap_init_i2c(i2c, &nau8824_regmap_config); + if (IS_ERR(nau8824->regmap)) + return PTR_ERR(nau8824->regmap); + nau8824->dev = dev; + nau8824->irq = i2c->irq; + sema_init(&nau8824->jd_sem, 1); + + nau8824_print_device_properties(nau8824); + + ret = regmap_read(nau8824->regmap, NAU8824_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU8824: %d\n", + ret); + return ret; + } + nau8824_reset_chip(nau8824->regmap); + nau8824_init_regs(nau8824); + + if (i2c->irq) + nau8824_setup_irq(nau8824); + + return snd_soc_register_codec(dev, + &nau8824_codec_driver, &nau8824_dai, 1); +} + + +static int nau8824_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id nau8824_i2c_ids[] = { + { "nau8824", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8824_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8824_of_ids[] = { + { .compatible = "nuvoton,nau8824", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8824_of_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id nau8824_acpi_match[] = { + { "10508824", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, nau8824_acpi_match); +#endif + +static struct i2c_driver nau8824_i2c_driver = { + .driver = { + .name = "nau8824", + .of_match_table = of_match_ptr(nau8824_of_ids), + .acpi_match_table = ACPI_PTR(nau8824_acpi_match), + }, + .probe = nau8824_i2c_probe, + .remove = nau8824_i2c_remove, + .id_table = nau8824_i2c_ids, +}; +module_i2c_driver(nau8824_i2c_driver); + + +MODULE_DESCRIPTION("ASoC NAU88L24 driver"); +MODULE_AUTHOR("John Hsu <KCHSU0@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/nau8824.h b/sound/soc/codecs/nau8824.h new file mode 100644 index 000000000000..87ac9a382aed --- /dev/null +++ b/sound/soc/codecs/nau8824.h @@ -0,0 +1,466 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu <KCHSU0@nuvoton.com> + * + * 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 __NAU8824_H__ +#define __NAU8824_H__ + +#define NAU8824_REG_RESET 0x00 +#define NAU8824_REG_ENA_CTRL 0x01 +#define NAU8824_REG_CLK_GATING_ENA 0x02 +#define NAU8824_REG_CLK_DIVIDER 0x03 +#define NAU8824_REG_FLL1 0x04 +#define NAU8824_REG_FLL2 0x05 +#define NAU8824_REG_FLL3 0x06 +#define NAU8824_REG_FLL4 0x07 +#define NAU8824_REG_FLL5 0x08 +#define NAU8824_REG_FLL6 0x09 +#define NAU8824_REG_FLL_VCO_RSV 0x0A +#define NAU8824_REG_JACK_DET_CTRL 0x0D +#define NAU8824_REG_INTERRUPT_SETTING_1 0x0F +#define NAU8824_REG_IRQ 0x10 +#define NAU8824_REG_CLEAR_INT_REG 0x11 +#define NAU8824_REG_INTERRUPT_SETTING 0x12 +#define NAU8824_REG_SAR_ADC 0x13 +#define NAU8824_REG_VDET_COEFFICIENT 0x14 +#define NAU8824_REG_VDET_THRESHOLD_1 0x15 +#define NAU8824_REG_VDET_THRESHOLD_2 0x16 +#define NAU8824_REG_VDET_THRESHOLD_3 0x17 +#define NAU8824_REG_VDET_THRESHOLD_4 0x18 +#define NAU8824_REG_GPIO_SEL 0x1A +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_1 0x1C +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_2 0x1D +#define NAU8824_REG_PORT0_LEFT_TIME_SLOT 0x1E +#define NAU8824_REG_PORT0_RIGHT_TIME_SLOT 0x1F +#define NAU8824_REG_TDM_CTRL 0x20 +#define NAU8824_REG_ADC_HPF_FILTER 0x23 +#define NAU8824_REG_ADC_FILTER_CTRL 0x24 +#define NAU8824_REG_DAC_FILTER_CTRL_1 0x25 +#define NAU8824_REG_DAC_FILTER_CTRL_2 0x26 +#define NAU8824_REG_NOTCH_FILTER_1 0x27 +#define NAU8824_REG_NOTCH_FILTER_2 0x28 +#define NAU8824_REG_EQ1_LOW 0x29 +#define NAU8824_REG_EQ2_EQ3 0x2A +#define NAU8824_REG_EQ4_EQ5 0x2B +#define NAU8824_REG_ADC_CH0_DGAIN_CTRL 0x2D +#define NAU8824_REG_ADC_CH1_DGAIN_CTRL 0x2E +#define NAU8824_REG_ADC_CH2_DGAIN_CTRL 0x2F +#define NAU8824_REG_ADC_CH3_DGAIN_CTRL 0x30 +#define NAU8824_REG_DAC_MUTE_CTRL 0x31 +#define NAU8824_REG_DAC_CH0_DGAIN_CTRL 0x32 +#define NAU8824_REG_DAC_CH1_DGAIN_CTRL 0x33 +#define NAU8824_REG_ADC_TO_DAC_ST 0x34 +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 0x38 +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH01 0x39 +#define NAU8824_REG_DRC_SLOPE_ADC_CH01 0x3A +#define NAU8824_REG_DRC_ATKDCY_ADC_CH01 0x3B +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH23 0x3C +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH23 0x3D +#define NAU8824_REG_DRC_SLOPE_ADC_CH23 0x3E +#define NAU8824_REG_DRC_ATKDCY_ADC_CH23 0x3F +#define NAU8824_REG_DRC_GAINL_ADC0 0x40 +#define NAU8824_REG_DRC_GAINL_ADC1 0x41 +#define NAU8824_REG_DRC_GAINL_ADC2 0x42 +#define NAU8824_REG_DRC_GAINL_ADC3 0x43 +#define NAU8824_REG_DRC_KNEE_IP12_DAC 0x45 +#define NAU8824_REG_DRC_KNEE_IP34_DAC 0x46 +#define NAU8824_REG_DRC_SLOPE_DAC 0x47 +#define NAU8824_REG_DRC_ATKDCY_DAC 0x48 +#define NAU8824_REG_DRC_GAIN_DAC_CH0 0x49 +#define NAU8824_REG_DRC_GAIN_DAC_CH1 0x4A +#define NAU8824_REG_MODE 0x4C +#define NAU8824_REG_MODE1 0x4D +#define NAU8824_REG_MODE2 0x4E +#define NAU8824_REG_CLASSG 0x50 +#define NAU8824_REG_OTP_EFUSE 0x51 +#define NAU8824_REG_OTPDOUT_1 0x53 +#define NAU8824_REG_OTPDOUT_2 0x54 +#define NAU8824_REG_MISC_CTRL 0x55 +#define NAU8824_REG_I2C_TIMEOUT 0x56 +#define NAU8824_REG_TEST_MODE 0x57 +#define NAU8824_REG_I2C_DEVICE_ID 0x58 +#define NAU8824_REG_SAR_ADC_DATA_OUT 0x59 +#define NAU8824_REG_BIAS_ADJ 0x66 +#define NAU8824_REG_PGA_GAIN 0x67 +#define NAU8824_REG_TRIM_SETTINGS 0x68 +#define NAU8824_REG_ANALOG_CONTROL_1 0x69 +#define NAU8824_REG_ANALOG_CONTROL_2 0x6A +#define NAU8824_REG_ENABLE_LO 0x6B +#define NAU8824_REG_GAIN_LO 0x6C +#define NAU8824_REG_CLASSD_GAIN_1 0x6D +#define NAU8824_REG_CLASSD_GAIN_2 0x6E +#define NAU8824_REG_ANALOG_ADC_1 0x71 +#define NAU8824_REG_ANALOG_ADC_2 0x72 +#define NAU8824_REG_RDAC 0x73 +#define NAU8824_REG_MIC_BIAS 0x74 +#define NAU8824_REG_HS_VOLUME_CONTROL 0x75 +#define NAU8824_REG_BOOST 0x76 +#define NAU8824_REG_FEPGA 0x77 +#define NAU8824_REG_FEPGA_II 0x78 +#define NAU8824_REG_FEPGA_SE 0x79 +#define NAU8824_REG_FEPGA_ATTENUATION 0x7A +#define NAU8824_REG_ATT_PORT0 0x7B +#define NAU8824_REG_ATT_PORT1 0x7C +#define NAU8824_REG_POWER_UP_CONTROL 0x7F +#define NAU8824_REG_CHARGE_PUMP_CONTROL 0x80 +#define NAU8824_REG_CHARGE_PUMP_INPUT 0x81 +#define NAU8824_REG_MAX NAU8824_REG_CHARGE_PUMP_INPUT +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8824_REG_ADDR_LEN 16 +#define NAU8824_REG_DATA_LEN 16 + + +/* ENA_CTRL (0x1) */ +#define NAU8824_DMIC_LCH_EDGE_CH23 (0x1 << 12) +#define NAU8824_DMIC_LCH_EDGE_CH01 (0x1 << 11) +#define NAU8824_JD_SLEEP_MODE (0x1 << 10) +#define NAU8824_ADC_CH3_DMIC_SFT 9 +#define NAU8824_ADC_CH3_DMIC_EN (0x1 << NAU8824_ADC_CH3_DMIC_SFT) +#define NAU8824_ADC_CH2_DMIC_SFT 8 +#define NAU8824_ADC_CH2_DMIC_EN (0x1 << NAU8824_ADC_CH2_DMIC_SFT) +#define NAU8824_ADC_CH1_DMIC_SFT 7 +#define NAU8824_ADC_CH1_DMIC_EN (0x1 << NAU8824_ADC_CH1_DMIC_SFT) +#define NAU8824_ADC_CH0_DMIC_SFT 6 +#define NAU8824_ADC_CH0_DMIC_EN (0x1 << NAU8824_ADC_CH0_DMIC_SFT) +#define NAU8824_DAC_CH1_EN (0x1 << 5) +#define NAU8824_DAC_CH0_EN (0x1 << 4) +#define NAU8824_ADC_CH3_EN (0x1 << 3) +#define NAU8824_ADC_CH2_EN (0x1 << 2) +#define NAU8824_ADC_CH1_EN (0x1 << 1) +#define NAU8824_ADC_CH0_EN 0x1 + +/* CLK_GATING_ENA (0x02) */ +#define NAU8824_CLK_ADC_CH23_EN (0x1 << 15) +#define NAU8824_CLK_ADC_CH01_EN (0x1 << 14) +#define NAU8824_CLK_DAC_CH1_EN (0x1 << 13) +#define NAU8824_CLK_DAC_CH0_EN (0x1 << 12) +#define NAU8824_CLK_I2S_EN (0x1 << 7) +#define NAU8824_CLK_GAIN_EN (0x1 << 5) +#define NAU8824_CLK_SAR_EN (0x1 << 3) +#define NAU8824_CLK_DMIC_CH23_EN (0x1 << 1) + +/* CLK_DIVIDER (0x3) */ +#define NAU8824_CLK_SRC_SFT 15 +#define NAU8824_CLK_SRC_MASK (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_VCO (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_MCLK (0 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_MCLK_SRC_MASK (0xf << 0) +#define NAU8824_CLK_DMIC_SRC_SFT 10 +#define NAU8824_CLK_DMIC_SRC_MASK (0x7 << NAU8824_CLK_DMIC_SRC_SFT) +#define NAU8824_CLK_ADC_SRC_SFT 6 +#define NAU8824_CLK_ADC_SRC_MASK (0x3 << NAU8824_CLK_ADC_SRC_SFT) +#define NAU8824_CLK_DAC_SRC_SFT 4 +#define NAU8824_CLK_DAC_SRC_MASK (0x3 << NAU8824_CLK_DAC_SRC_SFT) + +/* FLL1 (0x04) */ +#define NAU8824_FLL_RATIO_MASK (0x7f << 0) + +/* FLL3 (0x06) */ +#define NAU8824_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8824_FLL_CLK_SRC_SFT 10 +#define NAU8824_FLL_CLK_SRC_MASK (0x3 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_MCLK (0 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_BLK (0x2 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_FS (0x3 << NAU8824_FLL_CLK_SRC_SFT) + +/* FLL4 (0x07) */ +#define NAU8824_FLL_REF_DIV_SFT 10 +#define NAU8824_FLL_REF_DIV_MASK (0x3 << NAU8824_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8824_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8824_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8824_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8824_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8824_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8824_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8824_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8824_FLL_FTR_SW_FILTER (0x0 << 12) + +/* FLL6 (0x9) */ +#define NAU8824_DCO_EN (0x1 << 15) +#define NAU8824_SDM_EN (0x1 << 14) + +/* IRQ (0x10) */ +#define NAU8824_SHORT_CIRCUIT_IRQ (0x1 << 7) +#define NAU8824_IMPEDANCE_MEAS_IRQ (0x1 << 6) +#define NAU8824_KEY_RELEASE_IRQ (0x1 << 5) +#define NAU8824_KEY_LONG_PRESS_IRQ (0x1 << 4) +#define NAU8824_KEY_SHORT_PRESS_IRQ (0x1 << 3) +#define NAU8824_JACK_EJECTION_DETECTED (0x1 << 1) +#define NAU8824_JACK_INSERTION_DETECTED 0x1 + +/* JACK_DET_CTRL (0x0D) */ +#define NAU8824_JACK_EJECT_DT_SFT 2 +#define NAU8824_JACK_EJECT_DT_MASK (0x3 << NAU8824_JACK_EJECT_DT_SFT) +#define NAU8824_JACK_LOGIC 0x1 + + +/* INTERRUPT_SETTING_1 (0x0F) */ +#define NAU8824_IRQ_EJECT_EN (0x1 << 9) +#define NAU8824_IRQ_INSERT_EN (0x1 << 8) + +/* INTERRUPT_SETTING (0x12) */ +#define NAU8824_IRQ_KEY_RELEASE_DIS (0x1 << 5) +#define NAU8824_IRQ_KEY_SHORT_PRESS_DIS (0x1 << 3) +#define NAU8824_IRQ_EJECT_DIS (0x1 << 1) +#define NAU8824_IRQ_INSERT_DIS 0x1 + +/* SAR_ADC (0x13) */ +#define NAU8824_SAR_ADC_EN_SFT 12 +#define NAU8824_SAR_TRACKING_GAIN_SFT 8 +#define NAU8824_SAR_TRACKING_GAIN_MASK (0x7 << NAU8824_SAR_TRACKING_GAIN_SFT) +#define NAU8824_SAR_COMPARE_TIME_SFT 2 +#define NAU8824_SAR_COMPARE_TIME_MASK (3 << 2) +#define NAU8824_SAR_SAMPLING_TIME_SFT 0 +#define NAU8824_SAR_SAMPLING_TIME_MASK (3 << 0) + +/* VDET_COEFFICIENT (0x14) */ +#define NAU8824_SHORTKEY_DEBOUNCE_SFT 12 +#define NAU8824_SHORTKEY_DEBOUNCE_MASK (0x3 << NAU8824_SHORTKEY_DEBOUNCE_SFT) +#define NAU8824_LEVELS_NR_SFT 8 +#define NAU8824_LEVELS_NR_MASK (0x7 << 8) +#define NAU8824_HYSTERESIS_SFT 0 +#define NAU8824_HYSTERESIS_MASK 0xf + +/* PORT0_I2S_PCM_CTRL_1 (0x1C) */ +#define NAU8824_I2S_BP_SFT 7 +#define NAU8824_I2S_BP_MASK (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_BP_INV (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_PCMB_SFT 6 +#define NAU8824_I2S_PCMB_EN (1 << NAU8824_I2S_PCMB_SFT) +#define NAU8824_I2S_DL_SFT 2 +#define NAU8824_I2S_DL_MASK (0x3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_16 (0 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_20 (1 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_24 (2 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_32 (3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DF_MASK 0x3 +#define NAU8824_I2S_DF_RIGTH 0 +#define NAU8824_I2S_DF_LEFT 1 +#define NAU8824_I2S_DF_I2S 2 +#define NAU8824_I2S_DF_PCM_AB 3 + + +/* PORT0_I2S_PCM_CTRL_2 (0x1D) */ +#define NAU8824_I2S_LRC_DIV_SFT 12 +#define NAU8824_I2S_LRC_DIV_MASK (0x3 << NAU8824_I2S_LRC_DIV_SFT) +#define NAU8824_I2S_MS_SFT 3 +#define NAU8824_I2S_MS_MASK (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_MASTER (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_SLAVE (0 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_BLK_DIV_MASK 0x7 + +/* ADC_FILTER_CTRL (0x24) */ +#define NAU8824_ADC_SYNC_DOWN_MASK 0x3 +#define NAU8824_ADC_SYNC_DOWN_32 0 +#define NAU8824_ADC_SYNC_DOWN_64 1 +#define NAU8824_ADC_SYNC_DOWN_128 2 +#define NAU8824_ADC_SYNC_DOWN_256 3 + +/* DAC_FILTER_CTRL_1 (0x25) */ +#define NAU8824_DAC_CICCLP_OFF (0x1 << 7) +#define NAU8824_DAC_OVERSAMPLE_MASK 0x7 +#define NAU8824_DAC_OVERSAMPLE_64 0 +#define NAU8824_DAC_OVERSAMPLE_256 1 +#define NAU8824_DAC_OVERSAMPLE_128 2 +#define NAU8824_DAC_OVERSAMPLE_32 4 + +/* DAC_MUTE_CTRL (0x31) */ +#define NAU8824_DAC_CH01_MIX 0x3 +#define NAU8824_DAC_ZC_EN (0x1 << 11) + +/* DAC_CH0_DGAIN_CTRL (0x32) */ +#define NAU8824_DAC_CH0_SEL_SFT 9 +#define NAU8824_DAC_CH0_SEL_MASK (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S0 (0x0 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S1 (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_VOL_MASK 0x1ff + +/* DAC_CH1_DGAIN_CTRL (0x33) */ +#define NAU8824_DAC_CH1_SEL_SFT 9 +#define NAU8824_DAC_CH1_SEL_MASK (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S0 (0x0 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S1 (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_VOL_MASK 0x1ff + +/* CLASSG (0x50) */ +#define NAU8824_CLASSG_TIMER_SFT 8 +#define NAU8824_CLASSG_TIMER_MASK (0x3f << NAU8824_CLASSG_TIMER_SFT) +#define NAU8824_CLASSG_LDAC_EN_SFT 2 +#define NAU8824_CLASSG_RDAC_EN_SFT 1 +#define NAU8824_CLASSG_EN_SFT 0 + +/* SAR_ADC_DATA_OUT (0x59) */ +#define NAU8824_SAR_ADC_DATA_MASK 0xff + +/* BIAS_ADJ (0x66) */ +#define NAU8824_VMID (1 << 6) +#define NAU8824_VMID_SEL_SFT 4 +#define NAU8824_VMID_SEL_MASK (3 << NAU8824_VMID_SEL_SFT) +#define NAU8824_DMIC2_EN_SFT 3 +#define NAU8824_DMIC1_EN_SFT 2 + +/* TRIM_SETTINGS (0x68) */ +#define NAU8824_DRV_CURR_INC (1 << 15) + +/* ANALOG_CONTROL_1 (0x69) */ +#define NAU8824_DMIC_CLK_DRV_STRG (1 << 3) +#define NAU8824_DMIC_CLK_SLEW_FAST (0x7) + +/* ANALOG_CONTROL_2 (0x6A) */ +#define NAU8824_CLASSD_CLAMP_DIS_SFT 3 +#define NAU8824_CLASSD_CLAMP_DIS (0x1 << NAU8824_CLASSD_CLAMP_DIS_SFT) + +/* ENABLE_LO (0x6B) */ +#define NAU8824_TEST_DAC_SFT 14 +#define NAU8824_TEST_DAC_EN (0x3 << NAU8824_TEST_DAC_SFT) +#define NAU8824_DACL_HPR_EN_SFT 3 +#define NAU8824_DACL_HPR_EN (0x1 << NAU8824_DACL_HPR_EN_SFT) +#define NAU8824_DACR_HPR_EN_SFT 2 +#define NAU8824_DACR_HPR_EN (0x1 << NAU8824_DACR_HPR_EN_SFT) +#define NAU8824_DACR_HPL_EN_SFT 1 +#define NAU8824_DACR_HPL_EN (0x1 << NAU8824_DACR_HPL_EN_SFT) +#define NAU8824_DACL_HPL_EN_SFT 0 +#define NAU8824_DACL_HPL_EN 0x1 + +/* CLASSD_GAIN_1 (0x6D) */ +#define NAU8824_CLASSD_GAIN_1R_SFT 8 +#define NAU8824_CLASSD_GAIN_1R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_1L_MASK 0x1f + +/* CLASSD_GAIN_2 (0x6E) */ +#define NAU8824_CLASSD_GAIN_2R_SFT 8 +#define NAU8824_CLASSD_GAIN_2R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_2L_MASK 0x1f + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8824_ADCR_EN_SFT 7 +#define NAU8824_ADCL_EN_SFT 6 + +/* RDAC (0x73) */ +#define NAU8824_DACR_EN_SFT 13 +#define NAU8824_DACL_EN_SFT 12 +#define NAU8824_DACR_CLK_SFT 9 +#define NAU8824_DACL_CLK_SFT 8 +#define NAU8824_RDAC_CLK_DELAY_SFT 4 +#define NAU8824_RDAC_CLK_DELAY_MASK (0x7 << NAU8824_RDAC_CLK_DELAY_SFT) +#define NAU8824_RDAC_VREF_SFT 2 +#define NAU8824_RDAC_VREF_MASK (0x3 << NAU8824_RDAC_VREF_SFT) + +/* MIC_BIAS (0x74) */ +#define NAU8824_MICBIAS_JKSLV (1 << 14) +#define NAU8824_MICBIAS_JKR2 (1 << 12) +#define NAU8824_MICBIAS_POWERUP_SFT 8 +#define NAU8824_MICBIAS_VOLTAGE_SFT 0 +#define NAU8824_MICBIAS_VOLTAGE_MASK 0x7 + +/* BOOST (0x76) */ +#define NAU8824_PRECHARGE_DIS (0x1 << 13) +#define NAU8824_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8824_HP_BOOST_DIS_SFT 9 +#define NAU8824_HP_BOOST_DIS (0x1 << NAU8824_HP_BOOST_DIS_SFT) +#define NAU8824_HP_BOOST_G_DIS_SFT 8 +#define NAU8824_HP_BOOST_G_DIS (0x1 << NAU8824_HP_BOOST_G_DIS_SFT) +#define NAU8824_SHORT_SHUTDOWN_DIG_EN (1 << 7) +#define NAU8824_SHORT_SHUTDOWN_EN (1 << 6) + +/* FEPGA (0x77) */ +#define NAU8824_FEPGA_MODER_SHORT_SFT 7 +#define NAU8824_FEPGA_MODER_SHORT_EN (0x1 << NAU8824_FEPGA_MODER_SHORT_SFT) +#define NAU8824_FEPGA_MODER_MIC2_SFT 5 +#define NAU8824_FEPGA_MODER_MIC2_EN (0x1 << NAU8824_FEPGA_MODER_MIC2_SFT) +#define NAU8824_FEPGA_MODER_HSMIC_SFT 4 +#define NAU8824_FEPGA_MODER_HSMIC_EN (0x1 << NAU8824_FEPGA_MODER_HSMIC_SFT) +#define NAU8824_FEPGA_MODEL_SHORT_SFT 3 +#define NAU8824_FEPGA_MODEL_SHORT_EN (0x1 << NAU8824_FEPGA_MODEL_SHORT_SFT) +#define NAU8824_FEPGA_MODEL_MIC1_SFT 1 +#define NAU8824_FEPGA_MODEL_MIC1_EN (0x1 << NAU8824_FEPGA_MODEL_MIC1_SFT) +#define NAU8824_FEPGA_MODEL_HSMIC_SFT 0 +#define NAU8824_FEPGA_MODEL_HSMIC_EN (0x1 << NAU8824_FEPGA_MODEL_HSMIC_SFT) + +/* FEPGA_II (0x78) */ +#define NAU8824_FEPGA_GAINR_SFT 5 +#define NAU8824_FEPGA_GAINR_MASK (0x1f << NAU8824_FEPGA_GAINR_SFT) +#define NAU8824_FEPGA_GAINL_SFT 0 +#define NAU8824_FEPGA_GAINL_MASK 0x1f + +/* CHARGE_PUMP_CONTROL (0x80) */ +#define NAU8824_JAMNODCLOW (0x1 << 15) +#define NAU8824_SPKR_PULL_DOWN (0x1 << 13) +#define NAU8824_SPKL_PULL_DOWN (0x1 << 12) +#define NAU8824_POWER_DOWN_DACR (0x1 << 9) +#define NAU8824_POWER_DOWN_DACL (0x1 << 8) +#define NAU8824_CHARGE_PUMP_EN_SFT 5 +#define NAU8824_CHARGE_PUMP_EN (0x1 << NAU8824_CHARGE_PUMP_EN_SFT) + + +#define NAU8824_CODEC_DAI "nau8824-hifi" + +/* System Clock Source */ +enum { + NAU8824_CLK_DIS, + NAU8824_CLK_MCLK, + NAU8824_CLK_INTERNAL, + NAU8824_CLK_FLL_MCLK, + NAU8824_CLK_FLL_BLK, + NAU8824_CLK_FLL_FS, +}; + +struct nau8824 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack *jack; + struct work_struct jdet_work; + struct semaphore jd_sem; + int fs; + int irq; + int micbias_voltage; + int vref_impedance; + int jkdet_polarity; + int sar_threshold_num; + int sar_threshold[8]; + int sar_hysteresis; + int sar_voltage; + int sar_compare_time; + int sar_sampling_time; + int key_debounce; + int jack_eject_debounce; +}; + +struct nau8824_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8824_fll_attr { + unsigned int param; + unsigned int val; +}; + +struct nau8824_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + + +int nau8824_enable_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack); + +#endif /* _NAU8824_H */ + diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index b281a46d769d..f91221b1ddf0 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1084,13 +1084,28 @@ static int rt5514_parse_dt(struct rt5514_priv *rt5514, struct device *dev) return 0; } +static __maybe_unused int rt5514_i2c_resume(struct device *dev) +{ + struct rt5514_priv *rt5514 = dev_get_drvdata(dev); + unsigned int val; + + /* + * Add a bogus read to avoid rt5514's confusion after s2r in case it + * saw glitches on the i2c lines and thought the other side sent a + * start bit. + */ + regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + + return 0; +} + static int rt5514_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct rt5514_platform_data *pdata = dev_get_platdata(&i2c->dev); struct rt5514_priv *rt5514; int ret; - unsigned int val; + unsigned int val = ~0; rt5514 = devm_kzalloc(&i2c->dev, sizeof(struct rt5514_priv), GFP_KERNEL); @@ -1120,8 +1135,16 @@ static int rt5514_i2c_probe(struct i2c_client *i2c, return ret; } - regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); - if (val != RT5514_DEVICE_ID) { + /* + * The rt5514 can get confused if the i2c lines glitch together, as + * can happen at bootup as regulators are turned off and on. If it's + * in this glitched state the first i2c read will fail, so we'll give + * it one change to retry. + */ + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) { dev_err(&i2c->dev, "Device with ID register %x is not rt5514\n", val); return -ENODEV; @@ -1149,10 +1172,15 @@ static int rt5514_i2c_remove(struct i2c_client *i2c) return 0; } -struct i2c_driver rt5514_i2c_driver = { +static const struct dev_pm_ops rt5514_i2_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) +}; + +static struct i2c_driver rt5514_i2c_driver = { .driver = { .name = "rt5514", .of_match_table = of_match_ptr(rt5514_of_match), + .pm = &rt5514_i2_pm_ops, }, .probe = rt5514_i2c_probe, .remove = rt5514_i2c_remove, diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index e149f3ce5401..87844a45886a 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3542,6 +3542,15 @@ static const struct i2c_device_id rt5645_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); +#ifdef CONFIG_OF +static const struct of_device_id rt5645_of_match[] = { + { .compatible = "realtek,rt5645", }, + { .compatible = "realtek,rt5650", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5645_of_match); +#endif + #ifdef CONFIG_ACPI static const struct acpi_device_id rt5645_acpi_match[] = { { "10EC5645", 0 }, @@ -3912,6 +3921,7 @@ static void rt5645_i2c_shutdown(struct i2c_client *i2c) static struct i2c_driver rt5645_i2c_driver = { .driver = { .name = "rt5645", + .of_match_table = of_match_ptr(rt5645_of_match), .acpi_match_table = ACPI_PTR(rt5645_acpi_match), }, .probe = rt5645_i2c_probe, diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 476135ec5726..8cd22307f5b6 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -1139,7 +1139,8 @@ static void rt5665_enable_push_button_irq(struct snd_soc_codec *codec, bool enable) { if (enable) { - snd_soc_write(codec, RT5665_4BTN_IL_CMD_1, 0x000b); + snd_soc_write(codec, RT5665_4BTN_IL_CMD_1, 0x0003); + snd_soc_update_bits(codec, RT5665_SAR_IL_CMD_9, 0x1, 0x1); snd_soc_write(codec, RT5665_IL_CMD_1, 0x0048); snd_soc_update_bits(codec, RT5665_4BTN_IL_CMD_2, RT5665_4BTN_IL_MASK | RT5665_4BTN_IL_RST_MASK, @@ -1192,10 +1193,13 @@ static int rt5665_headset_detect(struct snd_soc_codec *codec, int jack_insert) } regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, - 0x180, 0x180); + 0x1a0, 0x120); regmap_write(rt5665->regmap, RT5665_EJD_CTRL_3, 0x3424); + regmap_write(rt5665->regmap, RT5665_IL_CMD_1, 0x0048); regmap_write(rt5665->regmap, RT5665_SAR_IL_CMD_1, 0xa291); + usleep_range(10000, 15000); + rt5665->sar_adc_value = snd_soc_read(rt5665->codec, RT5665_SAR_IL_CMD_4) & 0x7ff; @@ -1256,8 +1260,8 @@ static void rt5665_jd_check_handler(struct work_struct *work) } } -int rt5665_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *hs_jack) +static int rt5665_set_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *hs_jack, void *data) { struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); @@ -1284,7 +1288,6 @@ int rt5665_set_jack_detect(struct snd_soc_codec *codec, return 0; } -EXPORT_SYMBOL_GPL(rt5665_set_jack_detect); static void rt5665_jack_detect_handler(struct work_struct *work) { @@ -2600,6 +2603,55 @@ static int rt5655_set_verf(struct snd_soc_dapm_widget *w, return 0; } +static int rt5665_i2s_pin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + unsigned int val1, val2, mask1, mask2 = 0; + + switch (w->shift) { + case RT5665_PWR_I2S2_1_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP4_PIN_MASK | RT5665_GP5_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP4_PIN_DACDAT2_1 | RT5665_GP5_PIN_ADCDAT2_1; + break; + case RT5665_PWR_I2S2_2_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP8_PIN_DACDAT2_2; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT2_2; + break; + case RT5665_PWR_I2S3_BIT: + mask1 = RT5665_GP6_PIN_MASK | RT5665_GP7_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP6_PIN_BCLK3 | RT5665_GP7_PIN_LRCK3 | + RT5665_GP8_PIN_DACDAT3; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT3; + break; + } + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_1, mask1, val1); + if (mask2) + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_2, + mask2, val2); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_1, mask1, 0); + if (mask2) + snd_soc_update_bits(codec, RT5665_GPIO_CTRL_2, + mask2, 0); + break; + default: + return 0; + } + + return 0; +} static const struct snd_soc_dapm_widget rt5665_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("LDO2", RT5665_PWR_ANLG_3, RT5665_PWR_LDO2_BIT, 0, @@ -2852,11 +2904,14 @@ static const struct snd_soc_dapm_widget rt5665_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("I2S1_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S1_2_BIT, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("I2S2_1", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_1_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("I2S2_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_2_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("I2S3", RT5665_PWR_DIG_1, RT5665_PWR_I2S3_BIT, - 0, NULL, 0), + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("IF1 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -3963,12 +4018,68 @@ static const struct snd_soc_dapm_route rt5665_dapm_routes[] = { {"PDMR", NULL, "PDM R Playback"}, }; +static int rt5665_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val = 0; + + if (rx_mask || tx_mask) + val |= RT5665_I2S1_MODE_TDM; + + switch (slots) { + case 4: + val |= RT5665_TDM_IN_CH_4; + val |= RT5665_TDM_OUT_CH_4; + break; + case 6: + val |= RT5665_TDM_IN_CH_6; + val |= RT5665_TDM_OUT_CH_6; + break; + case 8: + val |= RT5665_TDM_IN_CH_8; + val |= RT5665_TDM_OUT_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= RT5665_TDM_IN_LEN_20; + val |= RT5665_TDM_OUT_LEN_20; + break; + case 24: + val |= RT5665_TDM_IN_LEN_24; + val |= RT5665_TDM_OUT_LEN_24; + break; + case 32: + val |= RT5665_TDM_IN_LEN_32; + val |= RT5665_TDM_OUT_LEN_32; + break; + case 16: + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, RT5665_TDM_CTRL_1, + RT5665_I2S1_MODE_MASK | RT5665_TDM_IN_CH_MASK | + RT5665_TDM_OUT_CH_MASK | RT5665_TDM_IN_LEN_MASK | + RT5665_TDM_OUT_LEN_MASK, val); + + return 0; +} + + static int rt5665_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 rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); - unsigned int val_len = 0, val_clk, mask_clk, val_bits = 0x0100; + unsigned int val_len = 0, val_clk, reg_clk, mask_clk, val_bits = 0x0100; int pre_div, frame_size; rt5665->lrck[dai->id] = params_rate(params); @@ -4009,6 +4120,10 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, switch (dai->id) { case RT5665_AIF1_1: case RT5665_AIF1_2: + if (params_channels(params) > 2) + rt5665_set_tdm_slot(dai, 0xf, 0xf, + params_channels(params), params_width(params)); + reg_clk = RT5665_ADDA_CLK_1; mask_clk = RT5665_I2S_PD1_MASK; val_clk = pre_div << RT5665_I2S_PD1_SFT; snd_soc_update_bits(codec, RT5665_I2S1_SDP, @@ -4016,12 +4131,14 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, break; case RT5665_AIF2_1: case RT5665_AIF2_2: + reg_clk = RT5665_ADDA_CLK_2; mask_clk = RT5665_I2S_PD2_MASK; val_clk = pre_div << RT5665_I2S_PD2_SFT; snd_soc_update_bits(codec, RT5665_I2S2_SDP, RT5665_I2S_DL_MASK, val_len); break; case RT5665_AIF3: + reg_clk = RT5665_ADDA_CLK_2; mask_clk = RT5665_I2S_PD3_MASK; val_clk = pre_div << RT5665_I2S_PD3_SFT; snd_soc_update_bits(codec, RT5665_I2S3_SDP, @@ -4032,7 +4149,7 @@ static int rt5665_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - snd_soc_update_bits(codec, RT5665_ADDA_CLK_1, mask_clk, val_clk); + snd_soc_update_bits(codec, reg_clk, mask_clk, val_clk); snd_soc_update_bits(codec, RT5665_STO1_DAC_SIL_DET, 0x3700, val_bits); switch (rt5665->lrck[dai->id]) { @@ -4125,10 +4242,9 @@ static int rt5665_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -static int rt5665_set_dai_sysclk(struct snd_soc_dai *dai, - int clk_id, unsigned int freq, int dir) +static int rt5665_set_codec_sysclk(struct snd_soc_codec *codec, int clk_id, + int source, unsigned int freq, int dir) { - struct snd_soc_codec *codec = dai->codec; struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); unsigned int reg_val = 0; @@ -4154,20 +4270,20 @@ static int rt5665_set_dai_sysclk(struct snd_soc_dai *dai, rt5665->sysclk = freq; rt5665->sysclk_src = clk_id; - dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + dev_dbg(codec->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); return 0; } -static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, - unsigned int freq_in, unsigned int freq_out) +static int rt5665_set_codec_pll(struct snd_soc_codec *codec, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) { - struct snd_soc_codec *codec = dai->codec; struct rt5665_priv *rt5665 = snd_soc_codec_get_drvdata(codec); struct rl6231_pll_code pll_code; int ret; - if (Source == rt5665->pll_src && freq_in == rt5665->pll_in && + if (source == rt5665->pll_src && freq_in == rt5665->pll_in && freq_out == rt5665->pll_out) return 0; @@ -4181,7 +4297,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, return 0; } - switch (Source) { + switch (source) { case RT5665_PLL1_S_MCLK: snd_soc_update_bits(codec, RT5665_GLB_CLK, RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_MCLK); @@ -4199,7 +4315,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_BCLK3); break; default: - dev_err(codec->dev, "Unknown PLL Source %d\n", Source); + dev_err(codec->dev, "Unknown PLL Source %d\n", source); return -EINVAL; } @@ -4221,62 +4337,7 @@ static int rt5665_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int Source, rt5665->pll_in = freq_in; rt5665->pll_out = freq_out; - rt5665->pll_src = Source; - - return 0; -} - -static int rt5665_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, - unsigned int rx_mask, int slots, int slot_width) -{ - struct snd_soc_codec *codec = dai->codec; - unsigned int val = 0; - - if (rx_mask || tx_mask) - val |= RT5665_I2S1_MODE_TDM; - - switch (slots) { - case 4: - val |= RT5665_TDM_IN_CH_4; - val |= RT5665_TDM_OUT_CH_4; - break; - case 6: - val |= RT5665_TDM_IN_CH_6; - val |= RT5665_TDM_OUT_CH_6; - break; - case 8: - val |= RT5665_TDM_IN_CH_8; - val |= RT5665_TDM_OUT_CH_8; - break; - case 2: - break; - default: - return -EINVAL; - } - - switch (slot_width) { - case 20: - val |= RT5665_TDM_IN_LEN_20; - val |= RT5665_TDM_OUT_LEN_20; - break; - case 24: - val |= RT5665_TDM_IN_LEN_24; - val |= RT5665_TDM_OUT_LEN_24; - break; - case 32: - val |= RT5665_TDM_IN_LEN_32; - val |= RT5665_TDM_OUT_LEN_32; - break; - case 16: - break; - default: - return -EINVAL; - } - - snd_soc_update_bits(codec, RT5665_TDM_CTRL_1, - RT5665_I2S1_MODE_MASK | RT5665_TDM_IN_CH_MASK | - RT5665_TDM_OUT_CH_MASK | RT5665_TDM_IN_LEN_MASK | - RT5665_TDM_OUT_LEN_MASK, val); + rt5665->pll_src = source; return 0; } @@ -4393,9 +4454,7 @@ static int rt5665_resume(struct snd_soc_codec *codec) static const struct snd_soc_dai_ops rt5665_aif_dai_ops = { .hw_params = rt5665_hw_params, .set_fmt = rt5665_set_dai_fmt, - .set_sysclk = rt5665_set_dai_sysclk, .set_tdm_slot = rt5665_set_tdm_slot, - .set_pll = rt5665_set_dai_pll, .set_bclk_ratio = rt5665_set_bclk_ratio, }; @@ -4504,7 +4563,10 @@ static struct snd_soc_codec_driver soc_codec_dev_rt5665 = { .num_dapm_widgets = ARRAY_SIZE(rt5665_dapm_widgets), .dapm_routes = rt5665_dapm_routes, .num_dapm_routes = ARRAY_SIZE(rt5665_dapm_routes), - } + }, + .set_sysclk = rt5665_set_codec_sysclk, + .set_pll = rt5665_set_codec_pll, + .set_jack = rt5665_set_jack_detect, }; @@ -4783,7 +4845,7 @@ static int rt5665_i2c_probe(struct i2c_client *i2c, regmap_write(rt5665->regmap, RT5665_HP_LOGIC_CTRL_2, 0x0002); regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, - 0xf000 | RT5665_VREF_POW_MASK, 0xd000 | RT5665_VREF_POW_REG); + 0xf000 | RT5665_VREF_POW_MASK, 0xe000 | RT5665_VREF_POW_REG); /* Work around for pow_pump */ regmap_update_bits(rt5665->regmap, RT5665_STO1_DAC_SIL_DET, RT5665_DEB_STO_DAC_MASK, RT5665_DEB_80_MS); diff --git a/sound/soc/codecs/rt5665.h b/sound/soc/codecs/rt5665.h index a30f5e6d0628..1db5c6a62a8e 100644 --- a/sound/soc/codecs/rt5665.h +++ b/sound/soc/codecs/rt5665.h @@ -1984,7 +1984,5 @@ enum { int rt5665_sel_asrc_clk_src(struct snd_soc_codec *codec, unsigned int filter_mask, unsigned int clk_src); -int rt5665_set_jack_detect(struct snd_soc_codec *codec, - struct snd_soc_jack *hs_jack); #endif /* __RT5665_H__ */ diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 17d20b99f041..e27c5a4a0a15 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2835,6 +2835,27 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Wyse 3040"), }, }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + }, + { + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), + }, + }, {} }; diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index abc802a5a479..65ac4518ad06 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5035,6 +5035,12 @@ static const struct i2c_device_id rt5677_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, rt5677_i2c_id); +static const struct of_device_id rt5677_of_match[] = { + { .compatible = "realtek,rt5677", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5677_of_match); + static const struct acpi_gpio_params plug_det_gpio = { RT5677_GPIO_PLUG_DET, 0, false }; static const struct acpi_gpio_params mic_present_gpio = { RT5677_GPIO_MIC_PRESENT_L, 0, false }; static const struct acpi_gpio_params headphone_enable_gpio = { RT5677_GPIO_HP_AMP_SHDN_L, 0, false }; @@ -5294,6 +5300,7 @@ static int rt5677_i2c_remove(struct i2c_client *i2c) static struct i2c_driver rt5677_i2c_driver = { .driver = { .name = "rt5677", + .of_match_table = rt5677_of_match, }, .probe = rt5677_i2c_probe, .remove = rt5677_i2c_remove, diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 1589325855bc..5a2702edeb77 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -99,6 +99,13 @@ enum sgtl5000_micbias_resistor { SGTL5000_MICBIAS_8K = 8, }; +enum { + I2S_LRCLK_STRENGTH_DISABLE, + I2S_LRCLK_STRENGTH_LOW, + I2S_LRCLK_STRENGTH_MEDIUM, + I2S_LRCLK_STRENGTH_HIGH, +}; + /* sgtl5000 private structure in codec */ struct sgtl5000_priv { int sysclk; /* sysclk rate */ @@ -111,6 +118,7 @@ struct sgtl5000_priv { int revision; u8 micbias_resistor; u8 micbias_voltage; + u8 lrclk_strength; }; /* @@ -1089,6 +1097,7 @@ static int sgtl5000_enable_regulators(struct i2c_client *client) static int sgtl5000_probe(struct snd_soc_codec *codec) { int ret; + u16 reg; struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); /* power up sgtl5000 */ @@ -1118,7 +1127,8 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT); - snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + reg = ((sgtl5000->lrclk_strength) << SGTL5000_PAD_I2S_LRCLK_SHIFT | 0x5f); + snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, reg); snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL, SGTL5000_HP_ZCD_EN | @@ -1347,6 +1357,13 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, } } + sgtl5000->lrclk_strength = I2S_LRCLK_STRENGTH_LOW; + if (!of_property_read_u32(np, "lrclk-strength", &value)) { + if (value > I2S_LRCLK_STRENGTH_HIGH) + value = I2S_LRCLK_STRENGTH_LOW; + sgtl5000->lrclk_strength = value; + } + /* Ensure sgtl5000 will start with sane register values */ sgtl5000_fill_defaults(client); diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c index 2bb5a11c9ba1..a622623e8558 100644 --- a/sound/soc/codecs/ssm4567.c +++ b/sound/soc/codecs/ssm4567.c @@ -485,6 +485,14 @@ static const struct i2c_device_id ssm4567_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, ssm4567_i2c_ids); +#ifdef CONFIG_OF +static const struct of_device_id ssm4567_of_match[] = { + { .compatible = "adi,ssm4567", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm4567_of_match); +#endif + #ifdef CONFIG_ACPI static const struct acpi_device_id ssm4567_acpi_match[] = { @@ -498,6 +506,7 @@ MODULE_DEVICE_TABLE(acpi, ssm4567_acpi_match); static struct i2c_driver ssm4567_driver = { .driver = { .name = "ssm4567", + .of_match_table = of_match_ptr(ssm4567_of_match), .acpi_match_table = ACPI_PTR(ssm4567_acpi_match), }, .probe = ssm4567_i2c_probe, diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index d4b384e4b266..660734359bf3 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -375,9 +375,16 @@ static const struct i2c_device_id sta529_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, sta529_i2c_id); +static const struct of_device_id sta529_of_match[] = { + { .compatible = "st,sta529", }, + { } +}; +MODULE_DEVICE_TABLE(of, sta529_of_match); + static struct i2c_driver sta529_i2c_driver = { .driver = { .name = "sta529", + .of_match_table = sta529_of_match, }, .probe = sta529_i2c_probe, .remove = sta529_i2c_remove, diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index baf455e8c2f7..8840f72f3c4a 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -611,7 +611,7 @@ probe_fail: regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies), tas2552->supplies); - return -EIO; + return ret; } static int tas2552_codec_remove(struct snd_soc_codec *codec) @@ -637,7 +637,7 @@ static int tas2552_suspend(struct snd_soc_codec *codec) if (ret != 0) dev_err(codec->dev, "Failed to disable supplies: %d\n", ret); - return 0; + return ret; } static int tas2552_resume(struct snd_soc_codec *codec) @@ -653,7 +653,7 @@ static int tas2552_resume(struct snd_soc_codec *codec) ret); } - return 0; + return ret; } #else #define tas2552_suspend NULL diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 410cae0f2060..628a8eeaab68 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -174,10 +174,9 @@ static const struct snd_soc_dapm_route tlv320aic23_intercon[] = { {"ROUT", NULL, "Output Mixer"}, /* Inputs */ - {"Line Input", "NULL", "LLINEIN"}, - {"Line Input", "NULL", "RLINEIN"}, - - {"Mic Input", "NULL", "MICIN"}, + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Input", NULL, "MICIN"}, /* input mux */ {"Capture Source", "Line", "Line Input"}, diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 748036e851ea..2b6ad09e0886 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -606,6 +606,14 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { twl6040_headset_power_get_enum, twl6040_headset_power_put_enum), + /* Left HS PDM data routed to Right HSDAC */ + SOC_SINGLE("Headset Mono to Stereo Playback Switch", + TWL6040_REG_HSRCTL, 7, 1, 0), + + /* Left HF PDM data routed to Right HFDAC */ + SOC_SINGLE("Handsfree Mono to Stereo Playback Switch", + TWL6040_REG_HFRCTL, 5, 1, 0), + SOC_ENUM_EXT("PLL Selection", twl6040_power_mode_enum, twl6040_pll_get_enum, twl6040_pll_put_enum), }; diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 2918fdb95e58..61cdc79840e7 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -791,9 +791,16 @@ static const struct i2c_device_id uda1380_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); +static const struct of_device_id uda1380_of_match[] = { + { .compatible = "nxp,uda1380", }, + { } +}; +MODULE_DEVICE_TABLE(of, uda1380_of_match); + static struct i2c_driver uda1380_i2c_driver = { .driver = { .name = "uda1380-codec", + .of_match_table = uda1380_of_match, }, .probe = uda1380_i2c_probe, .remove = uda1380_i2c_remove, diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 560575000cc5..138a84efdd54 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -2014,7 +2014,7 @@ static void wm5100_micd_irq(struct wm5100_priv *wm5100) ret = regmap_read(wm5100->regmap, WM5100_MIC_DETECT_3, &val); if (ret != 0) { - dev_err(wm5100->dev, "Failed to read micropone status: %d\n", + dev_err(wm5100->dev, "Failed to read microphone status: %d\n", ret); return; } diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 6e887c2c42b1..237eeb9a8b97 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -24,6 +24,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/irq.h> #include <linux/mutex.h> @@ -115,10 +116,19 @@ static const struct reg_default wm8903_reg_defaults[] = { { 172, 0x0000 }, /* R172 - Analogue Output Bias 0 */ }; +#define WM8903_NUM_SUPPLIES 4 +static const char *wm8903_supply_names[WM8903_NUM_SUPPLIES] = { + "AVDD", + "CPVDD", + "DBVDD", + "DCVDD", +}; + struct wm8903_priv { struct wm8903_platform_data *pdata; struct device *dev; struct regmap *regmap; + struct regulator_bulk_data supplies[WM8903_NUM_SUPPLIES]; int sysclk; int irq; @@ -2030,6 +2040,23 @@ static int wm8903_i2c_probe(struct i2c_client *i2c, pdata = wm8903->pdata; + for (i = 0; i < ARRAY_SIZE(wm8903->supplies); i++) + wm8903->supplies[i].supply = wm8903_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + ret = regmap_read(wm8903->regmap, WM8903_SW_RESET_AND_ID, &val); if (ret != 0) { dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); @@ -2160,6 +2187,8 @@ static int wm8903_i2c_probe(struct i2c_client *i2c, return 0; err: + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); return ret; } @@ -2167,6 +2196,8 @@ static int wm8903_i2c_remove(struct i2c_client *client) { struct wm8903_priv *wm8903 = i2c_get_clientdata(client); + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); if (client->irq) free_irq(client->irq, wm8903); wm8903_free_gpio(wm8903); diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 3bf081a7e450..9ed455700954 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -604,12 +604,150 @@ static const int bclk_divs[] = { 120, 160, 220, 240, 320, 320, 320 }; +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff, closest = mclk; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @codec: codec structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * < 0, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int sysclk, bclk, lrclk, freq_out; + int diff, closest, best_freq_out; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + closest = freq_in; + + best_freq_out = -EINVAL; + *sysclk_idx = *dac_idx = *bclk_idx = -1; + + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + return freq_out; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + best_freq_out = freq_out; + } + } + } + } + + return best_freq_out; +} static int wm8960_configure_clocking(struct snd_soc_codec *codec) { struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - int sysclk, bclk, lrclk, freq_out, freq_in; + int freq_out, freq_in; u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); int i, j, k; + int ret; if (!(iface1 & (1<<6))) { dev_dbg(codec->dev, @@ -623,8 +761,6 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } freq_in = wm8960->freq_in; - bclk = wm8960->bclk; - lrclk = wm8960->lrclk; /* * If it's sysclk auto mode, check if the MCLK can provide sysclk or * not. If MCLK can provide sysclk, using MCLK to provide sysclk @@ -643,60 +779,21 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec) } if (wm8960->clk_id != WM8960_SYSCLK_PLL) { - /* check if the sysclk frequency is available. */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - sysclk = freq_out / sysclk_divs[i]; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - if (sysclk != dac_divs[j] * lrclk) - continue; - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) - if (sysclk == bclk * bclk_divs[k] / 10) - break; - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - - if (i != ARRAY_SIZE(sysclk_divs)) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { goto configure_clock; } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { dev_err(codec->dev, "failed to configure clock\n"); return -EINVAL; } } - /* get a available pll out frequency and set pll */ - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { - if (sysclk_divs[i] == -1) - continue; - for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { - sysclk = lrclk * dac_divs[j]; - freq_out = sysclk * sysclk_divs[i]; - for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { - if (sysclk == bclk * bclk_divs[k] / 10 && - is_pll_freq_available(freq_in, freq_out)) { - wm8960_set_pll(codec, - freq_in, freq_out); - break; - } else { - continue; - } - } - if (k != ARRAY_SIZE(bclk_divs)) - break; - } - if (j != ARRAY_SIZE(dac_divs)) - break; - } - - if (i == ARRAY_SIZE(sysclk_divs)) { - dev_err(codec->dev, "failed to configure clock\n"); - return -EINVAL; + freq_out = wm8960_configure_pll(codec, freq_in, &i, &j, &k); + if (freq_out < 0) { + dev_err(codec->dev, "failed to configure clock via PLL\n"); + return freq_out; } + wm8960_set_pll(codec, freq_in, freq_out); configure_clock: /* configure sysclk clock */ diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 90b2d418ef60..cf761e2d7546 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -1071,9 +1071,16 @@ static const struct i2c_device_id wm8978_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id); +static const struct of_device_id wm8978_of_match[] = { + { .compatible = "wlf,wm8978", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8978_of_match); + static struct i2c_driver wm8978_i2c_driver = { .driver = { .name = "wm8978", + .of_match_table = wm8978_of_match, }, .probe = wm8978_i2c_probe, .remove = wm8978_i2c_remove, diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index bbdb72f73df1..20695b691aff 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -112,17 +112,22 @@ #define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ #define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ -#define ADSP2_CONTROL 0x0 -#define ADSP2_CLOCKING 0x1 -#define ADSP2_STATUS1 0x4 -#define ADSP2_WDMA_CONFIG_1 0x30 -#define ADSP2_WDMA_CONFIG_2 0x31 -#define ADSP2_RDMA_CONFIG_1 0x34 - -#define ADSP2_SCRATCH0 0x40 -#define ADSP2_SCRATCH1 0x41 -#define ADSP2_SCRATCH2 0x42 -#define ADSP2_SCRATCH3 0x43 +#define ADSP2_CONTROL 0x0 +#define ADSP2_CLOCKING 0x1 +#define ADSP2V2_CLOCKING 0x2 +#define ADSP2_STATUS1 0x4 +#define ADSP2_WDMA_CONFIG_1 0x30 +#define ADSP2_WDMA_CONFIG_2 0x31 +#define ADSP2V2_WDMA_CONFIG_2 0x32 +#define ADSP2_RDMA_CONFIG_1 0x34 + +#define ADSP2_SCRATCH0 0x40 +#define ADSP2_SCRATCH1 0x41 +#define ADSP2_SCRATCH2 0x42 +#define ADSP2_SCRATCH3 0x43 + +#define ADSP2V2_SCRATCH0_1 0x40 +#define ADSP2V2_SCRATCH2_3 0x42 /* * ADSP2 Control @@ -153,6 +158,17 @@ #define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ /* + * ADSP2V2 clocking + */ +#define ADSP2V2_CLK_SEL_MASK 0x70000 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_SHIFT 16 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2V2_RATE_MASK 0x7800 /* DSP_RATE */ +#define ADSP2V2_RATE_SHIFT 11 /* DSP_RATE */ +#define ADSP2V2_RATE_WIDTH 4 /* DSP_RATE */ + +/* * ADSP2 Status 1 */ #define ADSP2_RAM_RDY 0x0001 @@ -160,6 +176,37 @@ #define ADSP2_RAM_RDY_SHIFT 0 #define ADSP2_RAM_RDY_WIDTH 1 +/* + * ADSP2 Lock support + */ +#define ADSP2_LOCK_CODE_0 0x5555 +#define ADSP2_LOCK_CODE_1 0xAAAA + +#define ADSP2_WATCHDOG 0x0A +#define ADSP2_BUS_ERR_ADDR 0x52 +#define ADSP2_REGION_LOCK_STATUS 0x64 +#define ADSP2_LOCK_REGION_1_LOCK_REGION_0 0x66 +#define ADSP2_LOCK_REGION_3_LOCK_REGION_2 0x68 +#define ADSP2_LOCK_REGION_5_LOCK_REGION_4 0x6A +#define ADSP2_LOCK_REGION_7_LOCK_REGION_6 0x6C +#define ADSP2_LOCK_REGION_9_LOCK_REGION_8 0x6E +#define ADSP2_LOCK_REGION_CTRL 0x7A +#define ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR 0x7C + +#define ADSP2_REGION_LOCK_ERR_MASK 0x8000 +#define ADSP2_SLAVE_ERR_MASK 0x4000 +#define ADSP2_WDT_TIMEOUT_STS_MASK 0x2000 +#define ADSP2_CTRL_ERR_PAUSE_ENA 0x0002 +#define ADSP2_CTRL_ERR_EINT 0x0001 + +#define ADSP2_BUS_ERR_ADDR_MASK 0x00FFFFFF +#define ADSP2_XMEM_ERR_ADDR_MASK 0x0000FFFF +#define ADSP2_PMEM_ERR_ADDR_MASK 0x7FFF0000 +#define ADSP2_PMEM_ERR_ADDR_SHIFT 16 +#define ADSP2_WDT_ENA_MASK 0xFFFFFFFD + +#define ADSP2_LOCK_REGION_SHIFT 16 + #define ADSP_MAX_STD_CTRL_SIZE 512 #define WM_ADSP_ACKED_CTL_TIMEOUT_MS 100 @@ -683,6 +730,9 @@ static const struct soc_enum wm_adsp_fw_enum[] = { SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 4, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 5, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 6, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), }; const struct snd_kcontrol_new wm_adsp_fw_controls[] = { @@ -694,6 +744,12 @@ const struct snd_kcontrol_new wm_adsp_fw_controls[] = { wm_adsp_fw_get, wm_adsp_fw_put), SOC_ENUM_EXT("DSP4 Firmware", wm_adsp_fw_enum[3], wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP5 Firmware", wm_adsp_fw_enum[4], + wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP6 Firmware", wm_adsp_fw_enum[5], + wm_adsp_fw_get, wm_adsp_fw_put), + SOC_ENUM_EXT("DSP7 Firmware", wm_adsp_fw_enum[6], + wm_adsp_fw_get, wm_adsp_fw_put), }; EXPORT_SYMBOL_GPL(wm_adsp_fw_controls); @@ -750,6 +806,29 @@ static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) be16_to_cpu(scratch[3])); } +static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) +{ + u32 scratch[2]; + int ret; + + ret = regmap_raw_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH0_1, + scratch, sizeof(scratch)); + + if (ret) { + adsp_err(dsp, "Failed to read SCRATCH regs: %d\n", ret); + return; + } + + scratch[0] = be32_to_cpu(scratch[0]); + scratch[1] = be32_to_cpu(scratch[1]); + + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + scratch[0] & 0xFFFF, + scratch[0] >> 16, + scratch[1] & 0xFFFF, + scratch[1] >> 16); +} + static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) { return container_of(ext, struct wm_coeff_ctl, bytes_ext); @@ -2435,10 +2514,17 @@ static int wm_adsp2_ena(struct wm_adsp *dsp) unsigned int val; int ret, count; - ret = regmap_update_bits_async(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, ADSP2_SYS_ENA); - if (ret != 0) - return ret; + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + break; + default: + break; + } /* Wait for the RAM to start, should be near instantaneous */ for (count = 0; count < 10; ++count) { @@ -2497,11 +2583,17 @@ static void wm_adsp2_boot_work(struct work_struct *work) if (ret != 0) goto err_ena; - /* Turn DSP back off until we are ready to run */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); - if (ret != 0) - goto err_ena; + switch (dsp->rev) { + case 0: + /* Turn DSP back off until we are ready to run */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + if (ret != 0) + goto err_ena; + break; + default: + break; + } dsp->booted = true; @@ -2523,12 +2615,21 @@ static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq) { int ret; - ret = regmap_update_bits_async(dsp->regmap, - dsp->base + ADSP2_CLOCKING, - ADSP2_CLK_SEL_MASK, - freq << ADSP2_CLK_SEL_SHIFT); - if (ret != 0) - adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) { + adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + return; + } + break; + default: + /* clock is handled by parent codec driver */ + break; + } } int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, @@ -2568,6 +2669,18 @@ int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); +static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) +{ + switch (dsp->rev) { + case 0: + case 1: + return; + default: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); + } +} + int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event, unsigned int freq) @@ -2640,6 +2753,8 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, if (ret != 0) goto err; + wm_adsp2_lock(dsp, dsp->lock_regions); + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, ADSP2_CORE_ENA | ADSP2_START, @@ -2663,23 +2778,49 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, /* Tell the firmware to cleanup */ wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); + wm_adsp_stop_watchdog(dsp); + /* Log firmware state, it can be useful for analysis */ - wm_adsp2_show_fw_status(dsp); + switch (dsp->rev) { + case 0: + wm_adsp2_show_fw_status(dsp); + break; + default: + wm_adsp2v2_show_fw_status(dsp); + break; + } mutex_lock(&dsp->pwr_lock); dsp->running = false; - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, ADSP2_CORE_ENA | ADSP2_START, 0); /* Make sure DMAs are quiesced */ - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); - - regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA, 0); + switch (dsp->rev) { + case 0: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + break; + default: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); + break; + } if (wm_adsp_fw[dsp->fw].num_caps != 0) wm_adsp_buffer_free(dsp); @@ -2732,15 +2873,22 @@ int wm_adsp2_init(struct wm_adsp *dsp) { int ret; - /* - * Disable the DSP memory by default when in reset for a small - * power saving. - */ - ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_MEM_ENA, 0); - if (ret != 0) { - adsp_err(dsp, "Failed to clear memory retention: %d\n", ret); - return ret; + switch (dsp->rev) { + case 0: + /* + * Disable the DSP memory by default when in reset for a small + * power saving. + */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); + if (ret) { + adsp_err(dsp, + "Failed to clear memory retention: %d\n", ret); + return ret; + } + break; + default: + break; } INIT_LIST_HEAD(&dsp->alg_regions); @@ -3523,4 +3671,94 @@ int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf, } EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & WM_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= WM_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_lock); + +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) +{ + unsigned int val; + struct regmap *regmap = dsp->regmap; + int ret = 0; + + ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Region Lock Ctrl register: %d\n", ret); + return IRQ_HANDLED; + } + + if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { + adsp_err(dsp, "watchdog timeout error\n"); + wm_adsp_stop_watchdog(dsp); + } + + if (val & (ADSP2_SLAVE_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { + if (val & ADSP2_SLAVE_ERR_MASK) + adsp_err(dsp, "bus error: slave error\n"); + else + adsp_err(dsp, "bus error: region lock error\n"); + + ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Bus Err Addr register: %d\n", + ret); + return IRQ_HANDLED; + } + + adsp_err(dsp, "bus error address = 0x%x\n", + val & ADSP2_BUS_ERR_ADDR_MASK); + + ret = regmap_read(regmap, + dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR, + &val); + if (ret) { + adsp_err(dsp, + "Failed to read Pmem Xmem Err Addr register: %d\n", + ret); + return IRQ_HANDLED; + } + + adsp_err(dsp, "xmem error address = 0x%x\n", + val & ADSP2_XMEM_ERR_ADDR_MASK); + adsp_err(dsp, "pmem error address = 0x%x\n", + (val & ADSP2_PMEM_ERR_ADDR_MASK) >> + ADSP2_PMEM_ERR_ADDR_SHIFT); + } + + regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, + ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); + MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 3706b11053a3..41cc11c19b83 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -23,6 +23,23 @@ #define WM_ADSP_COMPR_OK 0 #define WM_ADSP_COMPR_VOICE_TRIGGER 1 +#define WM_ADSP2_REGION_0 BIT(0) +#define WM_ADSP2_REGION_1 BIT(1) +#define WM_ADSP2_REGION_2 BIT(2) +#define WM_ADSP2_REGION_3 BIT(3) +#define WM_ADSP2_REGION_4 BIT(4) +#define WM_ADSP2_REGION_5 BIT(5) +#define WM_ADSP2_REGION_6 BIT(6) +#define WM_ADSP2_REGION_7 BIT(7) +#define WM_ADSP2_REGION_8 BIT(8) +#define WM_ADSP2_REGION_9 BIT(9) +#define WM_ADSP2_REGION_1_9 (WM_ADSP2_REGION_1 | \ + WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3 | \ + WM_ADSP2_REGION_4 | WM_ADSP2_REGION_5 | \ + WM_ADSP2_REGION_6 | WM_ADSP2_REGION_7 | \ + WM_ADSP2_REGION_8 | WM_ADSP2_REGION_9) +#define WM_ADSP2_REGION_ALL (WM_ADSP2_REGION_0 | WM_ADSP2_REGION_1_9) + struct wm_adsp_region { int type; unsigned int base; @@ -40,6 +57,7 @@ struct wm_adsp_compr_buf; struct wm_adsp { const char *part; + int rev; int num; int type; struct device *dev; @@ -75,6 +93,8 @@ struct wm_adsp { struct mutex pwr_lock; + unsigned int lock_regions; + #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_root; char *wmfw_file_name; @@ -113,6 +133,10 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event, unsigned int freq); + +int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions); +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp); + int wm_adsp2_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index c297efe43861..c6fd95fa5ca6 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -8,10 +8,10 @@ config SND_DESIGNWARE_I2S maximum of 8 channels each for play and record. config SND_DESIGNWARE_PCM - tristate "PCM PIO extension for I2S driver" + bool "PCM PIO extension for I2S driver" depends on SND_DESIGNWARE_I2S help - Say Y, M or N if you want to add a custom ALSA extension that registers + Say Y or N if you want to add a custom ALSA extension that registers a PCM and uses PIO to transfer data. This functionality is specially suited for I2S devices that don't have diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 38f1ca31c5fa..3e24c0ff95fb 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,5 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o -ifdef CONFIG_SND_DESIGNWARE_PCM -obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o -endif + +designware_i2s-y := dwc-i2s.o +designware_i2s-$(CONFIG_SND_DESIGNWARE_PCM) += dwc-pcm.o diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/dwc-i2s.c index 9c46e4112026..9c46e4112026 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/dwc-i2s.c diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/dwc-pcm.c index 459ec861e6b6..406fd867117b 100644 --- a/sound/soc/dwc/designware_pcm.c +++ b/sound/soc/dwc/dwc-pcm.c @@ -129,13 +129,11 @@ void dw_pcm_push_tx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, true); } -EXPORT_SYMBOL_GPL(dw_pcm_push_tx); void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, false); } -EXPORT_SYMBOL_GPL(dw_pcm_pop_rx); static int dw_pcm_open(struct snd_pcm_substream *substream) { @@ -281,4 +279,3 @@ int dw_pcm_register(struct platform_device *pdev) { return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); } -EXPORT_SYMBOL_GPL(dw_pcm_register); diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c index 883087f2b092..84ef6385736c 100644 --- a/sound/soc/fsl/eukrea-tlv320.c +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -64,7 +64,7 @@ static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops eukrea_tlv320_snd_ops = { +static const struct snd_soc_ops eukrea_tlv320_snd_ops = { .hw_params = eukrea_tlv320_hw_params, }; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index dc30d780f874..282d841840b1 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -76,7 +76,7 @@ static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) pair->dma_chan[!dir], runtime->dma_addr, snd_pcm_lib_buffer_bytes(substream), snd_pcm_lib_period_bytes(substream), - dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags); + dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); if (!pair->desc[!dir]) { dev_err(dev, "failed to prepare slave DMA for Front-End\n"); return -ENOMEM; diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 38bfd46f4ad8..809a069d490b 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -19,7 +19,6 @@ #include "fsl_esai.h" #include "imx-pcm.h" -#define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000 #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -647,14 +646,14 @@ static struct snd_soc_dai_driver fsl_esai_dai = { .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 12, - .rates = FSL_ESAI_RATES, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = FSL_ESAI_FORMATS, }, .capture = { .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 8, - .rates = FSL_ESAI_RATES, + .rates = SNDRV_PCM_RATE_8000_192000, .formats = FSL_ESAI_FORMATS, }, .ops = &fsl_esai_dai_ops, diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index fde08660b63b..173cb8496641 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -35,6 +35,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/clk.h> +#include <linux/ctype.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/slab.h> @@ -55,16 +56,6 @@ #include "imx-pcm.h" /** - * FSLSSI_I2S_RATES: sample rates supported by the I2S - * - * This driver currently only supports the SSI running in I2S slave mode, - * which means the codec determines the sample rate. Therefore, we tell - * ALSA that we support all rates and let the codec driver decide what rates - * are really supported. - */ -#define FSLSSI_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS - -/** * FSLSSI_I2S_FORMATS: audio formats supported by the SSI * * The SSI has a limitation in that the samples must be in the same byte @@ -1212,14 +1203,14 @@ static struct snd_soc_dai_driver fsl_ssi_dai_template = { .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 32, - .rates = FSLSSI_I2S_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, .formats = FSLSSI_I2S_FORMATS, }, .capture = { .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 32, - .rates = FSLSSI_I2S_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, .formats = FSLSSI_I2S_FORMATS, }, .ops = &fsl_ssi_dai_ops, @@ -1325,14 +1316,10 @@ static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = { */ static void make_lowercase(char *s) { - char *p = s; - char c; - - while ((c = *p)) { - if ((c >= 'A') && (c <= 'Z')) - *p = c + ('a' - 'A'); - p++; - } + if (!s) + return; + for (; *s; s++) + *s = tolower(*s); } static int fsl_ssi_imx_probe(struct platform_device *pdev, diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c index bb0459018b45..9d19b808f634 100644 --- a/sound/soc/fsl/imx-mc13783.c +++ b/sound/soc/fsl/imx-mc13783.c @@ -48,7 +48,7 @@ static int imx_mc13783_hifi_hw_params(struct snd_pcm_substream *substream, return snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 16); } -static struct snd_soc_ops imx_mc13783_hifi_ops = { +static const struct snd_soc_ops imx_mc13783_hifi_ops = { .hw_params = imx_mc13783_hifi_hw_params, }; diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index f3d3d1ffa84e..314814ddd2b0 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -33,48 +33,20 @@ static bool filter(struct dma_chan *chan, void *param) return true; } -static const struct snd_pcm_hardware imx_pcm_hardware = { - .info = SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, - .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE, - .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ - .periods_min = 2, - .periods_max = 255, - .fifo_size = 0, -}; - static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { - .pcm_hardware = &imx_pcm_hardware, .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, .compat_filter_fn = filter, - .prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE, }; int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { struct snd_dmaengine_pcm_config *config; - struct snd_pcm_hardware *pcm_hardware; config = devm_kzalloc(&pdev->dev, sizeof(struct snd_dmaengine_pcm_config), GFP_KERNEL); if (!config) return -ENOMEM; *config = imx_dmaengine_pcm_config; - if (size) - config->prealloc_buffer_size = size; - - pcm_hardware = devm_kzalloc(&pdev->dev, - sizeof(struct snd_pcm_hardware), GFP_KERNEL); - *pcm_hardware = imx_pcm_hardware; - if (size) - pcm_hardware->buffer_bytes_max = size; - - config->pcm_hardware = pcm_hardware; return devm_snd_dmaengine_pcm_register(&pdev->dev, config, diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c index dac6688540dc..92410f7ca1fa 100644 --- a/sound/soc/fsl/imx-pcm-fiq.c +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -282,7 +282,7 @@ static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) return 0; } -static int ssi_irq = 0; +static int ssi_irq; static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd) { diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c index 1b60958e2080..206b898e554c 100644 --- a/sound/soc/fsl/imx-wm8962.c +++ b/sound/soc/fsl/imx-wm8962.c @@ -33,14 +33,14 @@ struct imx_wm8962_data { struct snd_soc_card card; char codec_dai_name[DAI_NAME_SIZE]; char platform_name[DAI_NAME_SIZE]; - struct clk *codec_clk; unsigned int clk_frequency; }; struct imx_priv { struct platform_device *pdev; + int sample_rate; + snd_pcm_format_t sample_format; }; -static struct imx_priv card_priv; static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), @@ -49,14 +49,14 @@ static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { SND_SOC_DAPM_MIC("DMIC", NULL), }; -static int sample_rate = 44100; -static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; - static int imx_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - sample_rate = params_rate(params); - sample_format = params_format(params); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_priv *priv = snd_soc_card_get_drvdata(rtd->card); + + priv->sample_rate = params_rate(params); + priv->sample_format = params_format(params); return 0; } @@ -71,7 +71,7 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card, { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *codec_dai; - struct imx_priv *priv = &card_priv; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); struct device *dev = &priv->pdev->dev; unsigned int pll_out; @@ -85,10 +85,10 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card, switch (level) { case SND_SOC_BIAS_PREPARE: if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { - if (sample_format == SNDRV_PCM_FORMAT_S24_LE) - pll_out = sample_rate * 384; + if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = priv->sample_rate * 384; else - pll_out = sample_rate * 256; + pll_out = priv->sample_rate * 256; ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK, data->clk_frequency, @@ -140,7 +140,7 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *codec_dai; - struct imx_priv *priv = &card_priv; + struct imx_priv *priv = snd_soc_card_get_drvdata(card); struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); struct device *dev = &priv->pdev->dev; int ret; @@ -160,13 +160,20 @@ static int imx_wm8962_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct device_node *ssi_np, *codec_np; struct platform_device *ssi_pdev; - struct imx_priv *priv = &card_priv; struct i2c_client *codec_dev; struct imx_wm8962_data *data; + struct imx_priv *priv; + struct clk *codec_clk; int int_port, ext_port; int ret; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->pdev = pdev; + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; ret = of_property_read_u32(np, "mux-int-port", &int_port); if (ret) { @@ -231,19 +238,15 @@ static int imx_wm8962_probe(struct platform_device *pdev) goto fail; } - data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); - if (IS_ERR(data->codec_clk)) { - ret = PTR_ERR(data->codec_clk); + codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(codec_clk)) { + ret = PTR_ERR(codec_clk); dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); goto fail; } - data->clk_frequency = clk_get_rate(data->codec_clk); - ret = clk_prepare_enable(data->codec_clk); - if (ret) { - dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); - goto fail; - } + data->clk_frequency = clk_get_rate(codec_clk); + clk_put(codec_clk); data->dai.name = "HiFi"; data->dai.stream_name = "HiFi"; @@ -258,10 +261,10 @@ static int imx_wm8962_probe(struct platform_device *pdev) data->card.dev = &pdev->dev; ret = snd_soc_of_parse_card_name(&data->card, "model"); if (ret) - goto clk_fail; + goto fail; ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); if (ret) - goto clk_fail; + goto fail; data->card.num_links = 1; data->card.owner = THIS_MODULE; data->card.dai_link = &data->dai; @@ -277,16 +280,9 @@ static int imx_wm8962_probe(struct platform_device *pdev) ret = devm_snd_soc_register_card(&pdev->dev, &data->card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); - goto clk_fail; + goto fail; } - of_node_put(ssi_np); - of_node_put(codec_np); - - return 0; - -clk_fail: - clk_disable_unprepare(data->codec_clk); fail: of_node_put(ssi_np); of_node_put(codec_np); @@ -294,17 +290,6 @@ fail: return ret; } -static int imx_wm8962_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); - - if (!IS_ERR(data->codec_clk)) - clk_disable_unprepare(data->codec_clk); - - return 0; -} - static const struct of_device_id imx_wm8962_dt_ids[] = { { .compatible = "fsl,imx-audio-wm8962", }, { /* sentinel */ } @@ -318,7 +303,6 @@ static struct platform_driver imx_wm8962_driver = { .of_match_table = imx_wm8962_dt_ids, }, .probe = imx_wm8962_probe, - .remove = imx_wm8962_remove, }; module_platform_driver(imx_wm8962_driver); diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index ddf49f30b23f..a639b52c16f6 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -174,7 +174,7 @@ static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) /** * mpc8610_hpcd_ops: ASoC machine driver operations */ -static struct snd_soc_ops mpc8610_hpcd_ops = { +static const struct snd_soc_ops mpc8610_hpcd_ops = { .startup = mpc8610_hpcd_startup, }; diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c index 198eeb3f3f7a..d7ec3d20065c 100644 --- a/sound/soc/fsl/mx27vis-aic32x4.c +++ b/sound/soc/fsl/mx27vis-aic32x4.c @@ -73,7 +73,7 @@ static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mx27vis_aic32x4_snd_ops = { +static const struct snd_soc_ops mx27vis_aic32x4_snd_ops = { .hw_params = mx27vis_aic32x4_hw_params, }; diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c index a1f780ecadf5..41c623c55c16 100644 --- a/sound/soc/fsl/p1022_ds.c +++ b/sound/soc/fsl/p1022_ds.c @@ -184,7 +184,7 @@ static int p1022_ds_machine_remove(struct snd_soc_card *card) /** * p1022_ds_ops: ASoC machine driver operations */ -static struct snd_soc_ops p1022_ds_ops = { +static const struct snd_soc_ops p1022_ds_ops = { .startup = p1022_ds_startup, }; diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c index d4d88a8cb9c0..4afbdd610bfa 100644 --- a/sound/soc/fsl/p1022_rdk.c +++ b/sound/soc/fsl/p1022_rdk.c @@ -188,7 +188,7 @@ static int p1022_rdk_machine_remove(struct snd_soc_card *card) /** * p1022_rdk_ops: ASoC machine driver operations */ -static struct snd_soc_ops p1022_rdk_ops = { +static const struct snd_soc_ops p1022_rdk_ops = { .startup = p1022_rdk_startup, }; diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c index ae403c29688f..66fb6c4614d2 100644 --- a/sound/soc/fsl/phycore-ac97.c +++ b/sound/soc/fsl/phycore-ac97.c @@ -23,7 +23,7 @@ static struct snd_soc_card imx_phycore; -static struct snd_soc_ops imx_phycore_hifi_ops = { +static const struct snd_soc_ops imx_phycore_hifi_ops = { }; static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c index b454972dce35..cdaf16367b47 100644 --- a/sound/soc/fsl/wm1133-ev1.c +++ b/sound/soc/fsl/wm1133-ev1.c @@ -139,7 +139,7 @@ static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops wm1133_ev1_ops = { +static const struct snd_soc_ops wm1133_ev1_ops = { .hw_params = wm1133_ev1_hw_params, }; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 85b4f1806514..2c9dedab5184 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -40,9 +40,10 @@ struct simple_card_data { struct snd_soc_dai_link *dai_link; }; -#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) -#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_card(priv) (&(priv)->snd_card) #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) #define DAI "sound-dai" #define CELL "#sound-dai-cells" @@ -323,6 +324,7 @@ static int asoc_simple_card_parse_aux_devs(struct device_node *node, { struct device *dev = simple_priv_to_dev(priv); struct device_node *aux_node; + struct snd_soc_card *card = simple_priv_to_card(priv); int i, n, len; if (!of_find_property(node, PREFIX "aux-devs", &len)) @@ -332,19 +334,19 @@ static int asoc_simple_card_parse_aux_devs(struct device_node *node, if (n <= 0) return -EINVAL; - priv->snd_card.aux_dev = devm_kzalloc(dev, - n * sizeof(*priv->snd_card.aux_dev), GFP_KERNEL); - if (!priv->snd_card.aux_dev) + card->aux_dev = devm_kzalloc(dev, + n * sizeof(*card->aux_dev), GFP_KERNEL); + if (!card->aux_dev) return -ENOMEM; for (i = 0; i < n; i++) { aux_node = of_parse_phandle(node, PREFIX "aux-devs", i); if (!aux_node) return -EINVAL; - priv->snd_card.aux_dev[i].codec_of_node = aux_node; + card->aux_dev[i].codec_of_node = aux_node; } - priv->snd_card.num_aux_devs = n; + card->num_aux_devs = n; return 0; } @@ -352,6 +354,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, struct simple_card_data *priv) { struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); struct device_node *dai_link; int ret; @@ -362,7 +365,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, /* The off-codec widgets */ if (of_property_read_bool(node, PREFIX "widgets")) { - ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card, + ret = snd_soc_of_parse_audio_simple_widgets(card, PREFIX "widgets"); if (ret) goto card_parse_end; @@ -370,7 +373,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, /* DAPM routes */ if (of_property_read_bool(node, PREFIX "routing")) { - ret = snd_soc_of_parse_audio_routing(&priv->snd_card, + ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing"); if (ret) goto card_parse_end; @@ -401,7 +404,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, goto card_parse_end; } - ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(card, PREFIX); if (ret < 0) goto card_parse_end; @@ -418,8 +421,9 @@ static int asoc_simple_card_probe(struct platform_device *pdev) struct simple_card_data *priv; struct snd_soc_dai_link *dai_link; struct simple_dai_props *dai_props; - struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct snd_soc_card *card; int num, ret; /* Get the number of DAI links */ @@ -442,10 +446,11 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->dai_link = dai_link; /* Init snd_soc_card */ - priv->snd_card.owner = THIS_MODULE; - priv->snd_card.dev = dev; - priv->snd_card.dai_link = priv->dai_link; - priv->snd_card.num_links = num; + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; if (np && of_device_is_available(np)) { @@ -474,7 +479,7 @@ static int asoc_simple_card_probe(struct platform_device *pdev) return -EINVAL; } - priv->snd_card.name = (cinfo->card) ? cinfo->card : cinfo->name; + card->name = (cinfo->card) ? cinfo->card : cinfo->name; dai_link->name = cinfo->name; dai_link->stream_name = cinfo->name; dai_link->platform_name = cinfo->platform; @@ -489,13 +494,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) sizeof(priv->dai_props->codec_dai)); } - snd_soc_card_set_drvdata(&priv->snd_card, priv); + snd_soc_card_set_drvdata(card, priv); - ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + ret = devm_snd_soc_register_card(dev, card); if (ret >= 0) return ret; err: - asoc_simple_card_clean_reference(&priv->snd_card); + asoc_simple_card_clean_reference(card); return ret; } diff --git a/sound/soc/generic/simple-scu-card.c b/sound/soc/generic/simple-scu-card.c index 308ff4c11a8d..dcbcab230d1b 100644 --- a/sound/soc/generic/simple-scu-card.c +++ b/sound/soc/generic/simple-scu-card.c @@ -31,9 +31,10 @@ struct simple_card_data { u32 convert_channels; }; -#define simple_priv_to_dev(priv) ((priv)->snd_card.dev) -#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define simple_priv_to_card(priv) (&(priv)->snd_card) #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) #define DAI "sound-dai" #define CELL "#sound-dai-cells" @@ -109,6 +110,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, struct device *dev = simple_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); + struct snd_soc_card *card = simple_priv_to_card(priv); int ret; if (is_fe) { @@ -163,7 +165,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *np, if (ret < 0) return ret; - snd_soc_of_parse_audio_prefix(&priv->snd_card, + snd_soc_of_parse_audio_prefix(card, &priv->codec_conf, dai_link->codec_of_node, PREFIX "prefix"); @@ -201,6 +203,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, { struct device *dev = simple_priv_to_dev(priv); struct device_node *np; + struct snd_soc_card *card = simple_priv_to_card(priv); unsigned int daifmt = 0; bool is_fe; int ret, i; @@ -208,7 +211,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, if (!node) return -EINVAL; - ret = snd_soc_of_parse_audio_routing(&priv->snd_card, PREFIX "routing"); + ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing"); if (ret < 0) return ret; @@ -239,12 +242,12 @@ static int asoc_simple_card_parse_of(struct device_node *node, i++; } - ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + ret = asoc_simple_card_parse_card_name(card, PREFIX); if (ret < 0) return ret; dev_dbg(dev, "New card: %s\n", - priv->snd_card.name ? priv->snd_card.name : ""); + card->name ? card->name : ""); dev_dbg(dev, "convert_rate %d\n", priv->convert_rate); dev_dbg(dev, "convert_channels %d\n", priv->convert_channels); @@ -256,8 +259,9 @@ static int asoc_simple_card_probe(struct platform_device *pdev) struct simple_card_data *priv; struct snd_soc_dai_link *dai_link; struct asoc_simple_dai *dai_props; + struct snd_soc_card *card; struct device *dev = &pdev->dev; - struct device_node *np = pdev->dev.of_node; + struct device_node *np = dev->of_node; int num, ret; /* Allocate the private data */ @@ -276,12 +280,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->dai_link = dai_link; /* Init snd_soc_card */ - priv->snd_card.owner = THIS_MODULE; - priv->snd_card.dev = dev; - priv->snd_card.dai_link = priv->dai_link; - priv->snd_card.num_links = num; - priv->snd_card.codec_conf = &priv->codec_conf; - priv->snd_card.num_configs = 1; + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; + card->codec_conf = &priv->codec_conf; + card->num_configs = 1; ret = asoc_simple_card_parse_of(np, priv); if (ret < 0) { @@ -290,13 +295,13 @@ static int asoc_simple_card_probe(struct platform_device *pdev) goto err; } - snd_soc_card_set_drvdata(&priv->snd_card, priv); + snd_soc_card_set_drvdata(card, priv); - ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + ret = devm_snd_soc_register_card(dev, card); if (ret >= 0) return ret; err: - asoc_simple_card_clean_reference(&priv->snd_card); + asoc_simple_card_clean_reference(card); return ret; } diff --git a/sound/soc/hisilicon/Kconfig b/sound/soc/hisilicon/Kconfig new file mode 100644 index 000000000000..4356d5a1d338 --- /dev/null +++ b/sound/soc/hisilicon/Kconfig @@ -0,0 +1,5 @@ +config SND_I2S_HI6210_I2S + tristate "Hisilicon I2S controller" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Hisilicon I2S diff --git a/sound/soc/hisilicon/Makefile b/sound/soc/hisilicon/Makefile new file mode 100644 index 000000000000..e8095e2af91a --- /dev/null +++ b/sound/soc/hisilicon/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SND_I2S_HI6210_I2S) += hi6210-i2s.o diff --git a/sound/soc/hisilicon/hi6210-i2s.c b/sound/soc/hisilicon/hi6210-i2s.c new file mode 100644 index 000000000000..45163e5202f5 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.c @@ -0,0 +1,618 @@ +/* + * linux/sound/soc/m8m/hi6210_i2s.c - I2S IP driver + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green <andy.green@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver only deals with S2 interface (BT) + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <linux/interrupt.h> +#include <linux/reset.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/mfd/syscon.h> +#include <linux/reset-controller.h> +#include <linux/clk.h> + +#include "hi6210-i2s.h" + +struct hi6210_i2s { + struct device *dev; + struct reset_control *rc; + struct clk *clk[8]; + int clocks; + struct snd_soc_dai_driver dai; + void __iomem *base; + struct regmap *sysctrl; + phys_addr_t base_phys; + struct snd_dmaengine_dai_dma_data dma_data[2]; + int clk_rate; + spinlock_t lock; + int rate; + int format; + u8 bits; + u8 channels; + u8 id; + u8 channel_length; + u8 use; + u32 master:1; + u32 status:1; +}; + +#define SC_PERIPH_CLKEN1 0x210 +#define SC_PERIPH_CLKDIS1 0x214 + +#define SC_PERIPH_CLKEN3 0x230 +#define SC_PERIPH_CLKDIS3 0x234 + +#define SC_PERIPH_CLKEN12 0x270 +#define SC_PERIPH_CLKDIS12 0x274 + +#define SC_PERIPH_RSTEN1 0x310 +#define SC_PERIPH_RSTDIS1 0x314 +#define SC_PERIPH_RSTSTAT1 0x318 + +#define SC_PERIPH_RSTEN2 0x320 +#define SC_PERIPH_RSTDIS2 0x324 +#define SC_PERIPH_RSTSTAT2 0x328 + +#define SOC_PMCTRL_BBPPLLALIAS 0x48 + +enum { + CLK_DACODEC, + CLK_I2S_BASE, +}; + +static inline void hi6210_write_reg(struct hi6210_i2s *i2s, int reg, u32 val) +{ + writel(val, i2s->base + reg); +} + +static inline u32 hi6210_read_reg(struct hi6210_i2s *i2s, int reg) +{ + return readl(i2s->base + reg); +} + +int hi6210_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int ret, n; + u32 val; + + /* deassert reset on ABB */ + regmap_read(i2s->sysctrl, SC_PERIPH_RSTSTAT2, &val); + if (val & BIT(4)) + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS2, BIT(4)); + + for (n = 0; n < i2s->clocks; n++) { + ret = clk_prepare_enable(i2s->clk[n]); + if (ret) { + while (n--) + clk_disable_unprepare(i2s->clk[n]); + return ret; + } + } + + ret = clk_set_rate(i2s->clk[CLK_I2S_BASE], 49152000); + if (ret) { + dev_err(i2s->dev, "%s: setting 49.152MHz base rate failed %d\n", + __func__, ret); + return ret; + } + + /* enable clock before frequency division */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN12, BIT(9)); + + /* enable codec working clock / == "codec bus clock" */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN1, BIT(5)); + + /* deassert reset on codec / interface clock / working clock */ + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS1, BIT(5)); + + /* not interested in i2s irqs */ + val = hi6210_read_reg(i2s, HII2S_CODEC_IRQ_MASK); + val |= 0x3f; + hi6210_write_reg(i2s, HII2S_CODEC_IRQ_MASK, val); + + + /* reset the stereo downlink fifo */ + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val |= (BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val &= ~(BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val &= ~(HII2S_SW_RST_N__ST_DL_WORDLEN_MASK << + HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + val |= (HII2S_BITS_16 << HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + val = hi6210_read_reg(i2s, HII2S_MISC_CFG); + /* mux 11/12 = APB not i2s */ + val &= ~HII2S_MISC_CFG__ST_DL_TEST_SEL; + /* BT R ch 0 = mixer op of DACR ch */ + val &= ~HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + val &= ~HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + + val |= HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + /* BT L ch = 1 = mux 7 = "mixer output of DACL */ + val |= HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + hi6210_write_reg(i2s, HII2S_MISC_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val |= HII2S_SW_RST_N__SW_RST_N; + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + return 0; +} +void hi6210_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int n; + + for (n = 0; n < i2s->clocks; n++) + clk_disable_unprepare(i2s->clk[n]); + + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); +} + +static void hi6210_i2s_txctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + /* enable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + /* disable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static void hi6210_i2s_rxctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static int hi6210_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + /* + * We don't actually set the hardware until the hw_params + * call, but we need to validate the user input here. + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + break; + default: + return -EINVAL; + } + + i2s->format = fmt; + i2s->master = (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; + + return 0; +} + +static int hi6210_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 bits = 0, rate = 0, signed_data = 0, fmt = 0; + u32 val; + struct snd_dmaengine_dai_dma_data *dma_data; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S16_LE: + bits = HII2S_BITS_16; + break; + case SNDRV_PCM_FORMAT_U24_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S24_LE: + bits = HII2S_BITS_24; + break; + default: + dev_err(cpu_dai->dev, "Bad format\n"); + return -EINVAL; + } + + + switch (params_rate(params)) { + case 8000: + rate = HII2S_FS_RATE_8KHZ; + break; + case 16000: + rate = HII2S_FS_RATE_16KHZ; + break; + case 32000: + rate = HII2S_FS_RATE_32KHZ; + break; + case 48000: + rate = HII2S_FS_RATE_48KHZ; + break; + case 96000: + rate = HII2S_FS_RATE_96KHZ; + break; + case 192000: + rate = HII2S_FS_RATE_192KHZ; + break; + default: + dev_err(cpu_dai->dev, "Bad rate: %d\n", params_rate(params)); + return -EINVAL; + } + + if (!(params_channels(params))) { + dev_err(cpu_dai->dev, "Bad channels\n"); + return -EINVAL; + } + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + switch (bits) { + case HII2S_BITS_24: + i2s->bits = 32; + dma_data->addr_width = 3; + break; + default: + i2s->bits = 16; + dma_data->addr_width = 2; + break; + } + i2s->rate = params_rate(params); + i2s->channels = params_channels(params); + i2s->channel_length = i2s->channels * i2s->bits; + + val = hi6210_read_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG); + val &= ~((HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + val |= ((16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + hi6210_write_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_IF_CLK_EN_CFG); + val |= (BIT(19) | BIT(18) | BIT(17) | + HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_R_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_L_EN); + hi6210_write_reg(i2s, HII2S_IF_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG); + val &= ~(HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN); + val |= (HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG); + val &= ~(HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE | + HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_MUX_TOP_MODULE_CFG); + val &= ~(HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_MUX_TOP_MODULE_CFG, val); + + + switch (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = false; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = true; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt MASTER_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + switch (i2s->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fmt = HII2S_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt = HII2S_FORMAT_LEFT_JUST; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = HII2S_FORMAT_RIGHT_JUST; + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt FORMAT_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~(HII2S_I2S_CFG__S2_FUNC_MODE_MASK << + HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT); + val |= fmt << HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_CLK_SEL); + val &= ~(HII2S_CLK_SEL__I2S_BT_FM_SEL | /* BT gets the I2S */ + HII2S_CLK_SEL__EXT_12_288MHZ_SEL); + hi6210_write_reg(i2s, HII2S_CLK_SEL, val); + + dma_data->maxburst = 2; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->addr = i2s->base_phys + HII2S_ST_DL_CHANNEL; + else + dma_data->addr = i2s->base_phys + HII2S_STEREO_UPLINK_CHANNEL; + + switch (i2s->channels) { + case 1: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + } + + /* clear loopback, set signed type and word length */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + val &= ~(HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK << + HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + val &= ~(HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK << + HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT); + val |= signed_data; + val |= (bits << HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + if (!i2s->master) + return 0; + + /* set DAC and related units to correct rate */ + val = hi6210_read_reg(i2s, HII2S_FS_CFG); + val &= ~(HII2S_FS_CFG__FS_S2_MASK << HII2S_FS_CFG__FS_S2_SHIFT); + val &= ~(HII2S_FS_CFG__FS_DACLR_MASK << HII2S_FS_CFG__FS_DACLR_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_R_MASK << + HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_L_MASK << + HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_S2_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_DACLR_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + hi6210_write_reg(i2s, HII2S_FS_CFG, val); + + return 0; +} + +static int hi6210_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 1); + else + hi6210_i2s_txctrl(cpu_dai, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 0); + else + hi6210_i2s_txctrl(cpu_dai, 0); + break; + default: + dev_err(cpu_dai->dev, "uknown cmd\n"); + return -EINVAL; + } + return 0; +} + +static int hi6210_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct hi6210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &i2s->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &i2s->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + + +static struct snd_soc_dai_ops hi6210_i2s_dai_ops = { + .trigger = hi6210_i2s_trigger, + .hw_params = hi6210_i2s_hw_params, + .set_fmt = hi6210_i2s_set_fmt, + .startup = hi6210_i2s_startup, + .shutdown = hi6210_i2s_shutdown, +}; + +struct snd_soc_dai_driver hi6210_i2s_dai_init = { + .probe = hi6210_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .ops = &hi6210_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver hi6210_i2s_i2s_comp = { + .name = "hi6210_i2s-i2s", +}; + +static int hi6210_i2s_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct hi6210_i2s *i2s; + struct resource *res; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->dev = dev; + spin_lock_init(&i2s->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->base_phys = (phys_addr_t)res->start; + i2s->dai = hi6210_i2s_dai_init; + + dev_set_drvdata(&pdev->dev, i2s); + + i2s->sysctrl = syscon_regmap_lookup_by_phandle(node, + "hisilicon,sysctrl-syscon"); + if (IS_ERR(i2s->sysctrl)) + return PTR_ERR(i2s->sysctrl); + + i2s->clk[CLK_DACODEC] = devm_clk_get(&pdev->dev, "dacodec"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_DACODEC])) + return PTR_ERR(i2s->clk[CLK_DACODEC]); + i2s->clocks++; + + i2s->clk[CLK_I2S_BASE] = devm_clk_get(&pdev->dev, "i2s-base"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_I2S_BASE])) + return PTR_ERR(i2s->clk[CLK_I2S_BASE]); + i2s->clocks++; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, &hi6210_i2s_i2s_comp, + &i2s->dai, 1); + return ret; +} + +static const struct of_device_id hi6210_i2s_dt_ids[] = { + { .compatible = "hisilicon,hi6210-i2s" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, hi6210_i2s_dt_ids); + +static struct platform_driver hi6210_i2s_driver = { + .probe = hi6210_i2s_probe, + .driver = { + .name = "hi6210_i2s", + .of_match_table = hi6210_i2s_dt_ids, + }, +}; + +module_platform_driver(hi6210_i2s_driver); + +MODULE_DESCRIPTION("Hisilicon HI6210 I2S driver"); +MODULE_AUTHOR("Andy Green <andy.green@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/hisilicon/hi6210-i2s.h b/sound/soc/hisilicon/hi6210-i2s.h new file mode 100644 index 000000000000..85cecc4939a0 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.h @@ -0,0 +1,276 @@ +/* + * linux/sound/soc/hisilicon/hi6210-i2s.h + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green <andy.green@linaro.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Note at least on 6220, S2 == BT, S1 == Digital FM Radio IF + */ + +#ifndef _HI6210_I2S_H +#define _HI6210_I2S_H + +#define HII2S_SW_RST_N 0 + +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_SHIFT 28 +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_SHIFT 26 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_SHIFT 24 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT 20 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_SHIFT 18 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_SHIFT 16 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_MASK 3 + +#define HII2S_SW_RST_N__SW_RST_N BIT(0) + +enum hi6210_bits { + HII2S_BITS_16, + HII2S_BITS_18, + HII2S_BITS_20, + HII2S_BITS_24, +}; + + +#define HII2S_IF_CLK_EN_CFG 4 + +#define HII2S_IF_CLK_EN_CFG__THIRDMD_UPLINK_EN BIT(25) +#define HII2S_IF_CLK_EN_CFG__THIRDMD_DLINK_EN BIT(24) +#define HII2S_IF_CLK_EN_CFG__S3_IF_CLK_EN BIT(20) +#define HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN BIT(16) +#define HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN BIT(15) +#define HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN BIT(14) +#define HII2S_IF_CLK_EN_CFG__S2_IR_PGA_EN BIT(13) +#define HII2S_IF_CLK_EN_CFG__S2_IL_PGA_EN BIT(12) +#define HII2S_IF_CLK_EN_CFG__S1_IR_PGA_EN BIT(10) +#define HII2S_IF_CLK_EN_CFG__S1_IL_PGA_EN BIT(9) +#define HII2S_IF_CLK_EN_CFG__S1_IF_CLK_EN BIT(8) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_SRC_EN BIT(7) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_EN BIT(6) +#define HII2S_IF_CLK_EN_CFG__ST_DL_R_EN BIT(5) +#define HII2S_IF_CLK_EN_CFG__ST_DL_L_EN BIT(4) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_R_EN BIT(3) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_L_EN BIT(2) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_R_EN BIT(1) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_L_EN BIT(0) + +#define HII2S_DIG_FILTER_CLK_EN_CFG 8 +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN BIT(30) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN BIT(28) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN BIT(25) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN BIT(24) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN BIT(22) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN BIT(20) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN BIT(17) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN BIT(16) + +#define HII2S_FS_CFG 0xc + +#define HII2S_FS_CFG__FS_S2_SHIFT 28 +#define HII2S_FS_CFG__FS_S2_MASK 7 +#define HII2S_FS_CFG__FS_S1_SHIFT 24 +#define HII2S_FS_CFG__FS_S1_MASK 7 +#define HII2S_FS_CFG__FS_ADCLR_SHIFT 20 +#define HII2S_FS_CFG__FS_ADCLR_MASK 7 +#define HII2S_FS_CFG__FS_DACLR_SHIFT 16 +#define HII2S_FS_CFG__FS_DACLR_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_R_SHIFT 8 +#define HII2S_FS_CFG__FS_ST_DL_R_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_L_SHIFT 4 +#define HII2S_FS_CFG__FS_ST_DL_L_MASK 7 +#define HII2S_FS_CFG__FS_VOICE_DLINK_SHIFT 0 +#define HII2S_FS_CFG__FS_VOICE_DLINK_MASK 7 + +enum hi6210_i2s_rates { + HII2S_FS_RATE_8KHZ = 0, + HII2S_FS_RATE_16KHZ = 1, + HII2S_FS_RATE_32KHZ = 2, + HII2S_FS_RATE_48KHZ = 4, + HII2S_FS_RATE_96KHZ = 5, + HII2S_FS_RATE_192KHZ = 6, +}; + +#define HII2S_I2S_CFG 0x10 + +#define HII2S_I2S_CFG__S2_IF_TX_EN BIT(31) +#define HII2S_I2S_CFG__S2_IF_RX_EN BIT(30) +#define HII2S_I2S_CFG__S2_FRAME_MODE BIT(29) +#define HII2S_I2S_CFG__S2_MST_SLV BIT(28) +#define HII2S_I2S_CFG__S2_LRCK_MODE BIT(27) +#define HII2S_I2S_CFG__S2_CHNNL_MODE BIT(26) +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT 24 +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT 22 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S2_TX_CLK_SEL BIT(21) +#define HII2S_I2S_CFG__S2_RX_CLK_SEL BIT(20) +#define HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT BIT(19) +#define HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT 16 +#define HII2S_I2S_CFG__S2_FUNC_MODE_MASK 7 +#define HII2S_I2S_CFG__S1_IF_TX_EN BIT(15) +#define HII2S_I2S_CFG__S1_IF_RX_EN BIT(14) +#define HII2S_I2S_CFG__S1_FRAME_MODE BIT(13) +#define HII2S_I2S_CFG__S1_MST_SLV BIT(12) +#define HII2S_I2S_CFG__S1_LRCK_MODE BIT(11) +#define HII2S_I2S_CFG__S1_CHNNL_MODE BIT(10) +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_SHIFT 8 +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_SHIFT 6 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S1_TX_CLK_SEL BIT(5) +#define HII2S_I2S_CFG__S1_RX_CLK_SEL BIT(4) +#define HII2S_I2S_CFG__S1_CODEC_DATA_FORMAT BIT(3) +#define HII2S_I2S_CFG__S1_FUNC_MODE_SHIFT 0 +#define HII2S_I2S_CFG__S1_FUNC_MODE_MASK 7 + +enum hi6210_i2s_formats { + HII2S_FORMAT_I2S, + HII2S_FORMAT_PCM_STD, + HII2S_FORMAT_PCM_USER, + HII2S_FORMAT_LEFT_JUST, + HII2S_FORMAT_RIGHT_JUST, +}; + +#define HII2S_DIG_FILTER_MODULE_CFG 0x14 + +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_SHIFT 28 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN4_MUTE BIT(27) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN3_MUTE BIT(26) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE BIT(25) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN1_MUTE BIT(24) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_SHIFT 20 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN4_MUTE BIT(19) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN3_MUTE BIT(18) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE BIT(17) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN1_MUTE BIT(16) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACR_SDM_DITHER BIT(9) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACL_SDM_DITHER BIT(8) +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_SHIFT 4 +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_MASK 7 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_SHIFT 0 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_MASK 7 + +enum hi6210_gains { + HII2S_GAIN_100PC, + HII2S_GAIN_50PC, + HII2S_GAIN_25PC, +}; + +#define HII2S_MUX_TOP_MODULE_CFG 0x18 + +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_SHIFT 14 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE BIT(13) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE BIT(12) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_SHIFT 10 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE BIT(9) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE BIT(8) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_RDY BIT(6) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_SHIFT 4 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_RDY BIT(3) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_SHIFT 0 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_MASK 7 + +enum hi6210_s2_src_mode { + HII2S_S2_SRC_MODE_3, + HII2S_S2_SRC_MODE_12, + HII2S_S2_SRC_MODE_6, + HII2S_S2_SRC_MODE_2, +}; + +enum hi6210_voice_dlink_src_mode { + HII2S_VOICE_DL_SRC_MODE_12 = 1, + HII2S_VOICE_DL_SRC_MODE_6, + HII2S_VOICE_DL_SRC_MODE_2, + HII2S_VOICE_DL_SRC_MODE_3, +}; + +#define HII2S_ADC_PGA_CFG 0x1c +#define HII2S_S1_INPUT_PGA_CFG 0x20 +#define HII2S_S2_INPUT_PGA_CFG 0x24 +#define HII2S_ST_DL_PGA_CFG 0x28 +#define HII2S_VOICE_SIDETONE_DLINK_PGA_CFG 0x2c +#define HII2S_APB_AFIFO_CFG_1 0x30 +#define HII2S_APB_AFIFO_CFG_2 0x34 +#define HII2S_ST_DL_FIFO_TH_CFG 0x38 + +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT 24 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT 16 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT 8 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT 0 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK 0x1f + +#define HII2S_STEREO_UPLINK_FIFO_TH_CFG 0x3c +#define HII2S_VOICE_UPLINK_FIFO_TH_CFG 0x40 +#define HII2S_CODEC_IRQ_MASK 0x44 +#define HII2S_CODEC_IRQ 0x48 +#define HII2S_DACL_AGC_CFG_1 0x4c +#define HII2S_DACL_AGC_CFG_2 0x50 +#define HII2S_DACR_AGC_CFG_1 0x54 +#define HII2S_DACR_AGC_CFG_2 0x58 +#define HII2S_DMIC_SIF_CFG 0x5c +#define HII2S_MISC_CFG 0x60 + +#define HII2S_MISC_CFG__THIRDMD_DLINK_TEST_SEL BIT(17) +#define HII2S_MISC_CFG__THIRDMD_DLINK_DIN_SEL BIT(16) +#define HII2S_MISC_CFG__S3_DOUT_RIGHT_SEL BIT(14) +#define HII2S_MISC_CFG__S3_DOUT_LEFT_SEL BIT(13) +#define HII2S_MISC_CFG__S3_DIN_TEST_SEL BIT(12) +#define HII2S_MISC_CFG__VOICE_DLINK_SRC_UP_DOUT_VLD_SEL BIT(8) +#define HII2S_MISC_CFG__VOICE_DLINK_TEST_SEL BIT(7) +#define HII2S_MISC_CFG__VOICE_DLINK_DIN_SEL BIT(6) +#define HII2S_MISC_CFG__ST_DL_TEST_SEL BIT(4) +#define HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL BIT(3) +#define HII2S_MISC_CFG__S2_DOUT_TEST_SEL BIT(2) +#define HII2S_MISC_CFG__S1_DOUT_TEST_SEL BIT(1) +#define HII2S_MISC_CFG__S2_DOUT_LEFT_SEL BIT(0) + +#define HII2S_S2_SRC_CFG 0x64 +#define HII2S_MEM_CFG 0x68 +#define HII2S_THIRDMD_PCM_PGA_CFG 0x6c +#define HII2S_THIRD_MODEM_FIFO_TH 0x70 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_INC_CNT 0x74 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_DEC_CNT 0x78 +#define HII2S_S3_ANTI_FREQ_JITTER_RX_INC_CNT 0x7c +#define HII2S_S3_ANTI_FREQ_JITTER_RX_DEC_CNT 0x80 +#define HII2S_ANTI_FREQ_JITTER_EN 0x84 +#define HII2S_CLK_SEL 0x88 + +/* 0 = BT owns the i2s */ +#define HII2S_CLK_SEL__I2S_BT_FM_SEL BIT(0) +/* 0 = internal source, 1 = ext */ +#define HII2S_CLK_SEL__EXT_12_288MHZ_SEL BIT(1) + + +#define HII2S_THIRDMD_DLINK_CHANNEL 0xe8 +#define HII2S_THIRDMD_ULINK_CHANNEL 0xec +#define HII2S_VOICE_DLINK_CHANNEL 0xf0 + +/* shovel data in here for playback */ +#define HII2S_ST_DL_CHANNEL 0xf4 +#define HII2S_STEREO_UPLINK_CHANNEL 0xf8 +#define HII2S_VOICE_UPLINK_CHANNEL 0xfc + +#endif/* _HI6210_I2S_H */ diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index 526855ad479e..67968ef3bbda 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -202,6 +202,30 @@ config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH platforms with MAX98090 audio codec it also can support TI jack chip as aux device. If unsure select "N". +config SND_SOC_INTEL_BYT_CHT_DA7213_MACH + tristate "ASoC Audio driver for Intel Baytrail & Cherrytrail with DA7212/7213 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_DA7213 + select SND_SST_ATOM_HIFI2_PLATFORM + select SND_SST_IPC_ACPI + select SND_SOC_INTEL_SST_MATCH if ACPI + help + This adds support for ASoC machine driver for Intel(R) Baytrail & CherryTrail + platforms with DA7212/7213 audio codec. + If unsure select "N". + +config SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH + tristate "ASoC Audio driver for Intel Baytrail & Cherrytrail platform with no codec (MinnowBoard MAX, Up)" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SST_ATOM_HIFI2_PLATFORM + select SND_SST_IPC_ACPI + select SND_SOC_INTEL_SST_MATCH if ACPI + help + This adds support for ASoC machine driver for the MinnowBoard Max or + Up boards and provides access to I2S signals on the Low-Speed + connector + If unsure select "N". + config SND_SOC_INTEL_SKYLAKE tristate select SND_HDA_EXT_CORE diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 747c0f393d2d..dd250b8b26f2 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -420,7 +420,21 @@ static const struct dmi_system_id byt_table[] = { .callback = byt_thinkpad10_quirk_cb, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "20C3001VHH"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), }, }, { } @@ -480,12 +494,23 @@ static struct sst_acpi_mach sst_acpi_bytcr[] = { &byt_rvp_platform_data }, {"10EC5651", "bytcr_rt5651", "intel/fw_sst_0f28.bin", "bytcr_rt5651", NULL, &byt_rvp_platform_data }, + {"DLGS7212", "bytcht_da7213", "intel/fw_sst_0f28.bin", "bytcht_da7213", NULL, + &byt_rvp_platform_data }, + {"DLGS7213", "bytcht_da7213", "intel/fw_sst_0f28.bin", "bytcht_da7213", NULL, + &byt_rvp_platform_data }, /* some Baytrail platforms rely on RT5645, use CHT machine driver */ {"10EC5645", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, &byt_rvp_platform_data }, {"10EC5648", "cht-bsw-rt5645", "intel/fw_sst_0f28.bin", "cht-bsw", NULL, &byt_rvp_platform_data }, - +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + {"80860F28", "bytcht_nocodec", "intel/fw_sst_0f28.bin", "bytcht_nocodec", NULL, + &byt_rvp_platform_data }, +#endif {}, }; @@ -504,6 +529,10 @@ static struct sst_acpi_mach sst_acpi_chv[] = { {"193C9890", "cht-bsw-max98090", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, &chv_platform_data }, + {"DLGS7212", "bytcht_da7213", "intel/fw_sst_22a8.bin", "bytcht_da7213", NULL, + &chv_platform_data }, + {"DLGS7213", "bytcht_da7213", "intel/fw_sst_22a8.bin", "bytcht_da7213", NULL, + &chv_platform_data }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ {"10EC5640", "bytcr_rt5640", "intel/fw_sst_22a8.bin", "bytcr_rt5640", cht_quirk, &chv_platform_data }, @@ -512,6 +541,14 @@ static struct sst_acpi_mach sst_acpi_chv[] = { /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */ {"10EC5651", "bytcr_rt5651", "intel/fw_sst_22a8.bin", "bytcr_rt5651", NULL, &chv_platform_data }, +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + {"808622A8", "bytcht_nocodec", "intel/fw_sst_22a8.bin", "bytcht_nocodec", NULL, + &chv_platform_data }, +#endif {}, }; diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c index 14c2d9d18180..20b01e02ed8f 100644 --- a/sound/soc/intel/atom/sst/sst_ipc.c +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -236,7 +236,9 @@ static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, retval = init->result; goto ret; } - dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n", + if (memcmp(&sst_drv_ctx->fw_version, &init->fw_version, + sizeof(init->fw_version))) + dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n", init->fw_version.type, init->fw_version.major, init->fw_version.minor, init->fw_version.build); dev_dbg(sst_drv_ctx->dev, "Build date %s Time %s\n", diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 5639f10774e6..56896e09445d 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -10,6 +10,8 @@ snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o +snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o +snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o snd-soc-skl_rt286-objs := skl_rt286.o snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o @@ -26,6 +28,8 @@ obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH) += snd-soc-sst-byt-cht-da7213.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) += snd-soc-sst-byt-cht-nocodec.o obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c index 53c6b4cbb1e1..14d9693c1641 100644 --- a/sound/soc/intel/boards/bdw-rt5677.c +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -193,13 +193,12 @@ static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) RT5677_CLK_SEL_I2S1_ASRC); /* Request rt5677 GPIO for headphone amp control */ - bdw_rt5677->gpio_hp_en = devm_gpiod_get_index(codec->dev, - "headphone-enable", 0, 0); + bdw_rt5677->gpio_hp_en = devm_gpiod_get(codec->dev, "headphone-enable", + GPIOD_OUT_LOW); if (IS_ERR(bdw_rt5677->gpio_hp_en)) { dev_err(codec->dev, "Can't find HP_AMP_SHDN_L gpio\n"); return PTR_ERR(bdw_rt5677->gpio_hp_en); } - gpiod_direction_output(bdw_rt5677->gpio_hp_en, 0); /* Create and initialize headphone jack */ if (!snd_soc_card_jack_new(rtd->card, "Headphone Jack", diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c index faf865bb1765..6dcbbcefc25b 100644 --- a/sound/soc/intel/boards/broadwell.c +++ b/sound/soc/intel/boards/broadwell.c @@ -269,9 +269,6 @@ static struct snd_soc_card broadwell_rt286 = { static int broadwell_audio_probe(struct platform_device *pdev) { broadwell_rt286.dev = &pdev->dev; - - snd_soc_set_dmi_name(&broadwell_rt286, NULL); - return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286); } diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c index 2cda06cde4d1..3a8c4d954a91 100644 --- a/sound/soc/intel/boards/bxt_da7219_max98357a.c +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -55,6 +55,54 @@ enum { BXT_DPCM_AUDIO_HDMI3_PB, }; +static inline struct snd_soc_dai *bxt_get_codec_dai(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (!strncmp(rtd->codec_dai->name, BXT_DIALOG_CODEC_DAI, + strlen(BXT_DIALOG_CODEC_DAI))) + return rtd->codec_dai; + } + + return NULL; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + + codec_dai = bxt_get_codec_dai(card); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if(SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); + if (ret) + dev_err(card->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + static const struct snd_kcontrol_new broxton_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Headset Mic"), @@ -69,6 +117,8 @@ static const struct snd_soc_dapm_widget broxton_widgets[] = { SND_SOC_DAPM_SPK("HDMI1", NULL), SND_SOC_DAPM_SPK("HDMI2", NULL), SND_SOC_DAPM_SPK("HDMI3", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU), }; static const struct snd_soc_dapm_route broxton_map[] = { @@ -109,6 +159,9 @@ static const struct snd_soc_dapm_route broxton_map[] = { /* DMIC */ {"dmic01_hifi", NULL, "DMIC01 Rx"}, {"DMIC01 Rx", NULL, "DMIC AIF"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, }; static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, @@ -243,49 +296,6 @@ static const struct snd_soc_ops broxton_da7219_fe_ops = { .startup = bxt_fe_startup, }; -static int broxton_da7219_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, - DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); - if (ret < 0) - dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); - - ret = snd_soc_dai_set_pll(codec_dai, 0, - DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); - if (ret < 0) { - dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); - return -EIO; - } - - return ret; -} - -static int broxton_da7219_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - ret = snd_soc_dai_set_pll(codec_dai, 0, - DA7219_SYSCLK_MCLK, 0, 0); - if (ret < 0) { - dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); - return -EIO; - } - - return ret; -} - -static const struct snd_soc_ops broxton_da7219_ops = { - .hw_params = broxton_da7219_hw_params, - .hw_free = broxton_da7219_hw_free, -}; - static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -467,7 +477,6 @@ static struct snd_soc_dai_link broxton_dais[] = { SND_SOC_DAIFMT_CBS_CFS, .ignore_pmdown_time = 1, .be_hw_params_fixup = broxton_ssp_fixup, - .ops = &broxton_da7219_ops, .dpcm_playback = 1, .dpcm_capture = 1, }, diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index 176c080a9818..1a68d043c803 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -274,12 +274,15 @@ static int bxt_fe_startup(struct snd_pcm_substream *substream) * on this platform for PCM device we support: * 48Khz * stereo + * 16-bit audio */ runtime->hw.channels_max = 2; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &constraints_channels); + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c new file mode 100644 index 000000000000..18873e23f404 --- /dev/null +++ b/sound/soc/intel/boards/bytcht_da7213.c @@ -0,0 +1,283 @@ +/* + * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and + * Cherrytrail-based platforms, with Dialog DA7213 codec + * + * Copyright (C) 2017 Intel Corporation + * Author: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/module.h> +#include <linux/acpi.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/platform_sst_audio.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../codecs/da7213.h" +#include "../atom/sst-atom-controls.h" +#include "../common/sst-acpi.h" + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Aux In"), +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"AUXL", NULL, "Aux In"}, + {"AUXR", NULL, "Aux In"}, + + /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ + {"MIC1", NULL, "Headset Mic"}, + {"MIC2", NULL, "Mic"}, + + /* SOC-codec link */ + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "Capture"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + int ret; + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static int aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, + 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int aif1_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static const struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static const struct snd_soc_ops ssp2_ops = { + .hw_params = aif1_hw_params, + .hw_free = aif1_hw_free, + +}; + +static struct snd_soc_dai_link dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "da7213-hifi", + .codec_name = "i2c-DLGS7213:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_da7213_card = { + .name = "bytcht-da7213", + .owner = THIS_MODULE, + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static char codec_name[16]; /* i2c-<HID>:00 with HID being 8 chars */ + +static int bytcht_da7213_probe(struct platform_device *pdev) +{ + int ret_val = 0; + int i; + struct snd_soc_card *card; + struct sst_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; + + mach = (&pdev->dev)->platform_data; + card = &bytcht_da7213_card; + card->dev = &pdev->dev; + + /* fix index of codec dai */ + dai_index = MERR_DPCM_COMPR + 1; + for (i = 0; i < ARRAY_SIZE(dailink); i++) { + if (!strcmp(dailink[i].codec_name, "i2c-DLGS7213:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = sst_acpi_find_name_from_hid(mach->id); + if (i2c_name != NULL) { + snprintf(codec_name, sizeof(codec_name), + "%s%s", "i2c-", i2c_name); + dailink[dai_index].codec_name = codec_name; + } + + ret_val = devm_snd_soc_register_card(&pdev->dev, card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, card); + return ret_val; +} + +static struct platform_driver bytcht_da7213_driver = { + .driver = { + .name = "bytcht_da7213", + }, + .probe = bytcht_da7213_probe, +}; +module_platform_driver(bytcht_da7213_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_da7213"); diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c new file mode 100644 index 000000000000..89853eeaaf9d --- /dev/null +++ b/sound/soc/intel/boards/bytcht_nocodec.c @@ -0,0 +1,208 @@ +/* + * bytcht_nocodec.c - ASoc Machine driver for MinnowBoard Max and Up + * to make I2S signals observable on the Low-Speed connector. Audio codec + * is not managed by ASoC/DAPM + * + * Copyright (C) 2015-2017 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../atom/sst-atom-controls.h" + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"ssp2 Rx", NULL, "Mic"}, + {"Speaker", NULL, "ssp2 Tx"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static unsigned int rates_48000[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static struct snd_soc_dai_link dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-LowSpeed Connector", + .id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .ignore_suspend = 1, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_nocodec_card = { + .name = "bytcht-nocodec", + .owner = THIS_MODULE, + .dai_link = dais, + .num_links = ARRAY_SIZE(dais), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .fully_routed = true, +}; + +static int snd_bytcht_nocodec_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + bytcht_nocodec_card.dev = &pdev->dev; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &bytcht_nocodec_card); + + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", + ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &bytcht_nocodec_card); + return ret_val; +} + +static struct platform_driver snd_bytcht_nocodec_mc_driver = { + .driver = { + .name = "bytcht_nocodec", + }, + .probe = snd_bytcht_nocodec_mc_probe, +}; +module_platform_driver(snd_bytcht_nocodec_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Nocodec Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_nocodec"); diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 9e2a3404a836..4a76b099a508 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -19,6 +19,7 @@ #include <linux/init.h> #include <linux/module.h> +#include <linux/moduleparam.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/device.h> @@ -56,35 +57,88 @@ enum { struct byt_rt5640_private { struct clk *mclk; }; +static bool is_bytcr; static unsigned long byt_rt5640_quirk = BYT_RT5640_MCLK_EN; +static unsigned int quirk_override; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static void log_quirks(struct device *dev) { - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_DMIC1_MAP) - dev_info(dev, "quirk DMIC1_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_DMIC2_MAP) - dev_info(dev, "quirk DMIC2_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_IN1_MAP) - dev_info(dev, "quirk IN1_MAP enabled"); - if (BYT_RT5640_MAP(byt_rt5640_quirk) == BYT_RT5640_IN3_MAP) - dev_info(dev, "quirk IN3_MAP enabled"); - if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) - dev_info(dev, "quirk DMIC enabled"); + int map; + bool has_dmic = false; + bool has_mclk = false; + bool has_ssp0 = false; + bool has_ssp0_aif1 = false; + bool has_ssp0_aif2 = false; + bool has_ssp2_aif2 = false; + + map = BYT_RT5640_MAP(byt_rt5640_quirk); + switch (map) { + case BYT_RT5640_DMIC1_MAP: + dev_info(dev, "quirk DMIC1_MAP enabled\n"); + has_dmic = true; + break; + case BYT_RT5640_DMIC2_MAP: + dev_info(dev, "quirk DMIC2_MAP enabled\n"); + has_dmic = true; + break; + case BYT_RT5640_IN1_MAP: + dev_info(dev, "quirk IN1_MAP enabled\n"); + break; + case BYT_RT5640_IN3_MAP: + dev_info(dev, "quirk IN3_MAP enabled\n"); + break; + default: + dev_err(dev, "quirk map 0x%x is not supported, microphone input will not work\n", map); + break; + } + if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { + if (has_dmic) + dev_info(dev, "quirk DMIC enabled\n"); + else + dev_err(dev, "quirk DMIC enabled but no DMIC input set, will be ignored\n"); + } if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) - dev_info(dev, "quirk MONO_SPEAKER enabled"); - if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) - dev_info(dev, "quirk DIFF_MIC enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) - dev_info(dev, "quirk SSP2_AIF2 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) - dev_info(dev, "quirk SSP0_AIF1 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) - dev_info(dev, "quirk SSP0_AIF2 enabled"); - if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) - dev_info(dev, "quirk MCLK_EN enabled"); - if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) - dev_info(dev, "quirk MCLK_25MHZ enabled"); + dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) { + if (!has_dmic) + dev_info(dev, "quirk DIFF_MIC enabled\n"); + else + dev_info(dev, "quirk DIFF_MIC enabled but DMIC input selected, will be ignored\n"); + } + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) { + dev_info(dev, "quirk SSP0_AIF1 enabled\n"); + has_ssp0 = true; + has_ssp0_aif1 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) { + dev_info(dev, "quirk SSP0_AIF2 enabled\n"); + has_ssp0 = true; + has_ssp0_aif2 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) { + dev_info(dev, "quirk SSP2_AIF2 enabled\n"); + has_ssp2_aif2 = true; + } + if (is_bytcr && !has_ssp0) + dev_err(dev, "Invalid routing, bytcr detected but no SSP0-based quirk, audio cannot work with SSP2 on bytcr\n"); + if (has_ssp0_aif1 && has_ssp0_aif2) + dev_err(dev, "Invalid routing, SSP0 cannot be connected to both AIF1 and AIF2\n"); + if (has_ssp0 && has_ssp2_aif2) + dev_err(dev, "Invalid routing, cannot have both SSP0 and SSP2 connected to codec\n"); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + dev_info(dev, "quirk MCLK_EN enabled\n"); + has_mclk = true; + } + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) { + if (has_mclk) + dev_info(dev, "quirk MCLK_25MHZ enabled\n"); + else + dev_err(dev, "quirk MCLK_25MHZ enabled but quirk MCLK not selected, will be ignored\n"); + } } @@ -128,7 +182,7 @@ static int platform_clock_control(struct snd_soc_dapm_widget *w, ret = clk_prepare_enable(priv->mclk); if (ret < 0) { dev_err(card->dev, - "could not configure MCLK state"); + "could not configure MCLK state\n"); return ret; } } @@ -710,8 +764,8 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) int i; int dai_index; struct byt_rt5640_private *priv; - bool is_bytcr = false; + is_bytcr = false; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_ATOMIC); if (!priv) return -ENOMEM; @@ -806,6 +860,11 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) /* check quirks before creating card */ dmi_check_system(byt_rt5640_quirk_table); + if (quirk_override) { + dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", + (unsigned int)byt_rt5640_quirk, quirk_override); + byt_rt5640_quirk = quirk_override; + } log_quirks(&pdev->dev); if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) || diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c index a3459d1682a6..d33bdaf92c57 100644 --- a/sound/soc/intel/haswell/sst-haswell-ipc.c +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -2000,10 +2000,8 @@ int sst_hsw_module_set_param(struct sst_hsw *hsw, u32 param_size, char *param) { int ret; - unsigned char *data = NULL; u32 header = 0; u32 payload_size = 0, transfer_parameter_size = 0; - dma_addr_t dma_addr = 0; struct sst_hsw_transfer_parameter *parameter; struct device *dev = hsw->dev; @@ -2047,10 +2045,6 @@ int sst_hsw_module_set_param(struct sst_hsw *hsw, kfree(parameter); - if (data) - dma_free_coherent(hsw->dsp->dma_dev, - param_size, (void *)data, dma_addr); - return ret; } diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c index 15a063a403cc..f5e7dbb1ba39 100644 --- a/sound/soc/intel/skylake/bxt-sst.c +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -25,7 +25,8 @@ #include "skl-sst-ipc.h" #define BXT_BASEFW_TIMEOUT 3000 -#define BXT_INIT_TIMEOUT 500 +#define BXT_INIT_TIMEOUT 300 +#define BXT_ROM_INIT_TIMEOUT 70 #define BXT_IPC_PURGE_FW 0x01004000 #define BXT_ROM_INIT 0x5 @@ -45,6 +46,8 @@ /* Delay before scheduling D0i3 entry */ #define BXT_D0I3_DELAY 5000 +#define BXT_FW_ROM_INIT_RETRY 3 + static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) { return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); @@ -55,29 +58,15 @@ bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) { struct snd_dma_buffer dmab; struct skl_sst *skl = ctx->thread_context; - const struct firmware *fw = NULL; struct firmware stripped_fw; int ret = 0, i, dma_id, stream_tag; /* library indices start from 1 to N. 0 represents base FW */ for (i = 1; i < lib_count; i++) { - ret = request_firmware(&fw, linfo[i].name, ctx->dev); - if (ret < 0) { - dev_err(ctx->dev, "Request lib %s failed:%d\n", - linfo[i].name, ret); - return ret; - } - - if (skl->is_first_boot) { - ret = snd_skl_parse_uuids(ctx, fw, + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, BXT_ADSP_FW_BIN_HDR_OFFSET, i); - if (ret < 0) - goto load_library_failed; - } - - stripped_fw.data = fw->data; - stripped_fw.size = fw->size; - skl_dsp_strip_extended_manifest(&stripped_fw); + if (ret < 0) + goto load_library_failed; stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, stripped_fw.size, &dmab); @@ -92,21 +81,19 @@ bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) memcpy(dmab.area, stripped_fw.data, stripped_fw.size); ctx->dsp_ops.trigger(ctx->dev, true, stream_tag); - ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i); + ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i, true); if (ret < 0) dev_err(ctx->dev, "IPC Load Lib for %s fail: %d\n", linfo[i].name, ret); ctx->dsp_ops.trigger(ctx->dev, false, stream_tag); ctx->dsp_ops.cleanup(ctx->dev, &dmab, stream_tag); - release_firmware(fw); - fw = NULL; } return ret; load_library_failed: - release_firmware(fw); + skl_release_library(linfo, lib_count); return ret; } @@ -156,7 +143,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, SKL_ADSP_REG_HIPCIE_DONE, BXT_INIT_TIMEOUT, "HIPCIE Done"); if (ret < 0) { - dev_err(ctx->dev, "Timout for Purge Request%d\n", ret); + dev_err(ctx->dev, "Timeout for Purge Request%d\n", ret); goto base_fw_load_failed; } @@ -173,7 +160,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, /* Step 7: Wait for ROM init */ ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK, - SKL_FW_INIT, BXT_INIT_TIMEOUT, "ROM Load"); + SKL_FW_INIT, BXT_ROM_INIT_TIMEOUT, "ROM Load"); if (ret < 0) { dev_err(ctx->dev, "Timeout for ROM init, ret:%d\n", ret); goto base_fw_load_failed; @@ -206,18 +193,16 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) { struct firmware stripped_fw; struct skl_sst *skl = ctx->thread_context; - int ret; + int ret, i; - ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); - if (ret < 0) { - dev_err(ctx->dev, "Request firmware failed %d\n", ret); - goto sst_load_base_firmware_failed; + if (ctx->fw == NULL) { + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "Request firmware failed %d\n", ret); + return ret; + } } - /* check for extended manifest */ - if (ctx->fw == NULL) - goto sst_load_base_firmware_failed; - /* prase uuids on first boot */ if (skl->is_first_boot) { ret = snd_skl_parse_uuids(ctx, ctx->fw, BXT_ADSP_FW_BIN_HDR_OFFSET, 0); @@ -229,18 +214,20 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) stripped_fw.size = ctx->fw->size; skl_dsp_strip_extended_manifest(&stripped_fw); - ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); - /* Retry Enabling core and ROM load. Retry seemed to help */ - if (ret < 0) { + + for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) { ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); - if (ret < 0) { - dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + if (ret == 0) + break; + } + + if (ret < 0) { + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); - goto sst_load_base_firmware_failed; - } + dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); + goto sst_load_base_firmware_failed; } ret = sst_transfer_fw_host_dma(ctx); @@ -265,8 +252,11 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) } } + return ret; + sst_load_base_firmware_failed: release_firmware(ctx->fw); + ctx->fw = NULL; return ret; } @@ -428,6 +418,7 @@ static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) return ret; } } + skl->cores.state[core_id] = SKL_DSP_RUNNING; return ret; } @@ -514,11 +505,22 @@ static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, BXT_BASE_FW_MODULE_ID, &dx); - if (ret < 0) + if (ret < 0) { dev_err(ctx->dev, "Failed to set DSP to D3:core id = %d;Continue reset\n", core_id); + /* + * In case of D3 failure, re-download the firmware, so set + * fw_loaded to false. + */ + skl->fw_loaded = false; + } + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); + } ret = skl_dsp_disable_core(ctx, core_mask); if (ret < 0) { dev_err(ctx->dev, "Failed to disable core %d\n", ret); @@ -560,23 +562,14 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, struct sst_dsp *sst; int ret; - skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); - if (skl == NULL) - return -ENOMEM; - - skl->dev = dev; - skl_dev.thread_context = skl; - INIT_LIST_HEAD(&skl->uuid_list); - - skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); - if (!skl->dsp) { - dev_err(skl->dev, "skl_dsp_ctx_init failed\n"); - return -ENODEV; + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; } + skl = *dsp; sst = skl->dsp; - sst->fw_name = fw_name; - sst->dsp_ops = dsp_ops; sst->fw_ops = bxt_fw_ops; sst->addr.lpe = mmio_base; sst->addr.shim = mmio_base; @@ -584,24 +577,15 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, sst_dsp_mailbox_init(sst, (BXT_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), SKL_ADSP_W0_UP_SZ, BXT_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); - INIT_LIST_HEAD(&sst->module_list); - ret = skl_ipc_init(dev, skl); - if (ret) - return ret; - /* set the D0i3 check */ skl->ipc.ops.check_dsp_lp_on = skl_ipc_check_D0i0; skl->cores.count = 2; skl->boot_complete = false; init_waitqueue_head(&skl->boot_wait); - skl->is_first_boot = true; INIT_DELAYED_WORK(&skl->d0i3.work, bxt_set_dsp_D0i3); skl->d0i3.state = SKL_DSP_D0I3_NONE; - if (dsp) - *dsp = skl; - return 0; } EXPORT_SYMBOL_GPL(bxt_sst_dsp_init); @@ -635,6 +619,10 @@ EXPORT_SYMBOL_GPL(bxt_sst_init_fw); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { + + skl_release_library(ctx->lib_info, ctx->lib_count); + if (ctx->dsp->fw) + release_firmware(ctx->dsp->fw); skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index e66870474f10..ab1adc0c9cc3 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -58,7 +58,7 @@ static int skl_free_dma_buf(struct device *dev, struct snd_dma_buffer *dmab) #define NOTIFICATION_MASK 0xf /* disable notfication for underruns/overruns from firmware module */ -static void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable) +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable) { struct notification_mask mask; struct skl_ipc_large_config_msg msg = {0}; @@ -209,7 +209,7 @@ static const struct skl_dsp_ops dsp_ops[] = { { .id = 0x9d71, .loader_ops = skl_get_loader_ops, - .init = skl_sst_dsp_init, + .init = kbl_sst_dsp_init, .init_fw = skl_sst_init_fw, .cleanup = skl_sst_dsp_cleanup }, @@ -274,6 +274,7 @@ int skl_init_dsp(struct skl *skl) if (ret < 0) return ret; + skl->skl_sst->dsp_ops = ops; dev_dbg(bus->dev, "dsp registration status=%d\n", ret); return ret; @@ -284,16 +285,11 @@ int skl_free_dsp(struct skl *skl) struct hdac_ext_bus *ebus = &skl->ebus; struct hdac_bus *bus = ebus_to_hbus(ebus); struct skl_sst *ctx = skl->skl_sst; - const struct skl_dsp_ops *ops; /* disable ppcap interrupt */ snd_hdac_ext_bus_ppcap_int_enable(&skl->ebus, false); - ops = skl_get_dsp_ops(skl->pci->device); - if (!ops) - return -EIO; - - ops->cleanup(bus->dev, ctx); + ctx->dsp_ops->cleanup(bus->dev, ctx); if (ctx->dsp->addr.lpe) iounmap(ctx->dsp->addr.lpe); @@ -866,7 +862,7 @@ static void skl_clear_module_state(struct skl_module_pin *mpin, int max, } if (!found) - mcfg->m_state = SKL_MODULE_UNINIT; + mcfg->m_state = SKL_MODULE_INIT_DONE; return; } @@ -1098,7 +1094,7 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); /* If pipe is started, do stop the pipe in FW. */ - if (pipe->state > SKL_PIPE_STARTED) { + if (pipe->state >= SKL_PIPE_STARTED) { ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); if (ret < 0) { dev_err(ctx->dev, "Failed to stop pipeline\n"); diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index 7eb9c419dc7f..e3f06672fd6d 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -24,8 +24,6 @@ static u8 OSC_UUID[16] = {0x6E, 0x88, 0x9F, 0xA6, 0xEB, 0x6C, 0x94, 0x45, 0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53}; -#define DSDT_NHLT_PATH "\\_SB.PCI0.HDAS" - struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) { acpi_handle handle; @@ -33,8 +31,9 @@ struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) struct nhlt_resource_desc *nhlt_ptr = NULL; struct nhlt_acpi_table *nhlt_table = NULL; - if (ACPI_FAILURE(acpi_get_handle(NULL, DSDT_NHLT_PATH, &handle))) { - dev_err(dev, "Requested NHLT device not found\n"); + handle = ACPI_HANDLE(dev); + if (!handle) { + dev_err(dev, "Didn't find ACPI_HANDLE\n"); return NULL; } diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index e12520e142ff..e91bbcffc856 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -21,6 +21,7 @@ #include <linux/pci.h> #include <linux/pm_runtime.h> +#include <linux/delay.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include "skl.h" @@ -155,7 +156,7 @@ int skl_pcm_host_dma_prepare(struct device *dev, struct skl_pipe_params *params) snd_hdac_ext_stream_decouple(ebus, stream, true); format_val = snd_hdac_calc_stream_format(params->s_freq, - params->ch, params->format, 32, 0); + params->ch, params->format, params->host_bps, 0); dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", format_val, params->s_freq, params->ch, params->format); @@ -190,8 +191,8 @@ int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params) stream = stream_to_hdac_ext_stream(hstream); snd_hdac_ext_stream_decouple(ebus, stream, true); - format_val = snd_hdac_calc_stream_format(params->s_freq, - params->ch, params->format, 24, 0); + format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, + params->format, params->link_bps, 0); dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", format_val, params->s_freq, params->ch, params->format); @@ -262,23 +263,6 @@ static int skl_pcm_open(struct snd_pcm_substream *substream, return 0; } -static int skl_be_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct skl *skl = get_skl_ctx(dai->dev); - struct skl_sst *ctx = skl->skl_sst; - struct skl_module_cfg *mconfig; - - if (dai->playback_widget->power || dai->capture_widget->power) - return 0; - - mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); - if (mconfig == NULL) - return -EINVAL; - - return skl_dsp_set_dma_control(ctx, mconfig); -} - static int skl_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -326,6 +310,11 @@ static int skl_pcm_hw_params(struct snd_pcm_substream *substream, p_params.host_dma_id = dma_id; p_params.stream = substream->stream; p_params.format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.host_bps = dai->driver->playback.sig_bits; + else + p_params.host_bps = dai->driver->capture.sig_bits; + m_cfg = skl_tplg_fe_get_cpr_module(dai, p_params.stream); if (m_cfg) @@ -564,6 +553,11 @@ static int skl_link_hw_params(struct snd_pcm_substream *substream, p_params.link_index = link->index; p_params.format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + return skl_tplg_be_update_params(dai, &p_params); } @@ -649,7 +643,6 @@ static struct snd_soc_dai_ops skl_dmic_dai_ops = { static struct snd_soc_dai_ops skl_be_ssp_dai_ops = { .hw_params = skl_be_hw_params, - .prepare = skl_be_prepare, }; static struct snd_soc_dai_ops skl_link_dai_ops = { @@ -670,6 +663,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, .capture = { .stream_name = "System Capture", @@ -677,6 +671,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -688,6 +683,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_QUAD, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -699,6 +695,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -710,6 +707,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_STEREO, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -721,6 +719,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { .channels_max = HDA_QUAD, .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, }, }, { @@ -736,6 +735,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, { @@ -751,6 +751,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, { @@ -766,6 +767,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, }, }, @@ -949,14 +951,12 @@ static struct snd_soc_dai_driver skl_platform_dai[] = { static int skl_platform_open(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_link *dai_link = rtd->dai_link; dev_dbg(rtd->cpu_dai->dev, "In %s:%s\n", __func__, dai_link->cpu_dai_name); - runtime = substream->runtime; snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw); return 0; @@ -1062,13 +1062,31 @@ static snd_pcm_uframes_t skl_platform_pcm_pointer * HAD space reflects the actual data that is transferred. * Use the position buffer for capture, as DPIB write gets * completed earlier than the actual data written to the DDR. + * + * For capture stream following workaround is required to fix the + * incorrect position reporting. + * + * 1. Wait for 20us before reading the DMA position in buffer once + * the interrupt is generated for stream completion as update happens + * on the HDA frame boundary i.e. 20.833uSec. + * 2. Read DPIB register to flush the DMA position value. This dummy + * read is required to flush DMA position value. + * 3. Read the DMA Position-in-Buffer. This value now will be equal to + * or greater than period boundary. */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { pos = readl(ebus->bus.remap_addr + AZX_REG_VS_SDXDPIB_XBASE + (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(hstream)->index)); - else + } else { + udelay(20); + readl(ebus->bus.remap_addr + + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hdac_stream(hstream)->index)); pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); + } if (pos >= hdac_stream(hstream)->bufsize) pos = 0; @@ -1165,7 +1183,7 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) snd_dma_pci_data(skl->pci), size, MAX_PREALLOC_SIZE); if (retval) { - dev_err(dai->dev, "dma buffer allocationf fail\n"); + dev_err(dai->dev, "dma buffer allocation fail\n"); return retval; } } @@ -1173,29 +1191,52 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) return retval; } +static int skl_get_module_info(struct skl *skl, struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + struct uuid_module *module; + uuid_le *uuid_mod; + + uuid_mod = (uuid_le *)mconfig->guid; + + if (list_empty(&ctx->uuid_list)) { + dev_err(ctx->dev, "Module list is empty\n"); + return -EIO; + } + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + mconfig->id.module_id = module->id; + mconfig->is_loadable = module->is_loadable; + return 0; + } + } + + return -EIO; +} + static int skl_populate_modules(struct skl *skl) { struct skl_pipeline *p; struct skl_pipe_module *m; struct snd_soc_dapm_widget *w; struct skl_module_cfg *mconfig; - int ret; + int ret = 0; list_for_each_entry(p, &skl->ppl_list, node) { list_for_each_entry(m, &p->pipe->w_list, node) { - w = m->w; mconfig = w->priv; - ret = snd_skl_get_module_info(skl->skl_sst, mconfig); + ret = skl_get_module_info(skl, mconfig); if (ret < 0) { dev_err(skl->skl_sst->dev, - "query module info failed:%d\n", ret); - goto err; + "query module info failed\n"); + return ret; } } } -err: + return ret; } @@ -1232,6 +1273,7 @@ static int skl_platform_soc_probe(struct snd_soc_platform *platform) } skl_populate_modules(skl); skl->skl_sst->update_d0i3c = skl_update_d0i3c; + skl_dsp_enable_notification(skl->skl_sst, false); } pm_runtime_mark_last_busy(platform->dev); pm_runtime_put_autosuspend(platform->dev); @@ -1256,6 +1298,7 @@ int skl_platform_register(struct device *dev) struct skl *skl = ebus_to_skl(ebus); INIT_LIST_HEAD(&skl->ppl_list); + INIT_LIST_HEAD(&skl->bind_list); ret = snd_soc_register_platform(dev, &skl_platform_drv); if (ret) { @@ -1276,6 +1319,17 @@ int skl_platform_register(struct device *dev) int skl_platform_unregister(struct device *dev) { + struct hdac_ext_bus *ebus = dev_get_drvdata(dev); + struct skl *skl = ebus_to_skl(ebus); + struct skl_module_deferred_bind *modules, *tmp; + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + list_del(&modules->node); + kfree(modules); + } + } + snd_soc_unregister_component(dev); snd_soc_unregister_platform(dev); return 0; diff --git a/sound/soc/intel/skylake/skl-sst-cldma.c b/sound/soc/intel/skylake/skl-sst-cldma.c index c9f6d87381db..d2b1d60fec02 100644 --- a/sound/soc/intel/skylake/skl-sst-cldma.c +++ b/sound/soc/intel/skylake/skl-sst-cldma.c @@ -164,7 +164,7 @@ static void skl_cldma_cleanup(struct sst_dsp *ctx) ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_bdl); } -static int skl_cldma_wait_interruptible(struct sst_dsp *ctx) +int skl_cldma_wait_interruptible(struct sst_dsp *ctx) { int ret = 0; @@ -243,9 +243,14 @@ static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size, * 2. Polling on fw register to identify if data left to transferred doesn't * fill the ring buffer. Caller takes care of polling the required status * register to identify the transfer status. + * 3. if wait flag is set, waits for DBL interrupt to copy the next chunk till + * bytes_left is 0. + * if wait flag is not set, doesn't wait for BDL interrupt. after ccopying + * the first chunk return the no of bytes_left to be copied. */ static int -skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) +skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, + u32 total_size, bool wait) { int ret = 0; bool start = true; @@ -272,13 +277,14 @@ skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) size = ctx->cl_dev.bufsize; skl_cldma_fill_buffer(ctx, size, curr_pos, true, start); - start = false; - ret = skl_cldma_wait_interruptible(ctx); - if (ret < 0) { - skl_cldma_stop(ctx); - return ret; + if (wait) { + start = false; + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) { + skl_cldma_stop(ctx); + return ret; + } } - } else { skl_cldma_int_disable(ctx); @@ -298,9 +304,11 @@ skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, u32 total_size) } bytes_left -= size; curr_pos = curr_pos + size; + if (!wait) + return bytes_left; } - return ret; + return bytes_left; } void skl_cldma_process_intr(struct sst_dsp *ctx) diff --git a/sound/soc/intel/skylake/skl-sst-cldma.h b/sound/soc/intel/skylake/skl-sst-cldma.h index 99e4c86b6358..5b730a1a0ae4 100644 --- a/sound/soc/intel/skylake/skl-sst-cldma.h +++ b/sound/soc/intel/skylake/skl-sst-cldma.h @@ -213,7 +213,7 @@ struct skl_cl_dev_ops { void (*cl_trigger)(struct sst_dsp *ctx, bool enable); void (*cl_cleanup_controller)(struct sst_dsp *ctx); int (*cl_copy_to_dmabuf)(struct sst_dsp *ctx, - const void *bin, u32 size); + const void *bin, u32 size, bool wait); void (*cl_stop_dma)(struct sst_dsp *ctx); }; diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c index c3deefab65d6..08332723c700 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.c +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -355,12 +355,13 @@ int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id) ret = ctx->fw_ops.set_state_D0(ctx, core_id); if (ret < 0) { dev_err(ctx->dev, "unable to get core%d\n", core_id); - return ret; + goto out; } } skl->cores.usage_count[core_id]++; +out: dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", core_id, skl->cores.state[core_id], skl->cores.usage_count[core_id]); @@ -379,7 +380,8 @@ int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id) return -EINVAL; } - if (--skl->cores.usage_count[core_id] == 0) { + if ((--skl->cores.usage_count[core_id] == 0) && + (skl->cores.state[core_id] != SKL_DSP_RESET)) { ret = ctx->fw_ops.set_state_D3(ctx, core_id); if (ret < 0) { dev_err(ctx->dev, "unable to put core %d: %d\n", diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h index 849410d0823e..eba20d37ba8c 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.h +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -17,13 +17,15 @@ #define __SKL_SST_DSP_H__ #include <linux/interrupt.h> +#include <linux/uuid.h> +#include <linux/firmware.h> #include <sound/memalloc.h> #include "skl-sst-cldma.h" -#include "skl-topology.h" struct sst_dsp; struct skl_sst; struct sst_dsp_device; +struct skl_lib_info; /* Intel HD Audio General DSP Registers */ #define SKL_ADSP_GEN_BASE 0x0 @@ -144,7 +146,7 @@ struct skl_dsp_fw_ops { int (*load_fw)(struct sst_dsp *ctx); /* FW module parser/loader */ int (*load_library)(struct sst_dsp *ctx, - struct skl_lib_info *linfo, int count); + struct skl_lib_info *linfo, int lib_count); int (*parse_fw)(struct sst_dsp *ctx); int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id); int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id); @@ -172,6 +174,19 @@ struct skl_dsp_loader_ops { int stream_tag); }; +#define MAX_INSTANCE_BUFF 2 + +struct uuid_module { + uuid_le uuid; + int id; + int is_loadable; + int max_instance; + u64 pvt_id[MAX_INSTANCE_BUFF]; + int *instance_id; + + struct list_head list; +}; + struct skl_load_module_info { u16 mod_id; const struct firmware *fw; @@ -186,6 +201,7 @@ struct skl_module_table { void skl_cldma_process_intr(struct sst_dsp *ctx); void skl_cldma_int_disable(struct sst_dsp *ctx); int skl_cldma_prepare(struct sst_dsp *ctx); +int skl_cldma_wait_interruptible(struct sst_dsp *ctx); void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state); struct sst_dsp *skl_dsp_ctx_init(struct device *dev, @@ -214,6 +230,9 @@ int skl_dsp_boot(struct sst_dsp *ctx); int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp); +int kbl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp); int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp); @@ -222,17 +241,22 @@ int bxt_sst_init_fw(struct device *dev, struct skl_sst *ctx); void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); -int snd_skl_get_module_info(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, unsigned int offset, int index); -int skl_get_pvt_id(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); -int skl_put_pvt_id(struct skl_sst *ctx, - struct skl_module_cfg *mconfig); +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id); +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id); int skl_get_pvt_instance_id_map(struct skl_sst *ctx, int module_id, int instance_id); void skl_freeup_uuid_list(struct skl_sst *ctx); int skl_dsp_strip_extended_manifest(struct firmware *fw); +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable); +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev); +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index); +void skl_release_library(struct skl_lib_info *linfo, int lib_count); + #endif /*__SKL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c index e1391dfbc9e9..58c525096a7c 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.c +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -34,6 +34,11 @@ #define IPC_GLB_REPLY_STATUS_MASK ((0x1 << IPC_GLB_REPLY_STATUS_SHIFT) - 1) #define IPC_GLB_REPLY_STATUS(x) ((x) << IPC_GLB_REPLY_STATUS_SHIFT) +#define IPC_GLB_REPLY_TYPE_SHIFT 29 +#define IPC_GLB_REPLY_TYPE_MASK 0x1F +#define IPC_GLB_REPLY_TYPE(x) (((x) >> IPC_GLB_REPLY_TYPE_SHIFT) \ + & IPC_GLB_RPLY_TYPE_MASK) + #define IPC_TIMEOUT_MSECS 3000 #define IPC_EMPTY_LIST_SIZE 8 @@ -387,12 +392,27 @@ static int skl_ipc_process_notification(struct sst_generic_ipc *ipc, return 0; } +static int skl_ipc_set_reply_error_code(u32 reply) +{ + switch (reply) { + case IPC_GLB_REPLY_OUT_OF_MEMORY: + return -ENOMEM; + + case IPC_GLB_REPLY_BUSY: + return -EBUSY; + + default: + return -EINVAL; + } +} + static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, struct skl_ipc_header header) { struct ipc_message *msg; u32 reply = header.primary & IPC_GLB_REPLY_STATUS_MASK; u64 *ipc_header = (u64 *)(&header); + struct skl_sst *skl = container_of(ipc, struct skl_sst, ipc); msg = skl_ipc_reply_get_msg(ipc, *ipc_header); if (msg == NULL) { @@ -401,33 +421,39 @@ static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, } /* first process the header */ - switch (reply) { - case IPC_GLB_REPLY_SUCCESS: + if (reply == IPC_GLB_REPLY_SUCCESS) { dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary); /* copy the rx data from the mailbox */ sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size); - break; - - case IPC_GLB_REPLY_OUT_OF_MEMORY: - dev_err(ipc->dev, "ipc fw reply: %x: no memory\n", header.primary); - msg->errno = -ENOMEM; - break; - - case IPC_GLB_REPLY_BUSY: - dev_err(ipc->dev, "ipc fw reply: %x: Busy\n", header.primary); - msg->errno = -EBUSY; - break; + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = true; + wake_up(&skl->mod_load_wait); + break; - default: - dev_err(ipc->dev, "Unknown ipc reply: 0x%x\n", reply); - msg->errno = -EINVAL; - break; - } + default: + break; - if (reply != IPC_GLB_REPLY_SUCCESS) { + } + } else { + msg->errno = skl_ipc_set_reply_error_code(reply); dev_err(ipc->dev, "ipc FW reply: reply=%d\n", reply); dev_err(ipc->dev, "FW Error Code: %u\n", ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp)); + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = false; + wake_up(&skl->mod_load_wait); + break; + + default: + break; + + } } list_del(&msg->list); @@ -811,8 +837,8 @@ int skl_ipc_load_modules(struct sst_generic_ipc *ipc, header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_MULTIPLE_MODS); header.primary |= IPC_LOAD_MODULE_CNT(module_cnt); - ret = sst_ipc_tx_message_wait(ipc, *ipc_header, data, - (sizeof(u16) * module_cnt), NULL, 0); + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, data, + (sizeof(u16) * module_cnt)); if (ret < 0) dev_err(ipc->dev, "ipc: load modules failed :%d\n", ret); @@ -947,7 +973,7 @@ int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, EXPORT_SYMBOL_GPL(skl_ipc_get_large_config); int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, - u8 dma_id, u8 table_id) + u8 dma_id, u8 table_id, bool wait) { struct skl_ipc_header header = {0}; u64 *ipc_header = (u64 *)(&header); @@ -959,7 +985,11 @@ int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, header.primary |= IPC_MOD_INSTANCE_ID(table_id); header.primary |= IPC_MOD_ID(dma_id); - ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (wait) + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, + NULL, 0, NULL, 0); + else + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, NULL, 0); if (ret < 0) dev_err(ipc->dev, "ipc: load lib failed\n"); diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h index 9660ace379ab..e057da2713c6 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.h +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -69,6 +69,14 @@ struct skl_d0i3_data { struct delayed_work work; }; +#define SKL_LIB_NAME_LENGTH 128 +#define SKL_MAX_LIB 16 + +struct skl_lib_info { + char name[SKL_LIB_NAME_LENGTH]; + const struct firmware *fw; +}; + struct skl_sst { struct device *dev; struct sst_dsp *dsp; @@ -77,6 +85,11 @@ struct skl_sst { wait_queue_head_t boot_wait; bool boot_complete; + /* module load */ + wait_queue_head_t mod_load_wait; + bool mod_load_complete; + bool mod_load_status; + /* IPC messaging */ struct sst_generic_ipc ipc; @@ -105,6 +118,8 @@ struct skl_sst { void (*update_d0i3c)(struct device *dev, bool enable); struct skl_d0i3_data d0i3; + + const struct skl_dsp_ops *dsp_ops; }; struct skl_ipc_init_instance_msg { @@ -182,7 +197,7 @@ int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, struct skl_ipc_large_config_msg *msg, u32 *param); int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, - u8 dma_id, u8 table_id); + u8 dma_id, u8 table_id, bool wait); int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, struct skl_ipc_d0ix_msg *msg); diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c index ea162fbf68e5..81ee251881b4 100644 --- a/sound/soc/intel/skylake/skl-sst-utils.c +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -94,19 +94,6 @@ struct adsp_fw_hdr { u32 load_offset; } __packed; -#define MAX_INSTANCE_BUFF 2 - -struct uuid_module { - uuid_le uuid; - int id; - int is_loadable; - int max_instance; - u64 pvt_id[MAX_INSTANCE_BUFF]; - int *instance_id; - - struct list_head list; -}; - struct skl_ext_manifest_hdr { u32 id; u32 len; @@ -115,32 +102,6 @@ struct skl_ext_manifest_hdr { u32 entries; }; -int snd_skl_get_module_info(struct skl_sst *ctx, - struct skl_module_cfg *mconfig) -{ - struct uuid_module *module; - uuid_le *uuid_mod; - - uuid_mod = (uuid_le *)mconfig->guid; - - if (list_empty(&ctx->uuid_list)) { - dev_err(ctx->dev, "Module list is empty\n"); - return -EINVAL; - } - - list_for_each_entry(module, &ctx->uuid_list, list) { - if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { - mconfig->id.module_id = module->id; - mconfig->is_loadable = module->is_loadable; - - return 0; - } - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(snd_skl_get_module_info); - static int skl_get_pvtid_map(struct uuid_module *module, int instance_id) { int pvt_id; @@ -222,21 +183,18 @@ static inline int skl_pvtid_128(struct uuid_module *module) * This generates a 128 bit private unique id for a module TYPE so that * module instance is unique */ -int skl_get_pvt_id(struct skl_sst *ctx, struct skl_module_cfg *mconfig) +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id) { struct uuid_module *module; - uuid_le *uuid_mod; int pvt_id; - uuid_mod = (uuid_le *)mconfig->guid; - list_for_each_entry(module, &ctx->uuid_list, list) { if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { pvt_id = skl_pvtid_128(module); if (pvt_id >= 0) { - module->instance_id[pvt_id] = - mconfig->id.instance_id; + module->instance_id[pvt_id] = instance_id; + return pvt_id; } } @@ -254,23 +212,21 @@ EXPORT_SYMBOL_GPL(skl_get_pvt_id); * * This frees a 128 bit private unique id previously generated */ -int skl_put_pvt_id(struct skl_sst *ctx, struct skl_module_cfg *mconfig) +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id) { int i; - uuid_le *uuid_mod; struct uuid_module *module; - uuid_mod = (uuid_le *)mconfig->guid; list_for_each_entry(module, &ctx->uuid_list, list) { if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { - if (mconfig->id.pvt_id != 0) - i = (mconfig->id.pvt_id) / 64; + if (*pvt_id != 0) + i = (*pvt_id) / 64; else i = 0; - module->pvt_id[i] &= ~(1 << (mconfig->id.pvt_id)); - mconfig->id.pvt_id = -1; + module->pvt_id[i] &= ~(1 << (*pvt_id)); + *pvt_id = -1; return 0; } } @@ -405,3 +361,83 @@ int skl_dsp_strip_extended_manifest(struct firmware *fw) return 0; } + +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev) +{ + struct skl_sst *skl; + struct sst_dsp *sst; + int ret; + + skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); + if (skl == NULL) + return -ENOMEM; + + skl->dev = dev; + skl_dev->thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); + skl->dsp = skl_dsp_ctx_init(dev, skl_dev, irq); + if (!skl->dsp) { + dev_err(skl->dev, "%s: no device\n", __func__); + return -ENODEV; + } + + sst = skl->dsp; + sst->fw_name = fw_name; + sst->dsp_ops = dsp_ops; + init_waitqueue_head(&skl->mod_load_wait); + INIT_LIST_HEAD(&sst->module_list); + ret = skl_ipc_init(dev, skl); + if (ret) + return ret; + + skl->is_first_boot = true; + if (dsp) + *dsp = skl; + + return ret; +} + +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index) +{ + int ret; + struct sst_dsp *dsp = skl->dsp; + + if (linfo->fw == NULL) { + ret = request_firmware(&linfo->fw, linfo->name, + skl->dev); + if (ret < 0) { + dev_err(skl->dev, "Request lib %s failed:%d\n", + linfo->name, ret); + return ret; + } + } + + if (skl->is_first_boot) { + ret = snd_skl_parse_uuids(dsp, linfo->fw, hdr_offset, index); + if (ret < 0) + return ret; + } + + stripped_fw->data = linfo->fw->data; + stripped_fw->size = linfo->fw->size; + skl_dsp_strip_extended_manifest(stripped_fw); + + return 0; +} + +void skl_release_library(struct skl_lib_info *linfo, int lib_count) +{ + int i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + if (linfo[i].fw) { + release_firmware(linfo[i].fw); + linfo[i].fw = NULL; + } + } +} diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c index b30bd384c8d3..155e456b7a3a 100644 --- a/sound/soc/intel/skylake/skl-sst.c +++ b/sound/soc/intel/skylake/skl-sst.c @@ -52,7 +52,8 @@ static int skl_transfer_firmware(struct sst_dsp *ctx, { int ret = 0; - ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size); + ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size, + true); if (ret < 0) return ret; @@ -178,6 +179,18 @@ static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) dev_err(ctx->dev, "unable to load firmware\n"); return ret; } + + /* load libs as they are also lost on D3 */ + if (skl->lib_count > 1) { + ret = ctx->fw_ops.load_library(ctx, skl->lib_info, + skl->lib_count); + if (ret < 0) { + dev_err(ctx->dev, "reload libs failed: %d\n", + ret); + return ret; + } + + } } /* @@ -203,7 +216,7 @@ static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) skl->cores.state[core_id] = SKL_DSP_RUNNING; - return ret; + return 0; } static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) @@ -323,27 +336,85 @@ static struct skl_module_table *skl_module_get_from_id( return NULL; } -static int skl_transfer_module(struct sst_dsp *ctx, - struct skl_load_module_info *module) +static int skl_transfer_module(struct sst_dsp *ctx, const void *data, + u32 size, u16 mod_id, u8 table_id, bool is_module) { - int ret; + int ret, bytes_left, curr_pos; struct skl_sst *skl = ctx->thread_context; + skl->mod_load_complete = false; - ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, module->fw->data, - module->fw->size); - if (ret < 0) - return ret; + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, data, size, false); + if (bytes_left < 0) + return bytes_left; - ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, - (void *)&module->mod_id); - if (ret < 0) - dev_err(ctx->dev, "Failed to Load module: %d\n", ret); + /* check is_module flag to load module or library */ + if (is_module) + ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id); + else + ret = skl_sst_ipc_load_library(&skl->ipc, 0, table_id, false); + + if (ret < 0) { + dev_err(ctx->dev, "Failed to Load %s with err %d\n", + is_module ? "module" : "lib", ret); + goto out; + } + + /* + * if bytes_left > 0 then wait for BDL complete interrupt and + * copy the next chunk till bytes_left is 0. if bytes_left is + * is zero, then wait for load module IPC reply + */ + while (bytes_left > 0) { + curr_pos = size - bytes_left; + + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) + goto out; + + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, + data + curr_pos, + bytes_left, false); + } + ret = wait_event_timeout(skl->mod_load_wait, skl->mod_load_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0 || !skl->mod_load_status) { + dev_err(ctx->dev, "Module Load failed\n"); + ret = -EIO; + } + +out: ctx->cl_dev.ops.cl_stop_dma(ctx); return ret; } +static int +kbl_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) +{ + struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; + int ret, i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, + SKL_ADSP_FW_BIN_HDR_OFFSET, i); + if (ret < 0) + goto load_library_failed; + ret = skl_transfer_module(ctx, stripped_fw.data, + stripped_fw.size, 0, i, false); + if (ret < 0) + goto load_library_failed; + } + + return 0; + +load_library_failed: + skl_release_library(linfo, lib_count); + return ret; +} + static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) { struct skl_module_table *module_entry = NULL; @@ -365,7 +436,9 @@ static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) } if (!module_entry->usage_cnt) { - ret = skl_transfer_module(ctx, module_entry->mod_info); + ret = skl_transfer_module(ctx, module_entry->mod_info->fw->data, + module_entry->mod_info->fw->size, + mod_id, 0, true); if (ret < 0) { dev_err(ctx->dev, "Failed to Load module\n"); return ret; @@ -388,6 +461,11 @@ static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt); return -EIO; } + + /* if module is used by others return, no need to unload */ + if (usage_cnt > 0) + return 0; + ret = skl_ipc_unload_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id); if (ret < 0) { @@ -434,6 +512,16 @@ static struct skl_dsp_fw_ops skl_fw_ops = { .unload_mod = skl_unload_module, }; +static struct skl_dsp_fw_ops kbl_fw_ops = { + .set_state_D0 = skl_set_dsp_D0, + .set_state_D3 = skl_set_dsp_D3, + .load_fw = skl_load_base_firmware, + .get_fw_errcode = skl_get_errorcode, + .load_library = kbl_load_library, + .load_mod = skl_load_module, + .unload_mod = skl_unload_module, +}; + static struct sst_ops skl_ops = { .irq_handler = skl_dsp_sst_interrupt, .write = sst_shim32_write, @@ -455,45 +543,47 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, struct sst_dsp *sst; int ret; - skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); - if (skl == NULL) - return -ENOMEM; - - skl->dev = dev; - skl_dev.thread_context = skl; - INIT_LIST_HEAD(&skl->uuid_list); - - skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); - if (!skl->dsp) { - dev_err(skl->dev, "%s: no device\n", __func__); - return -ENODEV; + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; } + skl = *dsp; sst = skl->dsp; - - sst->fw_name = fw_name; sst->addr.lpe = mmio_base; sst->addr.shim = mmio_base; sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); - INIT_LIST_HEAD(&sst->module_list); - sst->dsp_ops = dsp_ops; sst->fw_ops = skl_fw_ops; - ret = skl_ipc_init(dev, skl); - if (ret) + skl->cores.count = 2; + + return 0; +} +EXPORT_SYMBOL_GPL(skl_sst_dsp_init); + +int kbl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp) +{ + struct sst_dsp *sst; + int ret; + + ret = skl_sst_dsp_init(dev, mmio_base, irq, fw_name, dsp_ops, dsp); + if (ret < 0) { + dev_err(dev, "%s: Init failed %d\n", __func__, ret); return ret; + } - skl->cores.count = 2; - skl->is_first_boot = true; + sst = (*dsp)->dsp; + sst->fw_ops = kbl_fw_ops; - if (dsp) - *dsp = skl; + return 0; - return ret; } -EXPORT_SYMBOL_GPL(skl_sst_dsp_init); +EXPORT_SYMBOL_GPL(kbl_sst_dsp_init); int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx) { @@ -507,6 +597,15 @@ int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx) } skl_dsp_init_core_state(sst); + + if (ctx->lib_count > 1) { + ret = sst->fw_ops.load_library(sst, ctx->lib_info, + ctx->lib_count); + if (ret < 0) { + dev_err(dev, "Load Library failed : %x\n", ret); + return ret; + } + } ctx->is_first_boot = false; return 0; diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index 2dbfb1b24ef4..3a99712e44a8 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -299,8 +299,6 @@ static void skl_tplg_update_buffer_size(struct skl_sst *ctx, { int multiplier = 1; struct skl_module_fmt *in_fmt, *out_fmt; - int in_rate, out_rate; - /* Since fixups is applied to pin 0 only, ibs, obs needs * change for pin 0 only @@ -311,22 +309,12 @@ static void skl_tplg_update_buffer_size(struct skl_sst *ctx, if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT) multiplier = 5; - if (in_fmt->s_freq % 1000) - in_rate = (in_fmt->s_freq / 1000) + 1; - else - in_rate = (in_fmt->s_freq / 1000); - - mcfg->ibs = in_rate * (mcfg->in_fmt->channels) * - (mcfg->in_fmt->bit_depth >> 3) * + mcfg->ibs = DIV_ROUND_UP(in_fmt->s_freq, 1000) * + in_fmt->channels * (in_fmt->bit_depth >> 3) * multiplier; - if (mcfg->out_fmt->s_freq % 1000) - out_rate = (mcfg->out_fmt->s_freq / 1000) + 1; - else - out_rate = (mcfg->out_fmt->s_freq / 1000); - - mcfg->obs = out_rate * (mcfg->out_fmt->channels) * - (mcfg->out_fmt->bit_depth >> 3) * + mcfg->obs = DIV_ROUND_UP(out_fmt->s_freq, 1000) * + out_fmt->channels * (out_fmt->bit_depth >> 3) * multiplier; } @@ -551,6 +539,7 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) int ret = 0; list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; w = w_module->w; mconfig = w->priv; @@ -588,13 +577,15 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) * FE/BE params */ skl_tplg_update_module_params(w, ctx); - mconfig->id.pvt_id = skl_get_pvt_id(ctx, mconfig); + uuid_mod = (uuid_le *)mconfig->guid; + mconfig->id.pvt_id = skl_get_pvt_id(ctx, uuid_mod, + mconfig->id.instance_id); if (mconfig->id.pvt_id < 0) return ret; skl_tplg_set_module_init_data(w); ret = skl_init_module(ctx, mconfig); if (ret < 0) { - skl_put_pvt_id(ctx, mconfig); + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); return ret; } skl_tplg_alloc_pipe_mcps(skl, mconfig); @@ -614,7 +605,9 @@ static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx, struct skl_module_cfg *mconfig = NULL; list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; mconfig = w_module->w->priv; + uuid_mod = (uuid_le *)mconfig->guid; if (mconfig->is_loadable && ctx->dsp->fw_ops.unload_mod && mconfig->m_state > SKL_MODULE_UNINIT) { @@ -623,7 +616,7 @@ static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx, if (ret < 0) return -EIO; } - skl_put_pvt_id(ctx, mconfig); + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); } /* no modules to unload in this path, so return */ @@ -645,8 +638,9 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, struct skl_module_cfg *mconfig = w->priv; struct skl_pipe_module *w_module; struct skl_pipe *s_pipe = mconfig->pipe; - struct skl_module_cfg *src_module = NULL, *dst_module; + struct skl_module_cfg *src_module = NULL, *dst_module, *module; struct skl_sst *ctx = skl->skl_sst; + struct skl_module_deferred_bind *modules; /* check resource available */ if (!skl_is_pipe_mcps_avail(skl, mconfig)) @@ -687,29 +681,48 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, src_module = dst_module; } + /* + * When the destination module is initialized, check for these modules + * in deferred bind list. If found, bind them. + */ + list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + list_for_each_entry(modules, &skl->bind_list, node) { + module = w_module->w->priv; + if (modules->dst == module) + skl_bind_modules(ctx, modules->src, + modules->dst); + } + } + return 0; } -static int skl_fill_sink_instance_id(struct skl_sst *ctx, - struct skl_algo_data *alg_data) +static int skl_fill_sink_instance_id(struct skl_sst *ctx, u32 *params, + int size, struct skl_module_cfg *mcfg) { - struct skl_kpb_params *params = (struct skl_kpb_params *)alg_data->params; - struct skl_mod_inst_map *inst; int i, pvt_id; - inst = params->map; + if (mcfg->m_type == SKL_MODULE_TYPE_KPB) { + struct skl_kpb_params *kpb_params = + (struct skl_kpb_params *)params; + struct skl_mod_inst_map *inst = kpb_params->map; - for (i = 0; i < params->num_modules; i++) { - pvt_id = skl_get_pvt_instance_id_map(ctx, - inst->mod_id, inst->inst_id); - if (pvt_id < 0) - return -EINVAL; - inst->inst_id = pvt_id; - inst++; + for (i = 0; i < kpb_params->num_modules; i++) { + pvt_id = skl_get_pvt_instance_id_map(ctx, inst->mod_id, + inst->inst_id); + if (pvt_id < 0) + return -EINVAL; + + inst->inst_id = pvt_id; + inst++; + } } + return 0; } - /* * Some modules require params to be set after the module is bound to * all pins connected. @@ -726,6 +739,7 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, struct soc_bytes_ext *sb; struct skl_algo_data *bc; struct skl_specific_cfg *sp_cfg; + u32 *params; /* * check all out/in pins are in bind state. @@ -758,11 +772,18 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, bc = (struct skl_algo_data *)sb->dobj.private; if (bc->set_params == SKL_PARAM_BIND) { - if (mconfig->m_type == SKL_MODULE_TYPE_KPB) - skl_fill_sink_instance_id(ctx, bc); - ret = skl_set_module_params(ctx, - (u32 *)bc->params, bc->max, - bc->param_id, mconfig); + params = kzalloc(bc->max, GFP_KERNEL); + if (!params) + return -ENOMEM; + + memcpy(params, bc->params, bc->max); + skl_fill_sink_instance_id(ctx, params, bc->max, + mconfig); + + ret = skl_set_module_params(ctx, params, + bc->max, bc->param_id, mconfig); + kfree(params); + if (ret < 0) return ret; } @@ -772,6 +793,44 @@ static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, return 0; } + +static int skl_tplg_module_add_deferred_bind(struct skl *skl, + struct skl_module_cfg *src, struct skl_module_cfg *dst) +{ + struct skl_module_deferred_bind *m_list, *modules; + int i; + + /* only supported for module with static pin connection */ + for (i = 0; i < dst->max_in_queue; i++) { + struct skl_module_pin *pin = &dst->m_in_pin[i]; + + if (pin->is_dynamic) + continue; + + if ((pin->id.module_id == src->id.module_id) && + (pin->id.instance_id == src->id.instance_id)) { + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry(modules, &skl->bind_list, node) { + if (modules->src == src && modules->dst == dst) + return 0; + } + } + + m_list = kzalloc(sizeof(*m_list), GFP_KERNEL); + if (!m_list) + return -ENOMEM; + + m_list->src = src; + m_list->dst = dst; + + list_add(&m_list->node, &skl->bind_list); + } + } + + return 0; +} + static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w, struct skl *skl, struct snd_soc_dapm_widget *src_w, @@ -806,6 +865,28 @@ static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w, sink = p->sink; sink_mconfig = sink->priv; + /* + * Modules other than PGA leaf can be connected + * directly or via switch to a module in another + * pipeline. EX: reference path + * when the path is enabled, the dst module that needs + * to be bound may not be initialized. if the module is + * not initialized, add these modules in the deferred + * bind list and when the dst module is initialised, + * bind this module to the dst_module in deferred list. + */ + if (((src_mconfig->m_state == SKL_MODULE_INIT_DONE) + && (sink_mconfig->m_state == SKL_MODULE_UNINIT))) { + + ret = skl_tplg_module_add_deferred_bind(skl, + src_mconfig, sink_mconfig); + + if (ret < 0) + return ret; + + } + + if (src_mconfig->m_state == SKL_MODULE_UNINIT || sink_mconfig->m_state == SKL_MODULE_UNINIT) continue; @@ -985,15 +1066,6 @@ static int skl_tplg_mixer_dapm_pre_pmd_event(struct snd_soc_dapm_widget *w, src_mconfig = sink_mconfig->m_in_pin[i].tgt_mcfg; if (!src_mconfig) continue; - /* - * If path_found == 1, that means pmd for source - * pipe has not occurred, source is connected to - * some other sink. so its responsibility of sink - * to unbind itself from source. - */ - ret = skl_stop_pipe(ctx, src_mconfig->pipe); - if (ret < 0) - return ret; ret = skl_unbind_modules(ctx, src_mconfig, sink_mconfig); @@ -1019,6 +1091,7 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, struct skl_module_cfg *src_module = NULL, *dst_module; struct skl_sst *ctx = skl->skl_sst; struct skl_pipe *s_pipe = mconfig->pipe; + struct skl_module_deferred_bind *modules, *tmp; if (s_pipe->state == SKL_PIPE_INVALID) return -EINVAL; @@ -1027,6 +1100,35 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, skl_tplg_free_pipe_mem(skl, mconfig); list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + src_module = w_module->w->priv; + + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + /* + * When the destination module is deleted, Unbind the + * modules from deferred bind list. + */ + if (modules->dst == src_module) { + skl_unbind_modules(ctx, modules->src, + modules->dst); + } + + /* + * When the source module is deleted, remove this entry + * from the deferred bind list. + */ + if (modules->src == src_module) { + list_del(&modules->node); + modules->src = NULL; + modules->dst = NULL; + kfree(modules); + } + } + } + + list_for_each_entry(w_module, &s_pipe->w_list, node) { dst_module = w_module->w->priv; if (mconfig->m_state >= SKL_MODULE_INIT_DONE) @@ -1042,6 +1144,11 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, skl_delete_pipe(ctx, mconfig->pipe); + list_for_each_entry(w_module, &s_pipe->w_list, node) { + src_module = w_module->w->priv; + src_module->m_state = SKL_MODULE_UNINIT; + } + return skl_tplg_unload_pipe_modules(ctx, s_pipe); } @@ -1083,36 +1190,6 @@ static int skl_tplg_pga_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, } /* - * In modelling, we assume there will be ONLY one mixer in a pipeline. If - * mixer is not required then it is treated as static mixer aka vmixer with - * a hard path to source module - * So we don't need to check if source is started or not as hard path puts - * dependency on each other - */ -static int skl_tplg_vmixer_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_dapm_context *dapm = w->dapm; - struct skl *skl = get_skl_ctx(dapm->dev); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - return skl_tplg_mixer_dapm_pre_pmu_event(w, skl); - - case SND_SOC_DAPM_POST_PMU: - return skl_tplg_mixer_dapm_post_pmu_event(w, skl); - - case SND_SOC_DAPM_PRE_PMD: - return skl_tplg_mixer_dapm_pre_pmd_event(w, skl); - - case SND_SOC_DAPM_POST_PMD: - return skl_tplg_mixer_dapm_post_pmd_event(w, skl); - } - - return 0; -} - -/* * In modelling, we assume there will be ONLY one mixer in a pipeline. If a * second one is required that is created as another pipe entity. * The mixer is responsible for pipe management and represent a pipeline @@ -1252,10 +1329,12 @@ static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg, case SKL_DEVICE_HDALINK: pipe->p_params->link_dma_id = params->link_dma_id; pipe->p_params->link_index = params->link_index; + pipe->p_params->link_bps = params->link_bps; break; case SKL_DEVICE_HDAHOST: pipe->p_params->host_dma_id = params->host_dma_id; + pipe->p_params->host_bps = params->host_bps; break; default: @@ -1578,7 +1657,7 @@ int skl_tplg_be_update_params(struct snd_soc_dai *dai, static const struct snd_soc_tplg_widget_events skl_tplg_widget_ops[] = { {SKL_MIXER_EVENT, skl_tplg_mixer_event}, - {SKL_VMIXER_EVENT, skl_tplg_vmixer_event}, + {SKL_VMIXER_EVENT, skl_tplg_mixer_event}, {SKL_PGA_EVENT, skl_tplg_pga_event}, }; @@ -1632,7 +1711,7 @@ static int skl_tplg_add_pipe(struct device *dev, list_for_each_entry(ppl, &skl->ppl_list, node) { if (ppl->pipe->ppl_id == tkn_elem->value) { mconfig->pipe = ppl->pipe; - return EEXIST; + return -EEXIST; } } @@ -1924,11 +2003,13 @@ static int skl_tplg_get_token(struct device *dev, ret = skl_tplg_add_pipe(dev, mconfig, skl, tkn_elem); - if (ret < 0) + if (ret < 0) { + if (ret == -EEXIST) { + is_pipe_exists = 1; + break; + } return is_pipe_exists; - - if (ret == EEXIST) - is_pipe_exists = 1; + } break; diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h index fefab0e99a3b..cc64d6bdb4f6 100644 --- a/sound/soc/intel/skylake/skl-topology.h +++ b/sound/soc/intel/skylake/skl-topology.h @@ -257,6 +257,8 @@ struct skl_pipe_params { snd_pcm_format_t format; int link_index; int stream; + unsigned int host_bps; + unsigned int link_bps; }; struct skl_pipe { @@ -334,17 +336,10 @@ struct skl_pipeline { struct list_head node; }; -#define SKL_LIB_NAME_LENGTH 128 -#define SKL_MAX_LIB 16 - -struct skl_lib_info { - char name[SKL_LIB_NAME_LENGTH]; - const struct firmware *fw; -}; - -struct skl_manifest { - u32 lib_count; - struct skl_lib_info lib[SKL_MAX_LIB]; +struct skl_module_deferred_bind { + struct skl_module_cfg *src; + struct skl_module_cfg *dst; + struct list_head node; }; static inline struct skl *get_skl_ctx(struct device *dev) diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c index 0c57d4eaae3a..6df3b317a476 100644 --- a/sound/soc/intel/skylake/skl.c +++ b/sound/soc/intel/skylake/skl.c @@ -512,7 +512,7 @@ static int probe_codec(struct hdac_ext_bus *ebus, int addr) struct hdac_bus *bus = ebus_to_hbus(ebus); unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; - unsigned int res; + unsigned int res = -1; mutex_lock(&bus->cmd_mutex); snd_hdac_bus_send_cmd(bus, cmd); diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index bbef77d2b917..a454f6035f3e 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -27,27 +27,6 @@ #define SKL_SUSPEND_DELAY 2000 -/* Vendor Specific Registers */ -#define AZX_REG_VS_EM1 0x1000 -#define AZX_REG_VS_INRC 0x1004 -#define AZX_REG_VS_OUTRC 0x1008 -#define AZX_REG_VS_FIFOTRK 0x100C -#define AZX_REG_VS_FIFOTRK2 0x1010 -#define AZX_REG_VS_EM2 0x1030 -#define AZX_REG_VS_EM3L 0x1038 -#define AZX_REG_VS_EM3U 0x103C -#define AZX_REG_VS_EM4L 0x1040 -#define AZX_REG_VS_EM4U 0x1044 -#define AZX_REG_VS_LTRC 0x1048 -#define AZX_REG_VS_D0I3C 0x104A -#define AZX_REG_VS_PCE 0x104B -#define AZX_REG_VS_L2MAGC 0x1050 -#define AZX_REG_VS_L2LAHPT 0x1054 -#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 -#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 -#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 -#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 - #define AZX_PCIREG_PGCTL 0x44 #define AZX_PGCTL_LSRMD_MASK (1 << 4) #define AZX_PCIREG_CGCTL 0x48 @@ -77,6 +56,7 @@ struct skl { struct skl_dsp_resource resource; struct list_head ppl_list; + struct list_head bind_list; const char *fw_name; char tplg_name[64]; diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index d7013bde6f45..5c68797f36c4 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -22,6 +22,16 @@ config SND_SOC_MT2701_CS42448 Select Y if you have such device. If unsure select "N". +config SND_SOC_MT2701_WM8960 + tristate "ASoc Audio driver for MT2701 with WM8960 codec" + depends on SND_SOC_MT2701 && I2C + select SND_SOC_WM8960 + help + This adds ASoC driver for Mediatek MT2701 boards + with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + config SND_SOC_MT8173 tristate "ASoC support for Mediatek MT8173 chip" depends on ARCH_MEDIATEK diff --git a/sound/soc/mediatek/mt2701/Makefile b/sound/soc/mediatek/mt2701/Makefile index 31c3d04d4942..c91deb6aca21 100644 --- a/sound/soc/mediatek/mt2701/Makefile +++ b/sound/soc/mediatek/mt2701/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o # machine driver obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o +obj-$(CONFIG_SND_SOC_MT2701_WM8960) += mt2701-wm8960.o diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index c7fa3e663463..bc5d4db94de6 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -604,6 +604,22 @@ static struct snd_soc_dai_ops mt2701_btmrg_ops = { static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = { /* FE DAIs: memory intefaces to CPU */ { + .name = "PCMO0", + .id = MT2701_MEMIF_DL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { .name = "PCM_multi", .id = MT2701_MEMIF_DLM, .suspend = mtk_afe_dai_suspend, diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c index 1e7e8d43fd8a..aa5b31b121e3 100644 --- a/sound/soc/mediatek/mt2701/mt2701-cs42448.c +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -129,7 +129,7 @@ static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream) return 0; } -static struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { +static const struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { .startup = mt2701_cs42448_fe_ops_startup, }; diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c new file mode 100644 index 000000000000..a08ce2323bdc --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -0,0 +1,176 @@ +/* + * mt2701-wm8960.c -- MT2701 WM8960 ALSA SoC machine driver + * + * Copyright (c) 2017 MediaTek Inc. + * Author: Ryder Lee <ryder.lee@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <sound/soc.h> + +#include "mt2701-afe-common.h" + +static const struct snd_soc_dapm_widget mt2701_wm8960_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), +}; + +static const struct snd_kcontrol_new mt2701_wm8960_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("AMIC"), +}; + +static int mt2701_wm8960_be_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_wm8960_be_ops = { + .hw_params = mt2701_wm8960_be_ops_hw_params +}; + +static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .cpu_dai_name = "PCMO0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + { + .name = "wm8960-codec", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "wm8960-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_wm8960_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_wm8960_card = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, + .dai_link = mt2701_wm8960_dai_links, + .num_links = ARRAY_SIZE(mt2701_wm8960_dai_links), + .controls = mt2701_wm8960_controls, + .num_controls = ARRAY_SIZE(mt2701_wm8960_controls), + .dapm_widgets = mt2701_wm8960_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets), +}; + +static int mt2701_wm8960_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_wm8960_card; + struct device_node *platform_node, *codec_node; + int ret, i; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].platform_name) + continue; + mt2701_wm8960_dai_links[i].platform_of_node = platform_node; + } + + card->dev = &pdev->dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].codec_name) + continue; + mt2701_wm8960_dai_links[i].codec_of_node = codec_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_wm8960_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-wm8960-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_wm8960_machine = { + .driver = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt2701_wm8960_machine_dt_match, +#endif + }, + .probe = mt2701_wm8960_machine_probe, +}; + +module_platform_driver(mt2701_wm8960_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 WM8960 ALSA SoC machine driver"); +MODULE_AUTHOR("Ryder Lee <ryder.lee@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 wm8960 soc card"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-max98090.c b/sound/soc/mediatek/mt8173/mt8173-max98090.c index 46c8e6ae00b4..e0c2b23ec711 100644 --- a/sound/soc/mediatek/mt8173/mt8173-max98090.c +++ b/sound/soc/mediatek/mt8173/mt8173-max98090.c @@ -67,7 +67,7 @@ static int mt8173_max98090_hw_params(struct snd_pcm_substream *substream, SND_SOC_CLOCK_IN); } -static struct snd_soc_ops mt8173_max98090_ops = { +static const struct snd_soc_ops mt8173_max98090_ops = { .hw_params = mt8173_max98090_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c index 467f7049a288..5e383eb456a4 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c @@ -75,7 +75,7 @@ static int mt8173_rt5650_rt5514_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_rt5514_ops = { +static const struct snd_soc_ops mt8173_rt5650_rt5514_ops = { .hw_params = mt8173_rt5650_rt5514_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c index 1b8b2a778845..fed1f15a39c2 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c @@ -79,7 +79,7 @@ static int mt8173_rt5650_rt5676_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_rt5676_ops = { +static const struct snd_soc_ops mt8173_rt5650_rt5676_ops = { .hw_params = mt8173_rt5650_rt5676_hw_params, }; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c index ba65f4157a7e..a78470839b65 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -105,7 +105,7 @@ static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops mt8173_rt5650_ops = { +static const struct snd_soc_ops mt8173_rt5650_ops = { .hw_params = mt8173_rt5650_hw_params, }; diff --git a/sound/soc/omap/am3517evm.c b/sound/soc/omap/am3517evm.c index 25a33e9d417a..d5651026ec10 100644 --- a/sound/soc/omap/am3517evm.c +++ b/sound/soc/omap/am3517evm.c @@ -49,7 +49,7 @@ static int am3517evm_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops am3517evm_ops = { +static const struct snd_soc_ops am3517evm_ops = { .hw_params = am3517evm_hw_params, }; diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index fdecb7043174..71e5f31fa306 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -124,7 +124,7 @@ static int n810_hw_params(struct snd_pcm_substream *substream, return err; } -static struct snd_soc_ops n810_ops = { +static const struct snd_soc_ops n810_ops = { .startup = n810_startup, .hw_params = n810_hw_params, .shutdown = n810_shutdown, diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c index 89fe95e877db..614b18d2f631 100644 --- a/sound/soc/omap/omap-abe-twl6040.c +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -70,7 +70,7 @@ static int omap_abe_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops omap_abe_ops = { +static const struct snd_soc_ops omap_abe_ops = { .hw_params = omap_abe_hw_params, }; diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c index 743131473056..a24b0dedabb9 100644 --- a/sound/soc/omap/omap-twl4030.c +++ b/sound/soc/omap/omap-twl4030.c @@ -73,7 +73,7 @@ static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, return snd_soc_runtime_set_dai_fmt(rtd, fmt); } -static struct snd_soc_ops omap_twl4030_ops = { +static const struct snd_soc_ops omap_twl4030_ops = { .hw_params = omap_twl4030_hw_params, }; diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index 732e749a1f8e..4e3de712159c 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -184,7 +184,7 @@ static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static struct snd_soc_ops omap3pandora_ops = { +static const struct snd_soc_ops omap3pandora_ops = { .hw_params = omap3pandora_hw_params, }; diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c index aa4053bf6710..e4096779ca05 100644 --- a/sound/soc/omap/osk5912.c +++ b/sound/soc/omap/osk5912.c @@ -68,7 +68,7 @@ static int osk_hw_params(struct snd_pcm_substream *substream, return err; } -static struct snd_soc_ops osk_ops = { +static const struct snd_soc_ops osk_ops = { .startup = osk_startup, .hw_params = osk_hw_params, .shutdown = osk_shutdown, diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index a76845748a10..3aeb65feaea1 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -123,7 +123,7 @@ static int rx51_hw_params(struct snd_pcm_substream *substream, SND_SOC_CLOCK_IN); } -static struct snd_soc_ops rx51_ops = { +static const struct snd_soc_ops rx51_ops = { .startup = rx51_startup, .hw_params = rx51_hw_params, }; @@ -433,10 +433,9 @@ static int rx51_soc_probe(struct platform_device *pdev) } pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (pdata == NULL) { - dev_err(card->dev, "failed to create private data\n"); + if (pdata == NULL) return -ENOMEM; - } + snd_soc_card_set_drvdata(card, pdata); pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c index b6cb9950f05d..9a3f5b799720 100644 --- a/sound/soc/pxa/brownstone.c +++ b/sound/soc/pxa/brownstone.c @@ -74,7 +74,7 @@ static int brownstone_wm8994_hw_params(struct snd_pcm_substream *substream, } /* machine stream operations */ -static struct snd_soc_ops brownstone_ops = { +static const struct snd_soc_ops brownstone_ops = { .hw_params = brownstone_wm8994_hw_params, }; diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index 311774e9ca46..054e0d65db9d 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -154,7 +154,7 @@ static int corgi_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops corgi_ops = { +static const struct snd_soc_ops corgi_ops = { .startup = corgi_startup, .hw_params = corgi_hw_params, .shutdown = corgi_shutdown, diff --git a/sound/soc/pxa/e750_wm9705.c b/sound/soc/pxa/e750_wm9705.c index fdcd94adee7c..82bcbbb1841b 100644 --- a/sound/soc/pxa/e750_wm9705.c +++ b/sound/soc/pxa/e750_wm9705.c @@ -81,7 +81,7 @@ static struct snd_soc_dai_link e750_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9705-aux", + .codec_dai_name = "wm9705-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9705-codec", }, diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c index 2df714f70ec0..1ed8aa2348f1 100644 --- a/sound/soc/pxa/e800_wm9712.c +++ b/sound/soc/pxa/e800_wm9712.c @@ -81,7 +81,7 @@ static struct snd_soc_dai_link e800_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9712-aux", + .codec_dai_name = "wm9712-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9712-codec", }, diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c index 6f2020f6c8d3..e046770ce70e 100644 --- a/sound/soc/pxa/em-x270.c +++ b/sound/soc/pxa/em-x270.c @@ -43,7 +43,7 @@ static struct snd_soc_dai_link em_x270_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9712-aux", + .codec_dai_name = "wm9712-aux", .platform_name = "pxa-pcm-audio", .codec_name = "wm9712-codec", }, diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c index 85483049b916..a9ac881c2e14 100644 --- a/sound/soc/pxa/hx4700.c +++ b/sound/soc/pxa/hx4700.c @@ -79,7 +79,7 @@ static int hx4700_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops hx4700_ops = { +static const struct snd_soc_ops hx4700_ops = { .hw_params = hx4700_hw_params, }; diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c index 9d0e40771ef5..78475376f971 100644 --- a/sound/soc/pxa/imote2.c +++ b/sound/soc/pxa/imote2.c @@ -42,7 +42,7 @@ static int imote2_asoc_hw_params(struct snd_pcm_substream *substream, return ret; } -static struct snd_soc_ops imote2_asoc_ops = { +static const struct snd_soc_ops imote2_asoc_ops = { .hw_params = imote2_asoc_hw_params, }; diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 2d4d4455fe87..2fc012b06c43 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -255,12 +255,12 @@ static int magician_capture_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops magician_capture_ops = { +static const struct snd_soc_ops magician_capture_ops = { .startup = magician_startup, .hw_params = magician_capture_hw_params, }; -static struct snd_soc_ops magician_playback_ops = { +static const struct snd_soc_ops magician_playback_ops = { .startup = magician_startup, .hw_params = magician_playback_hw_params, }; diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index 8760a6687885..c4c6fbedc723 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -157,7 +157,7 @@ static struct snd_soc_dai_link mioa701_dai[] = { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai_name = "pxa2xx-ac97-aux", - .codec_dai_name ="wm9713-aux", + .codec_dai_name = "wm9713-aux", .codec_name = "wm9713-codec", .platform_name = "pxa-pcm-audio", .ops = &mioa701_ops, diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c index 96df9b2d8fc4..5b5f1a442891 100644 --- a/sound/soc/pxa/mmp-pcm.c +++ b/sound/soc/pxa/mmp-pcm.c @@ -166,7 +166,6 @@ static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm) buf->area = NULL; } - return; } static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index ca8b23f8c525..9cc35012e6e5 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -119,7 +119,6 @@ static void mmp_sspa_shutdown(struct snd_pcm_substream *substream, clk_disable(priv->sspa->clk); clk_disable(priv->sysclk); - return; } /* diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index a879aba0691f..b6693f32fc02 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -129,7 +129,7 @@ static int poodle_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops poodle_ops = { +static const struct snd_soc_ops poodle_ops = { .startup = poodle_startup, .hw_params = poodle_hw_params, .shutdown = poodle_shutdown, diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 3cad990dad2c..0291c7cb64eb 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -354,6 +354,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, if (ssp->type == PXA3xx_SSP) { u32 val; u64 tmp = 19968; + tmp *= 1000000; do_div(tmp, freq_out); val = tmp; @@ -590,13 +591,13 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, if ((pxa_ssp_get_scr(ssp) == 4) && (width == 16)) { /* This is a special case where the bitclk is 64fs - * and we're not dealing with 2*32 bits of audio - * samples. - * - * The SSP values used for that are all found out by - * trying and failing a lot; some of the registers - * needed for that mode are only available on PXA3xx. - */ + * and we're not dealing with 2*32 bits of audio + * samples. + * + * The SSP values used for that are all found out by + * trying and failing a lot; some of the registers + * needed for that mode are only available on PXA3xx. + */ if (ssp->type != PXA3xx_SSP) return -EINVAL; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 2e2fb1838ec2..f49bf02e5ec2 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -140,9 +140,8 @@ static int pxa2xx_ac97_mic_startup(struct snd_pcm_substream *substream, { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; - else - snd_soc_dai_set_dma_data(cpu_dai, substream, - &pxa2xx_ac97_pcm_mic_mono_in); + snd_soc_dai_set_dma_data(cpu_dai, substream, + &pxa2xx_ac97_pcm_mic_mono_in); return 0; } diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 0389cf7b4b1e..3fb60baf6eab 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -46,10 +46,10 @@ #define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */ #define SACR0_EFWR (1 << 4) /* Enable EFWR Function */ #define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */ -#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ +#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ #define SACR0_ENB (1 << 0) /* Enable I2S Link */ #define SACR1_ENLBF (1 << 5) /* Enable Loopback */ -#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ +#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ #define SACR1_DREC (1 << 3) /* Disable Recording Function */ #define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */ @@ -60,7 +60,7 @@ #define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */ #define SASR0_BSY (1 << 2) /* I2S Busy */ #define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */ -#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ +#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ #define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */ #define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */ @@ -119,7 +119,7 @@ static int pxa_i2s_wait(void) int i; /* flush the Rx FIFO */ - for(i = 0; i < 16; i++) + for (i = 0; i < 16; i++) SADR; return 0; } diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 410d48b93031..b51d7a0755d5 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -85,7 +85,7 @@ static int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd) } static struct snd_soc_platform_driver pxa2xx_soc_platform = { - .ops = &pxa2xx_pcm_ops, + .ops = &pxa2xx_pcm_ops, .pcm_new = pxa2xx_soc_pcm_new, .pcm_free = pxa2xx_pcm_free_dma_buffers, }; diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c index 552b763005ed..111a907c4eb9 100644 --- a/sound/soc/pxa/raumfeld.c +++ b/sound/soc/pxa/raumfeld.c @@ -132,7 +132,7 @@ static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops raumfeld_cs4270_ops = { +static const struct snd_soc_ops raumfeld_cs4270_ops = { .startup = raumfeld_cs4270_startup, .shutdown = raumfeld_cs4270_shutdown, .hw_params = raumfeld_cs4270_hw_params, @@ -228,14 +228,12 @@ static struct snd_soc_ops raumfeld_ak4104_ops = { .codec_name = "spi0.0", \ } -static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = -{ +static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = { DAI_LINK_CS4270, DAI_LINK_AK4104, }; -static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = -{ +static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = { DAI_LINK_CS4270, }; diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index 07d77cddac60..1671da648e95 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -156,7 +156,7 @@ static int spitz_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops spitz_ops = { +static const struct snd_soc_ops spitz_ops = { .startup = spitz_startup, .hw_params = spitz_hw_params, }; @@ -230,8 +230,8 @@ static const struct snd_soc_dapm_route spitz_audio_map[] = { {"Headset Jack", NULL, "ROUT1"}, /* ext speaker connected to LOUT2, ROUT2 */ - {"Ext Spk", NULL , "ROUT2"}, - {"Ext Spk", NULL , "LOUT2"}, + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, /* mic is connected to input 1 - with bias */ {"LINPUT1", NULL, "Mic Bias"}, diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index e022b2a777f6..ae9c12e1ea2a 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -85,7 +85,7 @@ static int tosa_startup(struct snd_pcm_substream *substream) return 0; } -static struct snd_soc_ops tosa_ops = { +static const struct snd_soc_ops tosa_ops = { .startup = tosa_startup, }; @@ -133,7 +133,7 @@ static int tosa_set_spk(struct snd_kcontrol *kcontrol, static int tosa_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0); + gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0); return 0; } diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c index 990b1aa6d7f6..5b0eccd2b4dd 100644 --- a/sound/soc/pxa/z2.c +++ b/sound/soc/pxa/z2.c @@ -119,8 +119,8 @@ static const struct snd_soc_dapm_route z2_audio_map[] = { {"Headphone Jack", NULL, "ROUT1"}, /* ext speaker connected to LOUT2, ROUT2 */ - {"Ext Spk", NULL , "ROUT2"}, - {"Ext Spk", NULL , "LOUT2"}, + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, /* mic is connected to R input 2 - with bias */ {"RINPUT2", NULL, "Mic Bias"}, @@ -152,7 +152,7 @@ err: return ret; } -static struct snd_soc_ops z2_ops = { +static const struct snd_soc_ops z2_ops = { .hw_params = z2_hw_params, }; diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c index 6fbcdf02c88d..ba468e560dd2 100644 --- a/sound/soc/pxa/zylonite.c +++ b/sound/soc/pxa/zylonite.c @@ -132,7 +132,7 @@ static int zylonite_voice_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops zylonite_voice_ops = { +static const struct snd_soc_ops zylonite_voice_ops = { .hw_params = zylonite_voice_hw_params, }; diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c index 8aed72be3224..8a74844d99e2 100644 --- a/sound/soc/qcom/lpass-apq8016.c +++ b/sound/soc/qcom/lpass-apq8016.c @@ -231,6 +231,18 @@ static struct lpass_variant apq8016_data = { .wrdma_channels = 2, .dai_driver = apq8016_lpass_cpu_dai_driver, .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver), + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk0", + "mi2s-osr-clk1", + "mi2s-osr-clk2", + "mi2s-osr-clk3", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk0", + "mi2s-bit-clk1", + "mi2s-bit-clk2", + "mi2s-bit-clk3", + }, .init = apq8016_lpass_init, .exit = apq8016_lpass_exit, .alloc_dma_channel = apq8016_lpass_alloc_dma_channel, diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 5202a584e0c6..292b103abada 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -429,7 +429,6 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) struct lpass_variant *variant; struct device *dev = &pdev->dev; const struct of_device_id *match; - char clk_name[16]; int ret, i, dai_id; dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); @@ -477,31 +476,24 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) for (i = 0; i < variant->num_dai; i++) { dai_id = variant->dai_driver[i].id; - if (variant->num_dai > 1) - sprintf(clk_name, "mi2s-osr-clk%d", i); - else - sprintf(clk_name, "mi2s-osr-clk"); - drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(&pdev->dev, - clk_name); + variant->dai_osr_clk_names[i]); if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) { dev_warn(&pdev->dev, - "error getting optional mi2s-osr-clk: %ld\n", + "%s() error getting optional %s: %ld\n", + __func__, + variant->dai_osr_clk_names[i], PTR_ERR(drvdata->mi2s_osr_clk[dai_id])); drvdata->mi2s_osr_clk[dai_id] = NULL; } - if (variant->num_dai > 1) - sprintf(clk_name, "mi2s-bit-clk%d", i); - else - sprintf(clk_name, "mi2s-bit-clk"); - drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(&pdev->dev, - clk_name); + variant->dai_bit_clk_names[i]); if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) { dev_err(&pdev->dev, - "error getting mi2s-bit-clk: %ld\n", + "error getting %s: %ld\n", + variant->dai_bit_clk_names[i], PTR_ERR(drvdata->mi2s_bit_clk[dai_id])); return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]); } diff --git a/sound/soc/qcom/lpass-ipq806x.c b/sound/soc/qcom/lpass-ipq806x.c index 608c1a92af8a..ca1e1f2d2787 100644 --- a/sound/soc/qcom/lpass-ipq806x.c +++ b/sound/soc/qcom/lpass-ipq806x.c @@ -92,6 +92,12 @@ static struct lpass_variant ipq806x_data = { .wrdma_channels = 4, .dai_driver = &ipq806x_lpass_cpu_dai_driver, .num_dai = 1, + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk", + }, .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel, .free_dma_channel = ipq806x_lpass_free_dma_channel, }; diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h index 9b031352ea3c..b848db2d6c3d 100644 --- a/sound/soc/qcom/lpass.h +++ b/sound/soc/qcom/lpass.h @@ -91,6 +91,8 @@ struct lpass_variant { /* SOC specific dais */ struct snd_soc_dai_driver *dai_driver; int num_dai; + const char * const *dai_osr_clk_names; + const char * const *dai_bit_clk_names; }; /* register the platform driver from the CPU DAI driver */ diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index b60abf322ce1..dbc53e48c52c 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -93,6 +93,9 @@ static int rk_hw_params(struct snd_pcm_substream *substream, case 96000: mclk = 12288000; break; + case 192000: + mclk = 24576000; + break; case 11025: case 22050: case 44100: diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index f1f1d7959a1b..0520f5afd7cc 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -185,6 +185,14 @@ config SND_SOC_SNOW Say Y if you want to add audio support for various Snow boards based on Exynos5 series of SoCs. +config SND_SOC_ODROID + tristate "Audio support for Odroid XU3/XU4" + depends on SND_SOC_SAMSUNG && I2C + select SND_SOC_MAX98090 + select SND_SAMSUNG_I2S + help + Say Y here to enable audio support for the Odroid XU3/XU4. + config SND_SOC_ARNDALE_RT5631_ALC5631 tristate "Audio support for RT5631(ALC5631) on Arndale Board" depends on I2C diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index b5df5e2e3d94..b6c2ee358333 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -40,6 +40,7 @@ snd-soc-tobermory-objs := tobermory.o snd-soc-lowland-objs := lowland.o snd-soc-littlemill-objs := littlemill.o snd-soc-bells-objs := bells.o +snd-soc-odroid-objs := odroid.o snd-soc-arndale-rt5631-objs := arndale_rt5631.o snd-soc-tm2-wm5110-objs := tm2_wm5110.o @@ -62,5 +63,6 @@ obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o +obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o diff --git a/sound/soc/samsung/bells.c b/sound/soc/samsung/bells.c index 3dd246fa0059..34deba461ae1 100644 --- a/sound/soc/samsung/bells.c +++ b/sound/soc/samsung/bells.c @@ -446,7 +446,6 @@ static struct snd_soc_card bells_cards[] = { }, }; - static int bells_probe(struct platform_device *pdev) { int ret; diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 9170c311d66e..fe6914005494 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -160,5 +160,3 @@ #define I2SSIZE_SHIFT (16) #endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */ - - diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 52a47ed292a4..af3ba4d4ccc5 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -1242,7 +1242,6 @@ static int samsung_i2s_probe(struct platform_device *pdev) i2s_dai_data = (struct samsung_i2s_dai_data *) platform_get_device_id(pdev)->driver_data; - pri_dai = i2s_alloc_dai(pdev, false); if (!pri_dai) { dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c new file mode 100644 index 000000000000..0c0b00e40646 --- /dev/null +++ b/sound/soc/samsung/odroid.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2017 Samsung Electronics Co., 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 <linux/clk.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "i2s.h" +#include "i2s-regs.h" + +struct odroid_priv { + struct snd_soc_card card; + struct snd_soc_dai_link dai_link; + + struct clk *pll; + struct clk *rclk; +}; + +static int odroid_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + return 0; +} + +static int odroid_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card); + unsigned int pll_freq, rclk_freq; + int ret; + + switch (params_rate(params)) { + case 32000: + case 64000: + pll_freq = 131072000U; + break; + case 44100: + case 88200: + case 176400: + pll_freq = 180633600U; + break; + case 48000: + case 96000: + case 192000: + pll_freq = 196608000U; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(priv->pll, pll_freq + 1); + if (ret < 0) + return ret; + + rclk_freq = params_rate(params) * 256 * 4; + + ret = clk_set_rate(priv->rclk, rclk_freq); + if (ret < 0) + return ret; + + if (rtd->num_codecs > 1) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[1]; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct snd_soc_ops odroid_card_ops = { + .startup = odroid_card_startup, + .hw_params = odroid_card_hw_params, +}; + +static void odroid_put_codec_of_nodes(struct snd_soc_dai_link *link) +{ + struct snd_soc_dai_link_component *component = link->codecs; + int i; + + for (i = 0; i < link->num_codecs; i++, component++) { + if (!component->of_node) + break; + of_node_put(component->of_node); + } +} + +static int odroid_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *cpu, *codec; + struct odroid_priv *priv; + struct snd_soc_dai_link *link; + struct snd_soc_card *card; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = &priv->card; + card->dev = dev; + + card->owner = THIS_MODULE; + card->fully_routed = true; + + snd_soc_card_set_drvdata(card, priv); + + priv->pll = devm_clk_get(dev, "epll"); + if (IS_ERR(priv->pll)) + return PTR_ERR(priv->pll); + + priv->rclk = devm_clk_get(dev, "i2s_rclk"); + if (IS_ERR(priv->rclk)) + return PTR_ERR(priv->rclk); + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret < 0) + return ret; + + if (of_property_read_bool(dev->of_node, "samsung,audio-widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(card, + "samsung,audio-widgets"); + if (ret < 0) + return ret; + } + + if (of_property_read_bool(dev->of_node, "samsung,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, + "samsung,audio-routing"); + if (ret < 0) + return ret; + } + + link = &priv->dai_link; + + link->ops = &odroid_card_ops; + link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + card->dai_link = &priv->dai_link; + card->num_links = 1; + + cpu = of_get_child_by_name(dev->of_node, "cpu"); + codec = of_get_child_by_name(dev->of_node, "codec"); + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!link->cpu_of_node) { + dev_err(dev, "Failed parsing cpu/sound-dai property\n"); + return -EINVAL; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) + goto err_put_codec_n; + + link->platform_of_node = link->cpu_of_node; + + link->name = "Primary"; + link->stream_name = link->name; + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + dev_err(dev, "snd_soc_register_card() failed: %d\n", ret); + goto err_put_i2s_n; + } + + return 0; + +err_put_i2s_n: + of_node_put(link->cpu_of_node); +err_put_codec_n: + odroid_put_codec_of_nodes(link); + return ret; +} + +static int odroid_audio_remove(struct platform_device *pdev) +{ + struct odroid_priv *priv = platform_get_drvdata(pdev); + + of_node_put(priv->dai_link.cpu_of_node); + odroid_put_codec_of_nodes(&priv->dai_link); + + return 0; +} + +static const struct of_device_id odroid_audio_of_match[] = { + { .compatible = "samsung,odroid-xu3-audio" }, + { .compatible = "samsung,odroid-xu4-audio"}, + { }, +}; +MODULE_DEVICE_TABLE(of, odroid_audio_of_match); + +static struct platform_driver odroid_audio_driver = { + .driver = { + .name = "odroid-audio", + .of_match_table = odroid_audio_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = odroid_audio_probe, + .remove = odroid_audio_remove, +}; +module_platform_driver(odroid_audio_driver); + +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_DESCRIPTION("Odroid XU3/XU4 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index 644f186fd35c..8f42deaa184b 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -72,7 +72,6 @@ static inline void dbg_showcon(const char *fn, u32 con) } #endif - /* Turn on or off the transmission path. */ static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) { diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index 85a33ac0a5c4..66203d107a11 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -43,6 +43,7 @@ struct rsnd_adg { }; #define LRCLK_ASYNC (1 << 0) +#define AUDIO_OUT_48 (1 << 1) #define adg_mode_flags(adg) (adg->flags) #define for_each_rsnd_clk(pos, adg, i) \ @@ -364,7 +365,10 @@ found_clock: rsnd_adg_set_ssi_clk(ssi_mod, data); - if (!(adg_mode_flags(adg) & LRCLK_ASYNC)) { + if (adg_mode_flags(adg) & LRCLK_ASYNC) { + if (adg_mode_flags(adg) & AUDIO_OUT_48) + ckr = 0x80000000; + } else { if (0 == (rate % 8000)) ckr = 0x80000000; } @@ -427,11 +431,14 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, struct clk *clk; struct device *dev = rsnd_priv_to_dev(priv); struct device_node *np = dev->of_node; + struct property *prop; u32 ckr, rbgx, rbga, rbgb; - u32 rate, req_rate = 0, div; + u32 rate, div; +#define REQ_SIZE 2 + u32 req_rate[REQ_SIZE] = {}; uint32_t count = 0; unsigned long req_48kHz_rate, req_441kHz_rate; - int i; + int i, req_size; const char *parent_clk_name = NULL; static const char * const clkout_name[] = { [CLKOUT] = "audio_clkout", @@ -446,19 +453,32 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, [CLKI] = 0x2, }; - of_property_read_u32(np, "#clock-cells", &count); + ckr = 0; + rbga = 2; /* default 1/6 */ + rbgb = 2; /* default 1/6 */ /* * ADG supports BRRA/BRRB output only * this means all clkout0/1/2/3 will be same rate */ - of_property_read_u32(np, "clock-frequency", &req_rate); + prop = of_find_property(np, "clock-frequency", NULL); + if (!prop) + goto rsnd_adg_get_clkout_end; + + req_size = prop->length / sizeof(u32); + + of_property_read_u32_array(np, "clock-frequency", req_rate, req_size); req_48kHz_rate = 0; req_441kHz_rate = 0; - if (0 == (req_rate % 44100)) - req_441kHz_rate = req_rate; - if (0 == (req_rate % 48000)) - req_48kHz_rate = req_rate; + for (i = 0; i < req_size; i++) { + if (0 == (req_rate[i] % 44100)) + req_441kHz_rate = req_rate[i]; + if (0 == (req_rate[i] % 48000)) + req_48kHz_rate = req_rate[i]; + } + + if (req_rate[0] % 48000 == 0) + adg->flags = AUDIO_OUT_48; /* * This driver is assuming that AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC @@ -469,9 +489,6 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, * rsnd_adg_ssi_clk_try_start() * rsnd_ssi_master_clk_start() */ - ckr = 0; - rbga = 2; /* default 1/6 */ - rbgb = 2; /* default 1/6 */ adg->rbga_rate_for_441khz = 0; adg->rbgb_rate_for_48khz = 0; for_each_rsnd_clk(clk, adg, i) { @@ -505,10 +522,8 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, rbgb = rbgx; adg->rbgb_rate_for_48khz = rate / div; ckr |= brg_table[i] << 16; - if (req_48kHz_rate) { + if (req_48kHz_rate) parent_clk_name = __clk_get_name(clk); - ckr |= 0x80000000; - } } } } @@ -518,12 +533,13 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, * this means all clkout0/1/2/3 will be * same rate */ + of_property_read_u32(np, "#clock-cells", &count); /* * for clkout */ if (!count) { clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT], - parent_clk_name, 0, req_rate); + parent_clk_name, 0, req_rate[0]); if (!IS_ERR(clk)) { adg->clkout[CLKOUT] = clk; of_clk_add_provider(np, of_clk_src_simple_get, clk); @@ -536,19 +552,18 @@ static void rsnd_adg_get_clkout(struct rsnd_priv *priv, for (i = 0; i < CLKOUTMAX; i++) { clk = clk_register_fixed_rate(dev, clkout_name[i], parent_clk_name, 0, - req_rate); - if (!IS_ERR(clk)) { - adg->onecell.clks = adg->clkout; - adg->onecell.clk_num = CLKOUTMAX; - + req_rate[0]); + adg->clkout[i] = ERR_PTR(-ENOENT); + if (!IS_ERR(clk)) adg->clkout[i] = clk; - - of_clk_add_provider(np, of_clk_src_onecell_get, - &adg->onecell); - } } + adg->onecell.clks = adg->clkout; + adg->onecell.clk_num = CLKOUTMAX; + of_clk_add_provider(np, of_clk_src_onecell_get, + &adg->onecell); } +rsnd_adg_get_clkout_end: adg->ckr = ckr; adg->rbga = rbga; adg->rbgb = rbgb; @@ -564,6 +579,7 @@ int rsnd_adg_probe(struct rsnd_priv *priv) struct rsnd_adg *adg; struct device *dev = rsnd_priv_to_dev(priv); struct device_node *np = dev->of_node; + int ret; adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); if (!adg) { @@ -571,8 +587,10 @@ int rsnd_adg_probe(struct rsnd_priv *priv) return -ENOMEM; } - rsnd_mod_init(priv, &adg->mod, &adg_ops, + ret = rsnd_mod_init(priv, &adg->mod, &adg_ops, NULL, NULL, 0, 0); + if (ret) + return ret; rsnd_adg_get_clkin(priv, adg); rsnd_adg_get_clkout(priv, adg); @@ -589,5 +607,10 @@ int rsnd_adg_probe(struct rsnd_priv *priv) void rsnd_adg_remove(struct rsnd_priv *priv) { + struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; + + of_clk_del_provider(np); + rsnd_adg_clk_disable(priv); } diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 47b370cb2d3b..1744015408c3 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -96,7 +96,7 @@ #include <linux/pm_runtime.h> #include "rsnd.h" -#define RSND_RATES SNDRV_PCM_RATE_8000_96000 +#define RSND_RATES SNDRV_PCM_RATE_8000_192000 #define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) static const struct of_device_id rsnd_of_match[] = { @@ -110,7 +110,6 @@ MODULE_DEVICE_TABLE(of, rsnd_of_match); /* * rsnd_mod functions */ -#ifdef DEBUG void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type) { if (mod->type != type) { @@ -121,7 +120,6 @@ void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type) rsnd_mod_name(mod), rsnd_mod_id(mod)); } } -#endif char *rsnd_mod_name(struct rsnd_mod *mod) { @@ -674,12 +672,10 @@ static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) /* set clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_IF: - rdai->bit_clk_inv = rdai->bit_clk_inv; rdai->frm_clk_inv = !rdai->frm_clk_inv; break; case SND_SOC_DAIFMT_IB_NF: rdai->bit_clk_inv = !rdai->bit_clk_inv; - rdai->frm_clk_inv = rdai->frm_clk_inv; break; case SND_SOC_DAIFMT_IB_IF: rdai->bit_clk_inv = !rdai->bit_clk_inv; @@ -1002,13 +998,30 @@ static int rsnd_kctrl_put(struct snd_kcontrol *kctrl, return change; } -static int __rsnd_kctrl_new(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg *cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod)) +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg) +{ + cfg->cfg.val = cfg->val; + + return &cfg->cfg; +} + +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg) +{ + cfg->cfg.val = &cfg->val; + + return &cfg->cfg; +} + +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max) { struct snd_card *card = rtd->card->snd_card; struct snd_kcontrol *kctrl; @@ -1023,6 +1036,9 @@ static int __rsnd_kctrl_new(struct rsnd_mod *mod, }; int ret; + if (size > RSND_MAX_CHANNELS) + return -EINVAL; + kctrl = snd_ctl_new1(&knew, mod); if (!kctrl) return -ENOMEM; @@ -1031,74 +1047,17 @@ static int __rsnd_kctrl_new(struct rsnd_mod *mod, if (ret < 0) return ret; - cfg->update = update; - cfg->card = card; - cfg->kctrl = kctrl; - cfg->io = io; + cfg->texts = texts; + cfg->max = max; + cfg->size = size; + cfg->update = update; + cfg->card = card; + cfg->kctrl = kctrl; + cfg->io = io; return 0; } -void _rsnd_kctrl_remove(struct rsnd_kctrl_cfg *cfg) -{ - if (cfg->card && cfg->kctrl) - snd_ctl_remove(cfg->card, cfg->kctrl); - - cfg->card = NULL; - cfg->kctrl = NULL; -} - -int rsnd_kctrl_new_m(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_m *_cfg, - int ch_size, - u32 max) -{ - if (ch_size > RSND_MAX_CHANNELS) - return -EINVAL; - - _cfg->cfg.max = max; - _cfg->cfg.size = ch_size; - _cfg->cfg.val = _cfg->val; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - -int rsnd_kctrl_new_s(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_s *_cfg, - u32 max) -{ - _cfg->cfg.max = max; - _cfg->cfg.size = 1; - _cfg->cfg.val = &_cfg->val; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - -int rsnd_kctrl_new_e(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg_s *_cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - const char * const *texts, - u32 max) -{ - _cfg->cfg.max = max; - _cfg->cfg.size = 1; - _cfg->cfg.val = &_cfg->val; - _cfg->cfg.texts = texts; - return __rsnd_kctrl_new(mod, io, rtd, name, &_cfg->cfg, update); -} - /* * snd_soc_platform */ diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index cf8f59cdd8d7..463de8360985 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -218,21 +218,6 @@ static int rsnd_dvc_probe_(struct rsnd_mod *mod, return rsnd_cmd_attach(io, rsnd_mod_id(mod)); } -static int rsnd_dvc_remove_(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct rsnd_priv *priv) -{ - struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); - - rsnd_kctrl_remove(dvc->volume); - rsnd_kctrl_remove(dvc->mute); - rsnd_kctrl_remove(dvc->ren); - rsnd_kctrl_remove(dvc->rup); - rsnd_kctrl_remove(dvc->rdown); - - return 0; -} - static int rsnd_dvc_init(struct rsnd_mod *mod, struct rsnd_dai_stream *io, struct rsnd_priv *priv) @@ -300,18 +285,18 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, ret = rsnd_kctrl_new_e(mod, io, rtd, is_play ? "DVC Out Ramp Up Rate" : "DVC In Ramp Up Rate", - &dvc->rup, rsnd_dvc_volume_update, - dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + &dvc->rup, + dvc_ramp_rate); if (ret < 0) return ret; ret = rsnd_kctrl_new_e(mod, io, rtd, is_play ? "DVC Out Ramp Down Rate" : "DVC In Ramp Down Rate", - &dvc->rdown, rsnd_dvc_volume_update, - dvc_ramp_rate, ARRAY_SIZE(dvc_ramp_rate)); + &dvc->rdown, + dvc_ramp_rate); if (ret < 0) return ret; @@ -332,7 +317,6 @@ static struct rsnd_mod_ops rsnd_dvc_ops = { .name = DVC_NAME, .dma_req = rsnd_dvc_dma_req, .probe = rsnd_dvc_probe_, - .remove = rsnd_dvc_remove_, .init = rsnd_dvc_init, .quit = rsnd_dvc_quit, .pcm_new = rsnd_dvc_pcm_new, diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 7410ec0174db..dbf4163427e8 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -611,35 +611,30 @@ struct rsnd_kctrl_cfg_s { u32 val; }; -void _rsnd_kctrl_remove(struct rsnd_kctrl_cfg *cfg); -#define rsnd_kctrl_remove(_cfg) _rsnd_kctrl_remove(&((_cfg).cfg)) - -int rsnd_kctrl_new_m(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_m *_cfg, - int ch_size, - u32 max); -int rsnd_kctrl_new_s(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - struct rsnd_kctrl_cfg_s *_cfg, - u32 max); -int rsnd_kctrl_new_e(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct snd_soc_pcm_runtime *rtd, - const unsigned char *name, - struct rsnd_kctrl_cfg_s *_cfg, - void (*update)(struct rsnd_dai_stream *io, - struct rsnd_mod *mod), - const char * const *texts, - u32 max); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg); +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max); + +#define rsnd_kctrl_new_m(mod, io, rtd, name, update, cfg, size, max) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_m(cfg), \ + NULL, size, max) + +#define rsnd_kctrl_new_s(mod, io, rtd, name, update, cfg, max) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_s(cfg), \ + NULL, 1, max) + +#define rsnd_kctrl_new_e(mod, io, rtd, name, update, cfg, texts) \ + rsnd_kctrl_new(mod, io, rtd, name, update, rsnd_kctrl_init_s(cfg), \ + texts, 1, ARRAY_SIZE(texts)) /* * R-Car SSI @@ -732,8 +727,8 @@ void rsnd_cmd_remove(struct rsnd_priv *priv); int rsnd_cmd_attach(struct rsnd_dai_stream *io, int id); struct rsnd_mod *rsnd_cmd_mod_get(struct rsnd_priv *priv, int id); -#ifdef DEBUG void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type); +#ifdef DEBUG #define rsnd_mod_confirm_ssi(mssi) rsnd_mod_make_sure(mssi, RSND_MOD_SSI) #define rsnd_mod_confirm_src(msrc) rsnd_mod_make_sure(msrc, RSND_MOD_SRC) #define rsnd_mod_confirm_dvc(mdvc) rsnd_mod_make_sure(mdvc, RSND_MOD_DVC) diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 42db48db09ba..20b5b2ec625e 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -167,6 +167,7 @@ static int rsnd_src_hw_params(struct rsnd_mod *mod, * dpcm_fe_dai_hw_params() * dpcm_be_dai_hw_params() */ + src->convert_rate = 0; if (fe->dai_link->dynamic) { int stream = substream->stream; struct snd_soc_dpcm *dpcm; @@ -414,8 +415,6 @@ static int rsnd_src_quit(struct rsnd_mod *mod, rsnd_mod_power_off(mod); - src->convert_rate = 0; - /* reset sync convert_rate */ src->sync.val = 0; diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 411bda2387ad..135c5669f796 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -228,6 +228,15 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) { /* + * It will set SSIWSR.CONT here, but SSICR.CKDV = 000 + * with it is not allowed. (SSIWSR.WS_MODE with + * SSICR.CKDV = 000 is not allowed either). + * Skip it. See SSICR.CKDV + */ + if (j == 0) + continue; + + /* * this driver is assuming that * system word is 32bit x chan * see rsnd_ssi_init() diff --git a/sound/soc/sirf/sirf-audio-port.c b/sound/soc/sirf/sirf-audio-port.c index 3f2cce03275c..be066de74aaa 100644 --- a/sound/soc/sirf/sirf-audio-port.c +++ b/sound/soc/sirf/sirf-audio-port.c @@ -19,6 +19,7 @@ struct sirf_audio_port { static int sirf_audio_port_dai_probe(struct snd_soc_dai *dai) { struct sirf_audio_port *port = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_init_dma_data(dai, &port->playback_dma_data, &port->capture_dma_data); return 0; diff --git a/sound/soc/sirf/sirf-audio.c b/sound/soc/sirf/sirf-audio.c index 94ea152e0362..f2bc50790f76 100644 --- a/sound/soc/sirf/sirf-audio.c +++ b/sound/soc/sirf/sirf-audio.c @@ -27,6 +27,7 @@ static int sirf_audio_hp_event(struct snd_soc_dapm_widget *w, struct snd_soc_card *card = dapm->card; struct sirf_audio_card *sirf_audio_card = snd_soc_card_get_drvdata(card); int on = !SND_SOC_DAPM_EVENT_OFF(event); + if (gpio_is_valid(sirf_audio_card->gpio_hp_pa)) gpio_set_value(sirf_audio_card->gpio_hp_pa, on); return 0; diff --git a/sound/soc/sirf/sirf-usp.c b/sound/soc/sirf/sirf-usp.c index 45fc06c0e0e5..77e7dcf969d0 100644 --- a/sound/soc/sirf/sirf-usp.c +++ b/sound/soc/sirf/sirf-usp.c @@ -71,6 +71,7 @@ static void sirf_usp_rx_disable(struct sirf_usp *usp) static int sirf_usp_pcm_dai_probe(struct snd_soc_dai *dai) { struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_init_dma_data(dai, &usp->playback_dma_data, &usp->capture_dma_data); return 0; @@ -294,6 +295,7 @@ static struct snd_soc_dai_driver sirf_usp_pcm_dai = { static int sirf_usp_pcm_runtime_suspend(struct device *dev) { struct sirf_usp *usp = dev_get_drvdata(dev); + clk_disable_unprepare(usp->clk); return 0; } @@ -302,6 +304,7 @@ static int sirf_usp_pcm_runtime_resume(struct device *dev) { struct sirf_usp *usp = dev_get_drvdata(dev); int ret; + ret = clk_prepare_enable(usp->clk); if (ret) { dev_err(dev, "clk_enable failed: %d\n", ret); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 2722bb0c5573..525f2f397b4c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1776,7 +1776,6 @@ static int soc_bind_aux_dev(struct snd_soc_card *card, int num) } component->init = aux_dev->init; - component->auxiliary = 1; list_add(&component->card_aux_list, &card->aux_comp_list); return 0; @@ -1788,14 +1787,13 @@ err_defer: static int soc_probe_aux_devices(struct snd_soc_card *card) { - struct snd_soc_component *comp, *tmp; + struct snd_soc_component *comp; int order; int ret; for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { - list_for_each_entry_safe(comp, tmp, &card->aux_comp_list, - card_aux_list) { + list_for_each_entry(comp, &card->aux_comp_list, card_aux_list) { if (comp->driver->probe_order == order) { ret = soc_probe_component(card, comp); if (ret < 0) { @@ -1804,7 +1802,6 @@ static int soc_probe_aux_devices(struct snd_soc_card *card) comp->name, ret); return ret; } - list_del(&comp->card_aux_list); } } } @@ -1820,14 +1817,12 @@ static void soc_remove_aux_devices(struct snd_soc_card *card) for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) { list_for_each_entry_safe(comp, _comp, - &card->component_dev_list, card_list) { - - if (!comp->auxiliary) - continue; + &card->aux_comp_list, card_aux_list) { if (comp->driver->remove_order == order) { soc_remove_component(comp); - comp->auxiliary = 0; + /* remove it from the card's aux_comp_list */ + list_del(&comp->card_aux_list); } } } @@ -1918,6 +1913,7 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); +#ifdef CONFIG_DMI /* Trim special characters, and replace '-' with '_' since '-' is used to * separate different DMI fields in the card long name. Only number and * alphabet characters and a few separator characters are kept. @@ -2049,6 +2045,7 @@ int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour) return 0; } EXPORT_SYMBOL_GPL(snd_soc_set_dmi_name); +#endif /* CONFIG_DMI */ static int snd_soc_instantiate_card(struct snd_soc_card *card) { @@ -2190,6 +2187,9 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, card->num_of_dapm_routes); + /* try to set some sane longname if DMI is available */ + snd_soc_set_dmi_name(card, NULL); + snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), "%s", card->name); snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), @@ -3139,7 +3139,7 @@ static int snd_soc_component_initialize(struct snd_soc_component *component, component->suspend = component->driver->suspend; component->resume = component->driver->resume; component->pcm_new = component->driver->pcm_new; - component->pcm_free= component->driver->pcm_free; + component->pcm_free = component->driver->pcm_free; dapm = &component->dapm; dapm->dev = dev; @@ -3240,6 +3240,11 @@ static void snd_soc_component_cleanup(struct snd_soc_component *component) static void snd_soc_component_del_unlocked(struct snd_soc_component *component) { + struct snd_soc_card *card = component->card; + + if (card) + snd_soc_unregister_card(card); + list_del(&component->list); } diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index fbaa1bb41102..7daf21fee355 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -19,9 +19,28 @@ #include <linux/workqueue.h> #include <linux/delay.h> #include <linux/export.h> +#include <linux/suspend.h> #include <trace/events/asoc.h> /** + * snd_soc_codec_set_jack - configure codec jack. + * @codec: CODEC + * @jack: structure to use for the jack + * @data: can be used if codec driver need extra data for configuring jack + * + * Configures and enables jack detection function. + */ +int snd_soc_codec_set_jack(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, void *data) +{ + if (codec->driver->set_jack) + return codec->driver->set_jack(codec, jack, data); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_jack); + +/** * snd_soc_card_jack_new - Create a new jack * @card: ASoC card * @id: an identifying string for this jack @@ -293,6 +312,27 @@ static void gpio_work(struct work_struct *work) snd_soc_jack_gpio_detect(gpio); } +static int snd_soc_jack_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct snd_soc_jack_gpio *gpio = + container_of(nb, struct snd_soc_jack_gpio, pm_notifier); + + switch (action) { + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * Use workqueue so we do not have to care about running + * concurrently with work triggered by the interrupt handler. + */ + queue_delayed_work(system_power_efficient_wq, &gpio->work, 0); + break; + } + + return NOTIFY_DONE; +} + /** * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack * @@ -369,6 +409,13 @@ got_gpio: i, ret); } + /* + * Register PM notifier so we do not miss state transitions + * happening while system is asleep. + */ + gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier; + register_pm_notifier(&gpios[i].pm_notifier); + /* Expose GPIO value over sysfs for diagnostic purposes */ gpiod_export(gpios[i].desc, false); @@ -428,6 +475,7 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, for (i = 0; i < count; i++) { gpiod_unexport(gpios[i].desc); + unregister_pm_notifier(&gpios[i].pm_notifier); free_irq(gpiod_to_irq(gpios[i].desc), &gpios[i]); cancel_delayed_work_sync(&gpios[i].work); gpiod_put(gpios[i].desc); diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 058bc99c6c34..002772e3ba2c 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -495,12 +495,13 @@ static void remove_widget(struct snd_soc_component *comp, struct snd_kcontrol *kcontrol = w->kcontrols[i]; struct soc_enum *se = (struct soc_enum *)kcontrol->private_value; + int j; snd_ctl_remove(card, kcontrol); kfree(se->dobj.control.dvalues); - for (i = 0; i < se->items; i++) - kfree(se->dobj.control.dtexts[i]); + for (j = 0; j < se->items; j++) + kfree(se->dobj.control.dtexts[j]); kfree(se); } diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index d7e8dd46d2cc..d8b6936e544e 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1074,7 +1074,7 @@ int uni_player_init(struct platform_device *pdev, player->clk = of_clk_get(pdev->dev.of_node, 0); if (IS_ERR(player->clk)) { dev_err(player->dev, "Failed to get clock\n"); - ret = PTR_ERR(player->clk); + return PTR_ERR(player->clk); } /* Select the frequency synthesizer clock */ diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig new file mode 100644 index 000000000000..972970f0890a --- /dev/null +++ b/sound/soc/stm/Kconfig @@ -0,0 +1,8 @@ +menuconfig SND_SOC_STM32 + tristate "STMicroelectronics STM32 SOC audio support" + depends on ARCH_STM32 || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to enable ASoC-support for STM32 diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile new file mode 100644 index 000000000000..e466a4759698 --- /dev/null +++ b/sound/soc/stm/Makefile @@ -0,0 +1,6 @@ +# SAI +snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o +obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai-sub.o + +snd-soc-stm32-sai-objs := stm32_sai.o +obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai.o diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c new file mode 100644 index 000000000000..2a27a26bf7a1 --- /dev/null +++ b/sound/soc/stm/stm32_sai.c @@ -0,0 +1,115 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * 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 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. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/reset.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/core.h> + +#include "stm32_sai.h" + +static const struct of_device_id stm32_sai_ids[] = { + { .compatible = "st,stm32f4-sai", .data = (void *)SAI_STM32F4 }, + {} +}; + +static int stm32_sai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct stm32_sai_data *sai; + struct reset_control *rst; + struct resource *res; + void __iomem *base; + const struct of_device_id *of_id; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + of_id = of_match_device(stm32_sai_ids, &pdev->dev); + if (of_id) + sai->version = (enum stm32_sai_version)of_id->data; + else + return -EINVAL; + + sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k"); + if (IS_ERR(sai->clk_x8k)) { + dev_err(&pdev->dev, "missing x8k parent clock\n"); + return PTR_ERR(sai->clk_x8k); + } + + sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k"); + if (IS_ERR(sai->clk_x11k)) { + dev_err(&pdev->dev, "missing x11k parent clock\n"); + return PTR_ERR(sai->clk_x11k); + } + + /* init irqs */ + sai->irq = platform_get_irq(pdev, 0); + if (sai->irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return sai->irq; + } + + /* reset */ + rst = reset_control_get(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + sai->pdev = pdev; + platform_set_drvdata(pdev, sai); + + return of_platform_populate(np, NULL, NULL, &pdev->dev); +} + +static int stm32_sai_remove(struct platform_device *pdev) +{ + of_platform_depopulate(&pdev->dev); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_sai_ids); + +static struct platform_driver stm32_sai_driver = { + .driver = { + .name = "st,stm32-sai", + .of_match_table = stm32_sai_ids, + }, + .probe = stm32_sai_probe, + .remove = stm32_sai_remove, +}; + +module_platform_driver(stm32_sai_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:st,stm32-sai"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h new file mode 100644 index 000000000000..a801fda5066f --- /dev/null +++ b/sound/soc/stm/stm32_sai.h @@ -0,0 +1,200 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * 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 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. + */ + +/******************** SAI Register Map **************************************/ + +/* common register */ +#define STM_SAI_GCR 0x00 + +/* Sub-block A&B registers offsets, relative to A&B sub-block addresses */ +#define STM_SAI_CR1_REGX 0x00 /* A offset: 0x04. B offset: 0x24 */ +#define STM_SAI_CR2_REGX 0x04 +#define STM_SAI_FRCR_REGX 0x08 +#define STM_SAI_SLOTR_REGX 0x0C +#define STM_SAI_IMR_REGX 0x10 +#define STM_SAI_SR_REGX 0x14 +#define STM_SAI_CLRFR_REGX 0x18 +#define STM_SAI_DR_REGX 0x1C + +/******************** Bit definition for SAI_GCR register *******************/ +#define SAI_GCR_SYNCIN_SHIFT 0 +#define SAI_GCR_SYNCIN_MASK GENMASK(1, SAI_GCR_SYNCIN_SHIFT) +#define SAI_GCR_SYNCIN_SET(x) ((x) << SAI_GCR_SYNCIN_SHIFT) + +#define SAI_GCR_SYNCOUT_SHIFT 4 +#define SAI_GCR_SYNCOUT_MASK GENMASK(5, SAI_GCR_SYNCOUT_SHIFT) +#define SAI_GCR_SYNCOUT_SET(x) ((x) << SAI_GCR_SYNCOUT_SHIFT) + +/******************* Bit definition for SAI_XCR1 register *******************/ +#define SAI_XCR1_RX_TX_SHIFT 0 +#define SAI_XCR1_RX_TX BIT(SAI_XCR1_RX_TX_SHIFT) +#define SAI_XCR1_SLAVE_SHIFT 1 +#define SAI_XCR1_SLAVE BIT(SAI_XCR1_SLAVE_SHIFT) + +#define SAI_XCR1_PRTCFG_SHIFT 2 +#define SAI_XCR1_PRTCFG_MASK GENMASK(3, SAI_XCR1_PRTCFG_SHIFT) +#define SAI_XCR1_PRTCFG_SET(x) ((x) << SAI_XCR1_PRTCFG_SHIFT) + +#define SAI_XCR1_DS_SHIFT 5 +#define SAI_XCR1_DS_MASK GENMASK(7, SAI_XCR1_DS_SHIFT) +#define SAI_XCR1_DS_SET(x) ((x) << SAI_XCR1_DS_SHIFT) + +#define SAI_XCR1_LSBFIRST_SHIFT 8 +#define SAI_XCR1_LSBFIRST BIT(SAI_XCR1_LSBFIRST_SHIFT) +#define SAI_XCR1_CKSTR_SHIFT 9 +#define SAI_XCR1_CKSTR BIT(SAI_XCR1_CKSTR_SHIFT) + +#define SAI_XCR1_SYNCEN_SHIFT 10 +#define SAI_XCR1_SYNCEN_MASK GENMASK(11, SAI_XCR1_SYNCEN_SHIFT) +#define SAI_XCR1_SYNCEN_SET(x) ((x) << SAI_XCR1_SYNCEN_SHIFT) + +#define SAI_XCR1_MONO_SHIFT 12 +#define SAI_XCR1_MONO BIT(SAI_XCR1_MONO_SHIFT) +#define SAI_XCR1_OUTDRIV_SHIFT 13 +#define SAI_XCR1_OUTDRIV BIT(SAI_XCR1_OUTDRIV_SHIFT) +#define SAI_XCR1_SAIEN_SHIFT 16 +#define SAI_XCR1_SAIEN BIT(SAI_XCR1_SAIEN_SHIFT) +#define SAI_XCR1_DMAEN_SHIFT 17 +#define SAI_XCR1_DMAEN BIT(SAI_XCR1_DMAEN_SHIFT) +#define SAI_XCR1_NODIV_SHIFT 19 +#define SAI_XCR1_NODIV BIT(SAI_XCR1_NODIV_SHIFT) + +#define SAI_XCR1_MCKDIV_SHIFT 20 +#define SAI_XCR1_MCKDIV_WIDTH 4 +#define SAI_XCR1_MCKDIV_MASK GENMASK(24, SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_MAX ((1 << SAI_XCR1_MCKDIV_WIDTH) - 1) + +#define SAI_XCR1_OSR_SHIFT 26 +#define SAI_XCR1_OSR BIT(SAI_XCR1_OSR_SHIFT) + +/******************* Bit definition for SAI_XCR2 register *******************/ +#define SAI_XCR2_FTH_SHIFT 0 +#define SAI_XCR2_FTH_MASK GENMASK(2, SAI_XCR2_FTH_SHIFT) +#define SAI_XCR2_FTH_SET(x) ((x) << SAI_XCR2_FTH_SHIFT) + +#define SAI_XCR2_FFLUSH_SHIFT 3 +#define SAI_XCR2_FFLUSH BIT(SAI_XCR2_FFLUSH_SHIFT) +#define SAI_XCR2_TRIS_SHIFT 4 +#define SAI_XCR2_TRIS BIT(SAI_XCR2_TRIS_SHIFT) +#define SAI_XCR2_MUTE_SHIFT 5 +#define SAI_XCR2_MUTE BIT(SAI_XCR2_MUTE_SHIFT) +#define SAI_XCR2_MUTEVAL_SHIFT 6 +#define SAI_XCR2_MUTEVAL BIT(SAI_XCR2_MUTEVAL_SHIFT) + +#define SAI_XCR2_MUTECNT_SHIFT 7 +#define SAI_XCR2_MUTECNT_MASK GENMASK(12, SAI_XCR2_MUTECNT_SHIFT) +#define SAI_XCR2_MUTECNT_SET(x) ((x) << SAI_XCR2_MUTECNT_SHIFT) + +#define SAI_XCR2_CPL_SHIFT 13 +#define SAI_XCR2_CPL BIT(SAI_XCR2_CPL_SHIFT) + +#define SAI_XCR2_COMP_SHIFT 14 +#define SAI_XCR2_COMP_MASK GENMASK(15, SAI_XCR2_COMP_SHIFT) +#define SAI_XCR2_COMP_SET(x) ((x) << SAI_XCR2_COMP_SHIFT) + +/****************** Bit definition for SAI_XFRCR register *******************/ +#define SAI_XFRCR_FRL_SHIFT 0 +#define SAI_XFRCR_FRL_MASK GENMASK(7, SAI_XFRCR_FRL_SHIFT) +#define SAI_XFRCR_FRL_SET(x) ((x) << SAI_XFRCR_FRL_SHIFT) + +#define SAI_XFRCR_FSALL_SHIFT 8 +#define SAI_XFRCR_FSALL_MASK GENMASK(14, SAI_XFRCR_FSALL_SHIFT) +#define SAI_XFRCR_FSALL_SET(x) ((x) << SAI_XFRCR_FSALL_SHIFT) + +#define SAI_XFRCR_FSDEF_SHIFT 16 +#define SAI_XFRCR_FSDEF BIT(SAI_XFRCR_FSDEF_SHIFT) +#define SAI_XFRCR_FSPOL_SHIFT 17 +#define SAI_XFRCR_FSPOL BIT(SAI_XFRCR_FSPOL_SHIFT) +#define SAI_XFRCR_FSOFF_SHIFT 18 +#define SAI_XFRCR_FSOFF BIT(SAI_XFRCR_FSOFF_SHIFT) + +/****************** Bit definition for SAI_XSLOTR register ******************/ + +#define SAI_XSLOTR_FBOFF_SHIFT 0 +#define SAI_XSLOTR_FBOFF_MASK GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT) +#define SAI_XSLOTR_FBOFF_SET(x) ((x) << SAI_XSLOTR_FBOFF_SHIFT) + +#define SAI_XSLOTR_SLOTSZ_SHIFT 6 +#define SAI_XSLOTR_SLOTSZ_MASK GENMASK(7, SAI_XSLOTR_SLOTSZ_SHIFT) +#define SAI_XSLOTR_SLOTSZ_SET(x) ((x) << SAI_XSLOTR_SLOTSZ_SHIFT) + +#define SAI_XSLOTR_NBSLOT_SHIFT 8 +#define SAI_XSLOTR_NBSLOT_MASK GENMASK(11, SAI_XSLOTR_NBSLOT_SHIFT) +#define SAI_XSLOTR_NBSLOT_SET(x) ((x) << SAI_XSLOTR_NBSLOT_SHIFT) + +#define SAI_XSLOTR_SLOTEN_SHIFT 16 +#define SAI_XSLOTR_SLOTEN_WIDTH 16 +#define SAI_XSLOTR_SLOTEN_MASK GENMASK(31, SAI_XSLOTR_SLOTEN_SHIFT) +#define SAI_XSLOTR_SLOTEN_SET(x) ((x) << SAI_XSLOTR_SLOTEN_SHIFT) + +/******************* Bit definition for SAI_XIMR register *******************/ +#define SAI_XIMR_OVRUDRIE BIT(0) +#define SAI_XIMR_MUTEDETIE BIT(1) +#define SAI_XIMR_WCKCFGIE BIT(2) +#define SAI_XIMR_FREQIE BIT(3) +#define SAI_XIMR_CNRDYIE BIT(4) +#define SAI_XIMR_AFSDETIE BIT(5) +#define SAI_XIMR_LFSDETIE BIT(6) + +#define SAI_XIMR_SHIFT 0 +#define SAI_XIMR_MASK GENMASK(6, SAI_XIMR_SHIFT) + +/******************** Bit definition for SAI_XSR register *******************/ +#define SAI_XSR_OVRUDR BIT(0) +#define SAI_XSR_MUTEDET BIT(1) +#define SAI_XSR_WCKCFG BIT(2) +#define SAI_XSR_FREQ BIT(3) +#define SAI_XSR_CNRDY BIT(4) +#define SAI_XSR_AFSDET BIT(5) +#define SAI_XSR_LFSDET BIT(6) + +#define SAI_XSR_SHIFT 0 +#define SAI_XSR_MASK GENMASK(6, SAI_XSR_SHIFT) + +/****************** Bit definition for SAI_XCLRFR register ******************/ +#define SAI_XCLRFR_COVRUDR BIT(0) +#define SAI_XCLRFR_CMUTEDET BIT(1) +#define SAI_XCLRFR_CWCKCFG BIT(2) +#define SAI_XCLRFR_CFREQ BIT(3) +#define SAI_XCLRFR_CCNRDY BIT(4) +#define SAI_XCLRFR_CAFSDET BIT(5) +#define SAI_XCLRFR_CLFSDET BIT(6) + +#define SAI_XCLRFR_SHIFT 0 +#define SAI_XCLRFR_MASK GENMASK(6, SAI_XCLRFR_SHIFT) + +enum stm32_sai_version { + SAI_STM32F4 +}; + +/** + * struct stm32_sai_data - private data of SAI instance driver + * @pdev: device data pointer + * @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz + * @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz + * @version: SOC version + * @irq: SAI interrupt line + */ +struct stm32_sai_data { + struct platform_device *pdev; + struct clk *clk_x8k; + struct clk *clk_x11k; + int version; + int irq; +}; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c new file mode 100644 index 000000000000..ae4706ca265b --- /dev/null +++ b/sound/soc/stm/stm32_sai_sub.c @@ -0,0 +1,884 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * 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 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. + */ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "stm32_sai.h" + +#define SAI_FREE_PROTOCOL 0x0 + +#define SAI_SLOT_SIZE_AUTO 0x0 +#define SAI_SLOT_SIZE_16 0x1 +#define SAI_SLOT_SIZE_32 0x2 + +#define SAI_DATASIZE_8 0x2 +#define SAI_DATASIZE_10 0x3 +#define SAI_DATASIZE_16 0x4 +#define SAI_DATASIZE_20 0x5 +#define SAI_DATASIZE_24 0x6 +#define SAI_DATASIZE_32 0x7 + +#define STM_SAI_FIFO_SIZE 8 +#define STM_SAI_DAI_NAME_SIZE 15 + +#define STM_SAI_IS_PLAYBACK(ip) ((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK) +#define STM_SAI_IS_CAPTURE(ip) ((ip)->dir == SNDRV_PCM_STREAM_CAPTURE) + +#define STM_SAI_A_ID 0x0 +#define STM_SAI_B_ID 0x1 + +#define STM_SAI_BLOCK_NAME(x) (((x)->id == STM_SAI_A_ID) ? "A" : "B") + +/** + * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) + * @pdev: device data pointer + * @regmap: SAI register map pointer + * @dma_params: dma configuration data for rx or tx channel + * @cpu_dai_drv: DAI driver data pointer + * @cpu_dai: DAI runtime data pointer + * @substream: PCM substream data pointer + * @pdata: SAI block parent data pointer + * @sai_ck: kernel clock feeding the SAI clock generator + * @phys_addr: SAI registers physical base address + * @mclk_rate: SAI block master clock frequency (Hz). set at init + * @id: SAI sub block id corresponding to sub-block A or B + * @dir: SAI block direction (playback or capture). set at init + * @master: SAI block mode flag. (true=master, false=slave) set at init + * @fmt: SAI block format. relevant only for custom protocols. set at init + * @sync: SAI block synchronization mode. (none, internal or external) + * @fs_length: frame synchronization length. depends on protocol settings + * @slots: rx or tx slot number + * @slot_width: rx or tx slot width in bits + * @slot_mask: rx or tx active slots mask. set at init or at runtime + * @data_size: PCM data width. corresponds to PCM substream width. + */ +struct stm32_sai_sub_data { + struct platform_device *pdev; + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data dma_params; + struct snd_soc_dai_driver *cpu_dai_drv; + struct snd_soc_dai *cpu_dai; + struct snd_pcm_substream *substream; + struct stm32_sai_data *pdata; + struct clk *sai_ck; + dma_addr_t phys_addr; + unsigned int mclk_rate; + unsigned int id; + int dir; + bool master; + int fmt; + int sync; + int fs_length; + int slots; + int slot_width; + int slot_mask; + int data_size; +}; + +enum stm32_sai_fifo_th { + STM_SAI_FIFO_TH_EMPTY, + STM_SAI_FIFO_TH_QUARTER, + STM_SAI_FIFO_TH_HALF, + STM_SAI_FIFO_TH_3_QUARTER, + STM_SAI_FIFO_TH_FULL, +}; + +static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static const struct regmap_config stm32_sai_sub_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM_SAI_DR_REGX, + .readable_reg = stm32_sai_sub_readable_reg, + .volatile_reg = stm32_sai_sub_volatile_reg, + .writeable_reg = stm32_sai_sub_writeable_reg, + .fast_io = true, +}; + +static irqreturn_t stm32_sai_isr(int irq, void *devid) +{ + struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid; + struct snd_pcm_substream *substream = sai->substream; + struct platform_device *pdev = sai->pdev; + unsigned int sr, imr, flags; + snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING; + + regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr); + regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr); + + flags = sr & imr; + if (!flags) + return IRQ_NONE; + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, + SAI_XCLRFR_MASK); + + if (flags & SAI_XIMR_OVRUDRIE) { + dev_err(&pdev->dev, "IT %s\n", + STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_MUTEDETIE) + dev_dbg(&pdev->dev, "IT mute detected\n"); + + if (flags & SAI_XIMR_WCKCFGIE) { + dev_err(&pdev->dev, "IT wrong clock configuration\n"); + status = SNDRV_PCM_STATE_DISCONNECTED; + } + + if (flags & SAI_XIMR_CNRDYIE) + dev_warn(&pdev->dev, "IT Codec not ready\n"); + + if (flags & SAI_XIMR_AFSDETIE) { + dev_warn(&pdev->dev, "IT Anticipated frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_LFSDETIE) { + dev_warn(&pdev->dev, "IT Late frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (status != SNDRV_PCM_STATE_RUNNING) { + snd_pcm_stream_lock(substream); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock(substream); + } + + return IRQ_HANDLED; +} + +static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + + if ((dir == SND_SOC_CLOCK_OUT) && sai->master) { + sai->mclk_rate = freq; + dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq); + } + + return 0; +} + +static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slotr_mask, slot_size; + + dev_dbg(cpu_dai->dev, "masks tx/rx:%#x/%#x, slots:%d, width:%d\n", + tx_mask, rx_mask, slots, slot_width); + + switch (slot_width) { + case 16: + slot_size = SAI_SLOT_SIZE_16; + break; + case 32: + slot_size = SAI_SLOT_SIZE_32; + break; + default: + slot_size = SAI_SLOT_SIZE_AUTO; + break; + } + + slotr = SAI_XSLOTR_SLOTSZ_SET(slot_size) | + SAI_XSLOTR_NBSLOT_SET(slots - 1); + slotr_mask = SAI_XSLOTR_SLOTSZ_MASK | SAI_XSLOTR_NBSLOT_MASK; + + /* tx/rx mask set in machine init, if slot number defined in DT */ + if (STM_SAI_IS_PLAYBACK(sai)) { + sai->slot_mask = tx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask); + } + + if (STM_SAI_IS_CAPTURE(sai)) { + sai->slot_mask = rx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask); + } + + slotr_mask |= SAI_XSLOTR_SLOTEN_MASK; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr); + + sai->slot_width = slot_width; + sai->slots = slots; + + return 0; +} + +static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1 = 0, frcr = 0; + int cr1_mask = 0, frcr_mask = 0; + int ret; + + dev_dbg(cpu_dai->dev, "fmt %x\n", fmt); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + /* SCK active high for all protocols */ + case SND_SOC_DAIFMT_I2S: + cr1 |= SAI_XCR1_CKSTR; + frcr |= SAI_XFRCR_FSOFF | SAI_XFRCR_FSDEF; + break; + /* Left justified */ + case SND_SOC_DAIFMT_MSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + /* Right justified */ + case SND_SOC_DAIFMT_LSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + case SND_SOC_DAIFMT_DSP_A: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF; + break; + case SND_SOC_DAIFMT_DSP_B: + frcr |= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported protocol %#x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + cr1_mask |= SAI_XCR1_PRTCFG_MASK | SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF | + SAI_XFRCR_FSDEF; + + /* DAI clock strobing. Invert setting previously set */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + cr1 ^= SAI_XCR1_CKSTR; + break; + case SND_SOC_DAIFMT_NB_IF: + frcr ^= SAI_XFRCR_FSPOL; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert fs & sck */ + cr1 ^= SAI_XCR1_CKSTR; + frcr ^= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported strobing %#x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + cr1_mask |= SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL; + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master */ + cr1 |= SAI_XCR1_SLAVE; + sai->master = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + sai->master = true; + break; + default: + dev_err(cpu_dai->dev, "Unsupported mode %#x\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + cr1_mask |= SAI_XCR1_SLAVE; + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + sai->fmt = fmt; + + return 0; +} + +static int stm32_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int imr, cr2, ret; + + sai->substream = substream; + + ret = clk_prepare_enable(sai->sai_ck); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Enable ITs */ + regmap_update_bits(sai->regmap, STM_SAI_SR_REGX, + SAI_XSR_MASK, (unsigned int)~SAI_XSR_MASK); + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, + SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); + + imr = SAI_XIMR_OVRUDRIE; + if (STM_SAI_IS_CAPTURE(sai)) { + regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2); + if (cr2 & SAI_XCR2_MUTECNT_MASK) + imr |= SAI_XIMR_MUTEDETIE; + } + + if (sai->master) + imr |= SAI_XIMR_WCKCFGIE; + else + imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE; + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, + SAI_XIMR_MASK, imr); + + return 0; +} + +static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, cr1_mask, ret; + int fth = STM_SAI_FIFO_TH_HALF; + + /* FIFO config */ + regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_SET(fth)); + + /* Mode, data format and channel config */ + cr1 = SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_16); + break; + case SNDRV_PCM_FORMAT_S32_LE: + cr1 |= SAI_XCR1_DS_SET(SAI_DATASIZE_32); + break; + default: + dev_err(cpu_dai->dev, "Data format not supported"); + return -EINVAL; + } + cr1_mask = SAI_XCR1_DS_MASK | SAI_XCR1_PRTCFG_MASK; + + cr1_mask |= SAI_XCR1_RX_TX; + if (STM_SAI_IS_CAPTURE(sai)) + cr1 |= SAI_XCR1_RX_TX; + + cr1_mask |= SAI_XCR1_MONO; + if ((sai->slots == 2) && (params_channels(params) == 1)) + cr1 |= SAI_XCR1_MONO; + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + /* DMA config */ + sai->dma_params.maxburst = STM_SAI_FIFO_SIZE * fth / sizeof(u32); + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)&sai->dma_params); + + return 0; +} + +static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slot_sz; + + regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr); + + /* + * If SLOTSZ is set to auto in SLOTR, align slot width on data size + * By default slot width = data size, if not forced from DT + */ + slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK; + if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO)) + sai->slot_width = sai->data_size; + + if (sai->slot_width < sai->data_size) { + dev_err(cpu_dai->dev, + "Data size %d larger than slot width\n", + sai->data_size); + return -EINVAL; + } + + /* Slot number is set to 2, if not specified in DT */ + if (!sai->slots) + sai->slots = 2; + + /* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/ + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_NBSLOT_MASK, + SAI_XSLOTR_NBSLOT_SET((sai->slots - 1))); + + /* Set default slots mask if not already set from DT */ + if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) { + sai->slot_mask = (1 << sai->slots) - 1; + regmap_update_bits(sai->regmap, + STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK, + SAI_XSLOTR_SLOTEN_SET(sai->slot_mask)); + } + + dev_dbg(cpu_dai->dev, "slots %d, slot width %d\n", + sai->slots, sai->slot_width); + + return 0; +} + +static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int fs_active, offset, format; + int frcr, frcr_mask; + + format = sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + sai->fs_length = sai->slot_width * sai->slots; + + fs_active = sai->fs_length / 2; + if ((format == SND_SOC_DAIFMT_DSP_A) || + (format == SND_SOC_DAIFMT_DSP_B)) + fs_active = 1; + + frcr = SAI_XFRCR_FRL_SET((sai->fs_length - 1)); + frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1)); + frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK; + + dev_dbg(cpu_dai->dev, "frame length %d, frame active %d\n", + sai->fs_length, fs_active); + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) { + offset = sai->slot_width - sai->data_size; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_FBOFF_MASK, + SAI_XSLOTR_FBOFF_SET(offset)); + } +} + +static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, mask, div = 0; + int sai_clk_rate, ret; + + if (!sai->mclk_rate) { + dev_err(cpu_dai->dev, "Mclk rate is null\n"); + return -EINVAL; + } + + if (!(params_rate(params) % 11025)) + clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); + else + clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); + sai_clk_rate = clk_get_rate(sai->sai_ck); + + /* + * mclk_rate = 256 * fs + * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate + * MCKDIV = sai_ck / (2 * mclk_rate) otherwise + */ + if (2 * sai_clk_rate >= 3 * sai->mclk_rate) + div = DIV_ROUND_CLOSEST(sai_clk_rate, 2 * sai->mclk_rate); + + if (div > SAI_XCR1_MCKDIV_MAX) { + dev_err(cpu_dai->dev, "Divider %d out of range\n", div); + return -EINVAL; + } + dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div); + + mask = SAI_XCR1_MCKDIV_MASK; + cr1 = SAI_XCR1_MCKDIV_SET(div); + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + return 0; +} + +static int stm32_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + sai->data_size = params_width(params); + + ret = stm32_sai_set_slots(cpu_dai); + if (ret < 0) + return ret; + stm32_sai_set_frame(cpu_dai); + + ret = stm32_sai_set_config(cpu_dai, substream, params); + if (ret) + return ret; + + if (sai->master) + ret = stm32_sai_configure_clock(cpu_dai, params); + + return ret; +} + +static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, SAI_XCR1_DMAEN); + + /* Enable SAI */ + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, SAI_XCR1_SAIEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, + (unsigned int)~SAI_XCR1_DMAEN); + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, + (unsigned int)~SAI_XCR1_SAIEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + break; + default: + return -EINVAL; + } + + return ret; +} + +static void stm32_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0); + + clk_disable_unprepare(sai->sai_ck); + sai->substream = NULL; +} + +static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + + sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX); + sai->dma_params.maxburst = 1; + /* Buswidth will be set by framework at runtime */ + sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + + if (STM_SAI_IS_PLAYBACK(sai)) + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL); + else + snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params); + + return 0; +} + +static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { + .set_sysclk = stm32_sai_set_sysclk, + .set_fmt = stm32_sai_set_dai_fmt, + .set_tdm_slot = stm32_sai_set_dai_tdm_slot, + .startup = stm32_sai_startup, + .hw_params = stm32_sai_hw_params, + .trigger = stm32_sai_trigger, + .shutdown = stm32_sai_shutdown, +}; + +static const struct snd_pcm_hardware stm32_sai_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_min = 1024, /* 5ms at 48kHz */ + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, +}; + +static struct snd_soc_dai_driver stm32_sai_playback_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .id = 1, /* avoid call to fmt_single_name() */ + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .id = 1, /* avoid call to fmt_single_name() */ + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct snd_soc_component_driver stm32_component = { + .name = "stm32-sai", +}; + +static const struct of_device_id stm32_sai_sub_ids[] = { + { .compatible = "st,stm32-sai-sub-a", + .data = (void *)STM_SAI_A_ID}, + { .compatible = "st,stm32-sai-sub-b", + .data = (void *)STM_SAI_B_ID}, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_sai_sub_ids); + +static int stm32_sai_sub_parse_of(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + void __iomem *base; + + if (!np) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dev_err(&pdev->dev, "res %pr\n", res); + + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->phys_addr = res->start; + sai->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &stm32_sai_sub_regmap_config); + + /* Get direction property */ + if (of_property_match_string(np, "dma-names", "tx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_PLAYBACK; + } else if (of_property_match_string(np, "dma-names", "rx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_CAPTURE; + } else { + dev_err(&pdev->dev, "Unsupported direction\n"); + return -EINVAL; + } + + sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck"); + if (IS_ERR(sai->sai_ck)) { + dev_err(&pdev->dev, "missing kernel clock sai_ck\n"); + return PTR_ERR(sai->sai_ck); + } + + return 0; +} + +static int stm32_sai_sub_dais_init(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + sai->cpu_dai_drv = devm_kzalloc(&pdev->dev, + sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!sai->cpu_dai_drv) + return -ENOMEM; + + sai->cpu_dai_drv->name = dev_name(&pdev->dev); + if (STM_SAI_IS_PLAYBACK(sai)) { + memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai, + sizeof(stm32_sai_playback_dai)); + sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name; + } else { + memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai, + sizeof(stm32_sai_capture_dai)); + sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name; + } + + return 0; +} + +static int stm32_sai_sub_probe(struct platform_device *pdev) +{ + struct stm32_sai_sub_data *sai; + const struct of_device_id *of_id; + int ret; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + of_id = of_match_device(stm32_sai_sub_ids, &pdev->dev); + if (!of_id) + return -EINVAL; + sai->id = (uintptr_t)of_id->data; + + sai->pdev = pdev; + platform_set_drvdata(pdev, sai); + + sai->pdata = dev_get_drvdata(pdev->dev.parent); + if (!sai->pdata) { + dev_err(&pdev->dev, "Parent device data not available\n"); + return -EINVAL; + } + + ret = stm32_sai_sub_parse_of(pdev, sai); + if (ret) + return ret; + + ret = stm32_sai_sub_dais_init(pdev, sai); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); + if (ret) { + dev_err(&pdev->dev, "irq request returned %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component, + sai->cpu_dai_drv, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &stm32_sai_pcm_config, 0); + if (ret) { + dev_err(&pdev->dev, "could not register pcm dma\n"); + return ret; + } + + return 0; +} + +static struct platform_driver stm32_sai_sub_driver = { + .driver = { + .name = "st,stm32-sai-sub", + .of_match_table = stm32_sai_sub_ids, + }, + .probe = stm32_sai_sub_probe, +}; + +module_platform_driver(stm32_sai_sub_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:st,stm32-sai-sub"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c index 72331332b72e..6c17c99c2c8d 100644 --- a/sound/soc/sunxi/sun8i-codec-analog.c +++ b/sound/soc/sunxi/sun8i-codec-analog.c @@ -252,24 +252,15 @@ static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, ); static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { - /* Mixer pre-gains */ - SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, - SUN8I_ADDA_LINEIN_GCTRL_LINEING, - 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + /* Mixer pre-gain */ SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC1G, 0x7, 0, sun8i_codec_out_mixer_pregain_scale), - SOC_SINGLE_TLV("Mic2 Playback Volume", - SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, - 0x7, 0, sun8i_codec_out_mixer_pregain_scale), - /* Microphone Amp boost gains */ + /* Microphone Amp boost gain */ SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, sun8i_codec_mic_gain_scale), - SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, - SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, - sun8i_codec_mic_gain_scale), /* ADC */ SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, @@ -295,12 +286,8 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { * stream widgets at the card level. */ - /* Line In */ - SND_SOC_DAPM_INPUT("LINEIN"), - - /* Microphone inputs */ + /* Microphone input */ SND_SOC_DAPM_INPUT("MIC1"), - SND_SOC_DAPM_INPUT("MIC2"), /* Microphone Bias */ SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, @@ -310,8 +297,6 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { /* Mic input path */ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), - SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, - SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), /* Mixers */ SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, @@ -335,35 +320,26 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { /* Microphone Routes */ { "Mic1 Amplifier", NULL, "MIC1"}, - { "Mic2 Amplifier", NULL, "MIC2"}, /* Left Mixer Routes */ { "Left Mixer", "DAC Playback Switch", "Left DAC" }, { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, - { "Left Mixer", "Line In Playback Switch", "LINEIN" }, { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, - { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, /* Right Mixer Routes */ { "Right Mixer", "DAC Playback Switch", "Right DAC" }, { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, - { "Right Mixer", "Line In Playback Switch", "LINEIN" }, { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, - { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, /* Left ADC Mixer Routes */ { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, - { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, - { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, /* Right ADC Mixer Routes */ { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, - { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, - { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, /* ADC Routes */ { "Left ADC", NULL, "Left ADC Mixer" }, @@ -498,6 +474,61 @@ static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) return ret; } +/* line in specific controls, widgets and rines */ +static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, + SUN8I_ADDA_LINEIN_GCTRL_LINEING, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { + /* Line input */ + SND_SOC_DAPM_INPUT("LINEIN"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, + + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, +}; + +static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_linein_controls, + ARRAY_SIZE(sun8i_codec_linein_controls)); + if (ret) { + dev_err(dev, "Failed to add Line In controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets, + ARRAY_SIZE(sun8i_codec_linein_widgets)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes, + ARRAY_SIZE(sun8i_codec_linein_routes)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + + /* line out specific controls, widgets and routes */ static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), @@ -578,19 +609,90 @@ static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) return 0; } +/* mic2 specific controls, widgets and routes */ +static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Mic2 Playback Volume", + SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gain */ + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, + sun8i_codec_mic_gain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { + /* Microphone input */ + SND_SOC_DAPM_INPUT("MIC2"), + + /* Mic input path */ + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { + { "Mic2 Amplifier", NULL, "MIC2"}, + + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, + + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, +}; + +static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_mic2_controls, + ARRAY_SIZE(sun8i_codec_mic2_controls)); + if (ret) { + dev_err(dev, "Failed to add MIC2 controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets, + ARRAY_SIZE(sun8i_codec_mic2_widgets)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes, + ARRAY_SIZE(sun8i_codec_mic2_routes)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + struct sun8i_codec_analog_quirks { bool has_headphone; bool has_hmic; + bool has_linein; bool has_lineout; + bool has_mic2; }; static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { .has_headphone = true, .has_hmic = true, + .has_linein = true, + .has_mic2 = true, }; static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { + .has_linein = true, .has_lineout = true, + .has_mic2 = true, }; static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) @@ -620,12 +722,24 @@ static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) return ret; } + if (quirks->has_linein) { + ret = sun8i_codec_add_linein(cmpnt); + if (ret) + return ret; + } + if (quirks->has_lineout) { ret = sun8i_codec_add_lineout(cmpnt); if (ret) return ret; } + if (quirks->has_mic2) { + ret = sun8i_codec_add_mic2(cmpnt); + if (ret) + return ret; + } + return 0; } diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 7527ba29a5a0..5723c3404f6b 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -290,12 +290,10 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), /* DAC Mixers */ - SND_SOC_DAPM_MIXER("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, - sun8i_dac_mixer_controls, - ARRAY_SIZE(sun8i_dac_mixer_controls)), - SND_SOC_DAPM_MIXER("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, - sun8i_dac_mixer_controls, - ARRAY_SIZE(sun8i_dac_mixer_controls)), + SOC_MIXER_ARRAY("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), + SOC_MIXER_ARRAY("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), /* Clocks */ SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA, diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index a68368edab9c..affad46bf188 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -318,7 +318,6 @@ static int tegra20_ac97_platform_probe(struct platform_device *pdev) ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97), GFP_KERNEL); if (!ac97) { - dev_err(&pdev->dev, "Can't allocate tegra20_ac97\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c index 89add13c31cf..4024e3abbeed 100644 --- a/sound/soc/tegra/tegra20_das.c +++ b/sound/soc/tegra/tegra20_das.c @@ -41,6 +41,7 @@ static inline void tegra20_das_write(u32 reg, u32 val) static inline u32 tegra20_das_read(u32 reg) { u32 val; + regmap_read(das->regmap, reg, &val); return val; } @@ -142,7 +143,6 @@ static int tegra20_das_probe(struct platform_device *pdev) das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); if (!das) { - dev_err(&pdev->dev, "Can't allocate tegra20_das\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index 14106fa82bca..26253c2849e7 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -345,7 +345,6 @@ static int tegra20_i2s_platform_probe(struct platform_device *pdev) i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); if (!i2s) { - dev_err(&pdev->dev, "Can't allocate tegra20_i2s\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index a0c3640572b9..767c0491e11a 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -271,10 +271,9 @@ static int tegra20_spdif_platform_probe(struct platform_device *pdev) spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), GFP_KERNEL); - if (!spdif) { - dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n"); + if (!spdif) return -ENOMEM; - } + dev_set_drvdata(&pdev->dev, spdif); spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c index fef3b9a21a66..8c10ae7982ba 100644 --- a/sound/soc/tegra/tegra30_ahub.c +++ b/sound/soc/tegra/tegra30_ahub.c @@ -41,6 +41,7 @@ static inline void tegra30_apbif_write(u32 reg, u32 val) static inline u32 tegra30_apbif_read(u32 reg) { u32 val; + regmap_read(ahub->regmap_apbif, reg, &val); return val; } @@ -560,10 +561,8 @@ static int tegra30_ahub_probe(struct platform_device *pdev) ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), GFP_KERNEL); - if (!ahub) { - dev_err(&pdev->dev, "Can't allocate tegra30_ahub\n"); + if (!ahub) return -ENOMEM; - } dev_set_drvdata(&pdev->dev, ahub); ahub->soc_data = soc_data; diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 8e55583aa104..b2b279c96029 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -385,7 +385,6 @@ static int tegra30_i2s_platform_probe(struct platform_device *pdev) i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); if (!i2s) { - dev_err(&pdev->dev, "Can't allocate tegra30_i2s\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index eead6e7f205b..0509902512cc 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -169,10 +169,8 @@ static int tegra_alc5632_probe(struct platform_device *pdev) alc5632 = devm_kzalloc(&pdev->dev, sizeof(struct tegra_alc5632), GFP_KERNEL); - if (!alc5632) { - dev_err(&pdev->dev, "Can't allocate tegra_alc5632\n"); + if (!alc5632) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c index a403db6d563e..c34a54d6e812 100644 --- a/sound/soc/tegra/tegra_max98090.c +++ b/sound/soc/tegra/tegra_max98090.c @@ -225,10 +225,8 @@ static int tegra_max98090_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_max98090), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_max98090\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index 25b9fc03ba62..93a356802345 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -170,10 +170,8 @@ static int tegra_rt5640_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_rt5640), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_rt5640\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c index 4bbab098f50b..6dda01f69983 100644 --- a/sound/soc/tegra/tegra_sgtl5000.c +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -120,10 +120,8 @@ static int tegra_sgtl5000_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_sgtl5000), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_sgtl5000 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c index bdedd1028569..d0ab0026a4cd 100644 --- a/sound/soc/tegra/tegra_wm8753.c +++ b/sound/soc/tegra/tegra_wm8753.c @@ -128,10 +128,8 @@ static int tegra_wm8753_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8753), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm8753 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 2013e9c4bba0..dbfb49298ae8 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -248,10 +248,8 @@ static int tegra_wm8903_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8903), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm8903 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c index 6492f8143ff1..c9cd22432627 100644 --- a/sound/soc/tegra/tegra_wm9712.c +++ b/sound/soc/tegra/tegra_wm9712.c @@ -77,10 +77,8 @@ static int tegra_wm9712_driver_probe(struct platform_device *pdev) machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712), GFP_KERNEL); - if (!machine) { - dev_err(&pdev->dev, "Can't allocate tegra_wm9712 struct\n"); + if (!machine) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c index 870f84ab5005..c9dcad9bb931 100644 --- a/sound/soc/tegra/trimslice.c +++ b/sound/soc/tegra/trimslice.c @@ -123,10 +123,8 @@ static int tegra_snd_trimslice_probe(struct platform_device *pdev) trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), GFP_KERNEL); - if (!trimslice) { - dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); + if (!trimslice) return -ENOMEM; - } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c index a8f705bb60dc..7912bf09dc4d 100644 --- a/sound/soc/txx9/txx9aclc.c +++ b/sound/soc/txx9/txx9aclc.c @@ -206,7 +206,7 @@ static void txx9aclc_dma_tasklet(unsigned long data) static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; - struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; void __iomem *base = drvdata->base; unsigned long flags; int ret = 0; @@ -340,7 +340,7 @@ static bool filter(struct dma_chan *chan, void *param) static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, struct txx9aclc_dmadata *dmadata) { - struct txx9aclc_plat_drvdata *drvdata =txx9aclc_drvdata; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; struct txx9dmac_slave *ds = &dmadata->dma_slave; dma_cap_mask_t mask; @@ -392,6 +392,7 @@ static int txx9aclc_pcm_remove(struct snd_soc_platform *platform) for (i = 0; i < 2; i++) { struct txx9aclc_dmadata *dmadata = &dev->dmadata[i]; struct dma_chan *chan = dmadata->dma_chan; + if (chan) { dmadata->frag_count = -1; dmaengine_terminate_all(chan); diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c index ba9fc099cf67..b50f68a439ce 100644 --- a/sound/soc/ux500/mop500.c +++ b/sound/soc/ux500/mop500.c @@ -33,7 +33,6 @@ static struct snd_soc_dai_link mop500_dai_links[] = { .stream_name = "ab8500_0", .cpu_dai_name = "ux500-msp-i2s.1", .codec_dai_name = "ab8500-codec-dai.0", - .platform_name = "ux500-msp-i2s.1", .codec_name = "ab8500-codec.0", .init = mop500_ab8500_machine_init, .ops = mop500_ab8500_ops, @@ -43,7 +42,6 @@ static struct snd_soc_dai_link mop500_dai_links[] = { .stream_name = "ab8500_1", .cpu_dai_name = "ux500-msp-i2s.3", .codec_dai_name = "ab8500-codec-dai.1", - .platform_name = "ux500-msp-i2s.3", .codec_name = "ab8500-codec.0", .init = NULL, .ops = mop500_ab8500_ops, @@ -87,8 +85,6 @@ static int mop500_of_probe(struct platform_device *pdev, for (i = 0; i < 2; i++) { mop500_dai_links[i].cpu_of_node = msp_np[i]; mop500_dai_links[i].cpu_dai_name = NULL; - mop500_dai_links[i].platform_of_node = msp_np[i]; - mop500_dai_links[i].platform_name = NULL; mop500_dai_links[i].codec_of_node = codec_np; mop500_dai_links[i].codec_name = NULL; } diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index b343efd9be5b..ec5152aa3f6e 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -133,6 +133,7 @@ static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); u32 frame_length = MSP_FRAME_LEN_1; + prot_desc->frame_width = 0; switch (drvdata->slots) { @@ -482,7 +483,8 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) && (drvdata->msp->f_bitclk > 19200000)) { /* If the bit-clock is higher than 19.2MHz, Vape should be - * run in 100% OPP. Only when bit-clock is used (MSP master) */ + * run in 100% OPP. Only when bit-clock is used (MSP master) + */ prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100); drvdata->vape_opp_constraint = 1; diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 959d7b4edf56..bd5266aca0f1 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -604,7 +604,6 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) break; default: return -EINVAL; - break; } return 0; diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig index 6d8a90d36315..75f67a5d23ea 100644 --- a/sound/soc/zte/Kconfig +++ b/sound/soc/zte/Kconfig @@ -15,3 +15,11 @@ config ZX_I2S help Say Y or M if you want to add support for codecs attached to the ZTE ZX I2S interface + +config ZX_TDM + tristate "ZTE ZX TDM Driver Support" + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + ZTE ZX TDM interface diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile index 77768f5fd10c..1fc841acdfdd 100644 --- a/sound/soc/zte/Makefile +++ b/sound/soc/zte/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_ZX_SPDIF) += zx-spdif.o obj-$(CONFIG_ZX_I2S) += zx-i2s.o +obj-$(CONFIG_ZX_TDM) += zx-tdm.o diff --git a/sound/soc/zte/zx-tdm.c b/sound/soc/zte/zx-tdm.c new file mode 100644 index 000000000000..bd632cc503b3 --- /dev/null +++ b/sound/soc/zte/zx-tdm.c @@ -0,0 +1,461 @@ +/* + * ZTE's TDM driver + * + * Copyright (C) 2017 ZTE Ltd + * + * Author: Baoyou Xie <baoyou.xie@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#define REG_TIMING_CTRL 0x04 +#define REG_TX_FIFO_CTRL 0x0C +#define REG_RX_FIFO_CTRL 0x10 +#define REG_INT_EN 0x1C +#define REG_INT_STATUS 0x20 +#define REG_DATABUF 0x24 +#define REG_TS_MASK0 0x44 +#define REG_PROCESS_CTRL 0x54 + +#define FIFO_CTRL_TX_RST BIT(0) +#define FIFO_CTRL_RX_RST BIT(0) +#define DEAGULT_FIFO_THRES GENMASK(4, 2) + +#define FIFO_CTRL_TX_DMA_EN BIT(1) +#define FIFO_CTRL_RX_DMA_EN BIT(1) + +#define TX_FIFO_RST_MASK BIT(0) +#define RX_FIFO_RST_MASK BIT(0) + +#define FIFOCTRL_TX_FIFO_RST BIT(0) +#define FIFOCTRL_RX_FIFO_RST BIT(0) + +#define TXTH_MASK GENMASK(5, 2) +#define RXTH_MASK GENMASK(5, 2) + +#define FIFOCTRL_THRESHOLD(x) ((x) << 2) + +#define TIMING_MS_MASK BIT(1) +/* + * 00: 8 clk cycles every timeslot + * 01: 16 clk cycles every timeslot + * 10: 32 clk cycles every timeslot + */ +#define TIMING_SYNC_WIDTH_MASK GENMASK(6, 5) +#define TIMING_WIDTH_SHIFT 5 +#define TIMING_DEFAULT_WIDTH 0 +#define TIMING_TS_WIDTH(x) ((x) << TIMING_WIDTH_SHIFT) +#define TIMING_WIDTH_FACTOR 8 + +#define TIMING_MASTER_MODE BIT(21) +#define TIMING_LSB_FIRST BIT(20) +#define TIMING_TS_NUM(x) (((x) - 1) << 7) +#define TIMING_CLK_SEL_MASK GENMASK(2, 0) +#define TIMING_CLK_SEL_DEF BIT(2) + +#define PROCESS_TX_EN BIT(0) +#define PROCESS_RX_EN BIT(1) +#define PROCESS_TDM_EN BIT(2) +#define PROCESS_DISABLE_ALL 0 + +#define INT_DISABLE_ALL 0 +#define INT_STATUS_MASK GENMASK(6, 0) + +struct zx_tdm_info { + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + resource_size_t phy_addr; + void __iomem *regbase; + struct clk *dai_wclk; + struct clk *dai_pclk; + int master; + struct device *dev; +}; + +static inline u32 zx_tdm_readl(struct zx_tdm_info *tdm, u16 reg) +{ + return readl_relaxed(tdm->regbase + reg); +} + +static inline void zx_tdm_writel(struct zx_tdm_info *tdm, u16 reg, u32 val) +{ + writel_relaxed(val, tdm->regbase + reg); +} + +static void zx_tdm_tx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_TX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_TX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_rx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_RX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_RX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_tx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val |= FIFO_CTRL_TX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_TX_DMA_EN; + else + val &= ~FIFO_CTRL_TX_DMA_EN; + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static void zx_tdm_rx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val |= FIFO_CTRL_RX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_RX_DMA_EN; + else + val &= ~FIFO_CTRL_RX_DMA_EN; + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); +} + +#define ZX_TDM_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define ZX_TDM_FMTBIT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_MU_LAW | \ + SNDRV_PCM_FORMAT_A_LAW) + +static int zx_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_tdm); + zx_tdm->dma_playback.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_playback.maxburst = 16; + zx_tdm->dma_capture.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_capture.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &zx_tdm->dma_playback, + &zx_tdm->dma_capture); + return 0; +} + +static int zx_tdm_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val &= ~(TIMING_SYNC_WIDTH_MASK | TIMING_MS_MASK); + val |= TIMING_DEFAULT_WIDTH << TIMING_WIDTH_SHIFT; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tdm->master = 1; + val |= TIMING_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tdm->master = 0; + val &= ~TIMING_MASTER_MODE; + break; + default: + dev_err(cpu_dai->dev, "Unknown master/slave format\n"); + return -EINVAL; + } + + + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + return 0; +} + +static int zx_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data; + unsigned int ts_width = TIMING_DEFAULT_WIDTH; + unsigned int ch_num = 32; + unsigned int mask = 0; + unsigned int ret = 0; + unsigned long val; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = ch_num >> 3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_MU_LAW: + case SNDRV_PCM_FORMAT_A_LAW: + case SNDRV_PCM_FORMAT_S16_LE: + ts_width = 1; + break; + default: + ts_width = 0; + dev_err(socdai->dev, "Unknown data format\n"); + return -EINVAL; + } + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_TS_WIDTH(ts_width) | TIMING_TS_NUM(1); + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + zx_tdm_writel(tdm, REG_TS_MASK0, mask); + + if (tdm->master) + ret = clk_set_rate(tdm->dai_wclk, + params_rate(params) * TIMING_WIDTH_FACTOR * ch_num); + + return ret; +} + +static int zx_tdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + unsigned int val; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (capture) { + val = zx_tdm_readl(zx_tdm, REG_RX_FIFO_CTRL); + val |= FIFOCTRL_RX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_RX_FIFO_CTRL, val); + + zx_tdm_rx_dma_en(zx_tdm, true); + } else { + val = zx_tdm_readl(zx_tdm, REG_TX_FIFO_CTRL); + val |= FIFOCTRL_TX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_TX_FIFO_CTRL, val); + + zx_tdm_tx_dma_en(zx_tdm, true); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (capture) + zx_tdm_rx_en(zx_tdm, true); + else + zx_tdm_tx_en(zx_tdm, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (capture) + zx_tdm_rx_dma_en(zx_tdm, false); + else + zx_tdm_tx_dma_en(zx_tdm, false); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (capture) + zx_tdm_rx_en(zx_tdm, false); + else + zx_tdm_tx_en(zx_tdm, false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_tdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + int ret; + + ret = clk_prepare_enable(zx_tdm->dai_wclk); + if (ret) + return ret; + + ret = clk_prepare_enable(zx_tdm->dai_pclk); + if (ret) { + clk_disable_unprepare(zx_tdm->dai_wclk); + return ret; + } + + return 0; +} + +static void zx_tdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_tdm->dai_pclk); + clk_disable_unprepare(zx_tdm->dai_wclk); +} + +static struct snd_soc_dai_ops zx_tdm_dai_ops = { + .trigger = zx_tdm_trigger, + .hw_params = zx_tdm_hw_params, + .set_fmt = zx_tdm_set_fmt, + .startup = zx_tdm_startup, + .shutdown = zx_tdm_shutdown, +}; + +static const struct snd_soc_component_driver zx_tdm_component = { + .name = "zx-tdm", +}; + +static void zx_tdm_init_state(struct zx_tdm_info *tdm) +{ + unsigned int val; + + zx_tdm_writel(tdm, REG_PROCESS_CTRL, PROCESS_DISABLE_ALL); + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_LSB_FIRST; + val &= ~TIMING_CLK_SEL_MASK; + val |= TIMING_CLK_SEL_DEF; + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + zx_tdm_writel(tdm, REG_INT_EN, INT_DISABLE_ALL); + /* + * write INT_STATUS register to clear it. + */ + zx_tdm_writel(tdm, REG_INT_STATUS, INT_STATUS_MASK); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, FIFOCTRL_RX_FIFO_RST); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, FIFOCTRL_TX_FIFO_RST); + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val &= ~(RXTH_MASK | RX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val &= ~(TXTH_MASK | TX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static struct snd_soc_dai_driver zx_tdm_dai = { + .name = "zx-tdm-dai", + .id = 0, + .probe = zx_tdm_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .ops = &zx_tdm_dai_ops, +}; + +static int zx_tdm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args out_args; + unsigned int dma_reg_offset; + struct zx_tdm_info *zx_tdm; + unsigned int dma_mask; + struct resource *res; + struct regmap *regmap_sysctrl; + int ret; + + zx_tdm = devm_kzalloc(&pdev->dev, sizeof(*zx_tdm), GFP_KERNEL); + if (!zx_tdm) + return -ENOMEM; + + zx_tdm->dev = dev; + + zx_tdm->dai_wclk = devm_clk_get(&pdev->dev, "wclk"); + if (IS_ERR(zx_tdm->dai_wclk)) { + dev_err(&pdev->dev, "Fail to get wclk\n"); + return PTR_ERR(zx_tdm->dai_wclk); + } + + zx_tdm->dai_pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(zx_tdm->dai_pclk)) { + dev_err(&pdev->dev, "Fail to get pclk\n"); + return PTR_ERR(zx_tdm->dai_pclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_tdm->phy_addr = res->start; + zx_tdm->regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zx_tdm->regbase)) + return PTR_ERR(zx_tdm->regbase); + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "zte,tdm-dma-sysctrl", 2, 0, &out_args); + if (ret) { + dev_err(&pdev->dev, "Fail to get zte,tdm-dma-sysctrl\n"); + return ret; + } + + dma_reg_offset = out_args.args[0]; + dma_mask = out_args.args[1]; + regmap_sysctrl = syscon_node_to_regmap(out_args.np); + if (IS_ERR(regmap_sysctrl)) { + of_node_put(out_args.np); + return PTR_ERR(regmap_sysctrl); + } + + regmap_update_bits(regmap_sysctrl, dma_reg_offset, dma_mask, dma_mask); + of_node_put(out_args.np); + + zx_tdm_init_state(zx_tdm); + platform_set_drvdata(pdev, zx_tdm); + + ret = devm_snd_soc_register_component(&pdev->dev, &zx_tdm_component, + &zx_tdm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_tdm_dt_ids[] = { + { .compatible = "zte,zx296718-tdm", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_tdm_dt_ids); + +static struct platform_driver tdm_driver = { + .probe = zx_tdm_probe, + .driver = { + .name = "zx-tdm", + .of_match_table = zx_tdm_dt_ids, + }, +}; +module_platform_driver(tdm_driver); + +MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); +MODULE_DESCRIPTION("ZTE TDM DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index ac75816ada7c..850fab4a8f3b 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -225,9 +225,9 @@ snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, else if (format == SNDRV_OSS_SOUNDFONT_PATCH) { struct soundfont_patch_info patch; if (count < (int)sizeof(patch)) - rc = -EINVAL; + return -EINVAL; if (copy_from_user(&patch, buf, sizeof(patch))) - rc = -EFAULT; + return -EFAULT; if (patch.type >= SNDRV_SFNT_LOAD_INFO && patch.type <= SNDRV_SFNT_PROBE_DATA) rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port)); diff --git a/sound/usb/card.c b/sound/usb/card.c index f36cb068dad3..6640277a725b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -332,6 +332,7 @@ static int snd_usb_audio_dev_free(struct snd_device *device) static int snd_usb_audio_create(struct usb_interface *intf, struct usb_device *dev, int idx, const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id, struct snd_usb_audio **rchip) { struct snd_card *card; @@ -381,8 +382,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, atomic_set(&chip->usage_count, 0); atomic_set(&chip->shutdown, 0); - chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), - le16_to_cpu(dev->descriptor.idProduct)); + chip->usb_id = usb_id; INIT_LIST_HEAD(&chip->pcm_list); INIT_LIST_HEAD(&chip->ep_list); INIT_LIST_HEAD(&chip->midi_list); @@ -569,7 +569,7 @@ static int usb_audio_probe(struct usb_interface *intf, (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { err = snd_usb_audio_create(intf, dev, i, quirk, - &chip); + id, &chip); if (err < 0) goto __error; chip->pm_intf = intf; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index fab53f58d447..b3854f8c0c67 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -430,7 +430,7 @@ static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new line6_controls[] = { +static const struct snd_kcontrol_new line6_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 17aa616e61f5..358224cc5638 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -380,7 +380,7 @@ static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, } /* control definition */ -static struct snd_kcontrol_new pod_control_monitor = { +static const struct snd_kcontrol_new pod_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c index 8e22f430d700..ba7975c0d03d 100644 --- a/sound/usb/line6/toneport.c +++ b/sound/usb/line6/toneport.c @@ -250,7 +250,7 @@ static void toneport_start_pcm(unsigned long arg) } /* control definition */ -static struct snd_kcontrol_new toneport_control_monitor = { +static const struct snd_kcontrol_new toneport_control_monitor = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Monitor Playback Volume", .index = 0, @@ -261,7 +261,7 @@ static struct snd_kcontrol_new toneport_control_monitor = { }; /* source selector definition */ -static struct snd_kcontrol_new toneport_control_source = { +static const struct snd_kcontrol_new toneport_control_source = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Capture Source", .index = 0, diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 6e763bc8d7db..a35f41467237 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -1922,7 +1922,7 @@ static int roland_load_put(struct snd_kcontrol *kcontrol, return changed; } -static struct snd_kcontrol_new roland_load_ctl = { +static const struct snd_kcontrol_new roland_load_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "MIDI Input Mode", .info = roland_load_info, diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 4703caea56b2..082736c539bc 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1172,7 +1172,7 @@ static struct snd_kcontrol_new usb_feature_unit_ctl = { }; /* the read-only variant */ -static struct snd_kcontrol_new usb_feature_unit_ctl_ro = { +static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later manually */ .info = mixer_ctl_feature_info, @@ -1745,7 +1745,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for processing/extension unit */ -static struct snd_kcontrol_new mixer_procunit_ctl = { +static const struct snd_kcontrol_new mixer_procunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_feature_info, @@ -2033,7 +2033,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, } /* alsa control interface for selector unit */ -static struct snd_kcontrol_new mixer_selectunit_ctl = { +static const struct snd_kcontrol_new mixer_selectunit_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = mixer_ctl_selector_info, diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 7438e7c4a842..c33e2378089d 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -477,7 +477,7 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, return 0; } -static struct snd_kcontrol_new usb_scarlett_ctl_switch = { +static const struct snd_kcontrol_new usb_scarlett_ctl_switch = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_switch_info, @@ -487,7 +487,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_switch = { static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); -static struct snd_kcontrol_new usb_scarlett_ctl = { +static const struct snd_kcontrol_new usb_scarlett_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -499,7 +499,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_master = { +static const struct snd_kcontrol_new usb_scarlett_ctl_master = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, @@ -511,7 +511,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_master = { .tlv = { .p = db_scale_scarlett_gain } }; -static struct snd_kcontrol_new usb_scarlett_ctl_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_info, @@ -519,7 +519,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { +static const struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", .info = scarlett_ctl_enum_dynamic_info, @@ -527,7 +527,7 @@ static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { .put = scarlett_ctl_enum_put, }; -static struct snd_kcontrol_new usb_scarlett_ctl_sync = { +static const struct snd_kcontrol_new usb_scarlett_ctl_sync = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, .name = "", diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index cf45bf1f7ee0..e118bdca983d 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -472,7 +472,7 @@ static int usb_stream_hwdep_new(struct snd_card *card) hw->ops.mmap = usb_stream_hwdep_mmap; hw->ops.poll = usb_stream_hwdep_poll; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); return 0; } diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 605e1047c01d..f4b3cda412fc 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -258,7 +258,7 @@ int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) hw->ops.mmap = snd_us428ctls_mmap; hw->ops.poll = snd_us428ctls_poll; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); return 0; } diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c index f95164b91152..d51c7fd7835b 100644 --- a/sound/usb/usx2y/usx2yhwdeppcm.c +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -723,7 +723,7 @@ int usX2Y_hwdep_pcm_new(struct snd_card *card) hw->ops.release = snd_usX2Y_hwdep_pcm_release; hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap; hw->exclusive = 1; - sprintf(hw->name, "/proc/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); if (err < 0) { |