diff options
-rw-r--r-- | Documentation/devicetree/bindings/sound/cirrus,cs4271.yaml | 101 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/sound/cs4271.txt | 57 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/sound/fsl,saif.yaml | 83 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/sound/mxs-saif.txt | 41 | ||||
-rw-r--r-- | sound/soc/codecs/wsa884x.c | 201 |
5 files changed, 385 insertions, 98 deletions
diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs4271.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs4271.yaml new file mode 100644 index 000000000000..68fbf5cc208f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cirrus,cs4271.yaml @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cirrus,cs4271.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cirrus Logic CS4271 audio CODEC + +maintainers: + - Alexander Sverdlin <[email protected]> + - Nikita Shubin <[email protected]> + +description: + The CS4271 is a stereo audio codec. This device supports both the I2C + and the SPI bus. + +allOf: + - $ref: dai-common.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: cirrus,cs4271 + + reg: + maxItems: 1 + + spi-cpha: true + + spi-cpol: true + + '#sound-dai-cells': + const: 0 + + reset-gpios: + description: + This pin will be deasserted before communication to the codec starts. + maxItems: 1 + + va-supply: + description: Analog power supply. + + vd-supply: + description: Digital power supply. + + vl-supply: + description: Serial Control Port power supply. + + port: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + + cirrus,amuteb-eq-bmutec: + description: + When given, the Codec's AMUTEB=BMUTEC flag is enabled. + type: boolean + + cirrus,enable-soft-reset: + description: | + The CS4271 requires its LRCLK and MCLK to be stable before its RESET + line is de-asserted. That also means that clocks cannot be changed + without putting the chip back into hardware reset, which also requires + a complete re-initialization of all registers. + + One (undocumented) workaround is to assert and de-assert the PDN bit + in the MODE2 register. This workaround can be enabled with this DT + property. + + Note that this is not needed in case the clocks are stable + throughout the entire runtime of the codec. + type: boolean + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + spi { + #address-cells = <1>; + #size-cells = <0>; + codec@0 { + compatible = "cirrus,cs4271"; + reg = <0>; + #sound-dai-cells = <0>; + spi-max-frequency = <6000000>; + spi-cpol; + spi-cpha; + reset-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; + port { + endpoint { + remote-endpoint = <&i2s_ep>; + }; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/cs4271.txt b/Documentation/devicetree/bindings/sound/cs4271.txt deleted file mode 100644 index 6e699ceabacd..000000000000 --- a/Documentation/devicetree/bindings/sound/cs4271.txt +++ /dev/null @@ -1,57 +0,0 @@ -Cirrus Logic CS4271 DT bindings - -This driver supports both the I2C and the SPI bus. - -Required properties: - - - compatible: "cirrus,cs4271" - -For required properties on SPI, please consult -Documentation/devicetree/bindings/spi/spi-bus.txt - -Required properties on I2C: - - - reg: the i2c address - - -Optional properties: - - - reset-gpio: a GPIO spec to define which pin is connected to the chip's - !RESET pin - - cirrus,amuteb-eq-bmutec: When given, the Codec's AMUTEB=BMUTEC flag - is enabled. - - cirrus,enable-soft-reset: - The CS4271 requires its LRCLK and MCLK to be stable before its RESET - line is de-asserted. That also means that clocks cannot be changed - without putting the chip back into hardware reset, which also requires - a complete re-initialization of all registers. - - One (undocumented) workaround is to assert and de-assert the PDN bit - in the MODE2 register. This workaround can be enabled with this DT - property. - - Note that this is not needed in case the clocks are stable - throughout the entire runtime of the codec. - - - vd-supply: Digital power - - vl-supply: Logic power - - va-supply: Analog Power - -Examples: - - codec_i2c: cs4271@10 { - compatible = "cirrus,cs4271"; - reg = <0x10>; - reset-gpio = <&gpio 23 0>; - vd-supply = <&vdd_3v3_reg>; - vl-supply = <&vdd_3v3_reg>; - va-supply = <&vdd_3v3_reg>; - }; - - codec_spi: cs4271@0 { - compatible = "cirrus,cs4271"; - reg = <0x0>; - reset-gpio = <&gpio 23 0>; - spi-max-frequency = <6000000>; - }; - diff --git a/Documentation/devicetree/bindings/sound/fsl,saif.yaml b/Documentation/devicetree/bindings/sound/fsl,saif.yaml new file mode 100644 index 000000000000..0b5db6bb1b7c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,saif.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/fsl,saif.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Freescale MXS Serial Audio Interface (SAIF) + +maintainers: + - Lukasz Majewski <[email protected]> + +allOf: + - $ref: dai-common.yaml# + +description: + The SAIF is based on I2S module that is used to communicate with audio codecs, + but only with half-duplex manner (i.e. it can either transmit or receive PCM + audio). + +properties: + compatible: + const: fsl,imx28-saif + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + + interrupts: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + const: rx-tx + + "#clock-cells": + description: Configure the I2S device as MCLK clock provider. + const: 0 + + clocks: + maxItems: 1 + + fsl,saif-master: + description: Indicate that saif is a slave and its phandle points to master + $ref: /schemas/types.yaml#/definitions/phandle + +required: + - compatible + - reg + - "#sound-dai-cells" + - interrupts + - dmas + - dma-names + - clocks + +unevaluatedProperties: false + +examples: + - | + saif0: saif@80042000 { + compatible = "fsl,imx28-saif"; + reg = <0x80042000 2000>; + #sound-dai-cells = <0>; + interrupts = <59>; + dmas = <&dma_apbx 4>; + dma-names = "rx-tx"; + #clock-cells = <0>; + clocks = <&clks 53>; + }; + - | + saif1: saif@80046000 { + compatible = "fsl,imx28-saif"; + reg = <0x80046000 2000>; + #sound-dai-cells = <0>; + interrupts = <58>; + dmas = <&dma_apbx 5>; + dma-names = "rx-tx"; + clocks = <&clks 53>; + fsl,saif-master = <&saif0>; + }; diff --git a/Documentation/devicetree/bindings/sound/mxs-saif.txt b/Documentation/devicetree/bindings/sound/mxs-saif.txt deleted file mode 100644 index 7ba07a118e37..000000000000 --- a/Documentation/devicetree/bindings/sound/mxs-saif.txt +++ /dev/null @@ -1,41 +0,0 @@ -* Freescale MXS Serial Audio Interface (SAIF) - -Required properties: -- compatible: Should be "fsl,<chip>-saif" -- reg: Should contain registers location and length -- interrupts: Should contain ERROR interrupt number -- dmas: DMA specifier, consisting of a phandle to DMA controller node - and SAIF DMA channel ID. - Refer to dma.txt and fsl-mxs-dma.txt for details. -- dma-names: Must be "rx-tx". - -Optional properties: -- fsl,saif-master: phandle to the master SAIF. It's only required for - the slave SAIF. - -Note: Each SAIF controller should have an alias correctly numbered -in "aliases" node. - -Example: - -aliases { - saif0 = &saif0; - saif1 = &saif1; -}; - -saif0: saif@80042000 { - compatible = "fsl,imx28-saif"; - reg = <0x80042000 2000>; - interrupts = <59>; - dmas = <&dma_apbx 4>; - dma-names = "rx-tx"; -}; - -saif1: saif@80046000 { - compatible = "fsl,imx28-saif"; - reg = <0x80046000 2000>; - interrupts = <58>; - dmas = <&dma_apbx 5>; - dma-names = "rx-tx"; - fsl,saif-master = <&saif0>; -}; diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c index 8db1380d1f10..86df5152c547 100644 --- a/sound/soc/codecs/wsa884x.c +++ b/sound/soc/codecs/wsa884x.c @@ -5,11 +5,14 @@ */ #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/gpio/consumer.h> +#include <linux/hwmon.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> @@ -301,8 +304,28 @@ #define WSA884X_PA_FSM_MSK1 (WSA884X_DIG_CTRL0_BASE + 0x3b) #define WSA884X_PA_FSM_BYP_CTL (WSA884X_DIG_CTRL0_BASE + 0x3c) #define WSA884X_PA_FSM_BYP0 (WSA884X_DIG_CTRL0_BASE + 0x3d) +#define WSA884X_PA_FSM_BYP0_DC_CAL_EN_MASK 0x01 +#define WSA884X_PA_FSM_BYP0_DC_CAL_EN_SHIFT 0 +#define WSA884X_PA_FSM_BYP0_CLK_WD_EN_MASK 0x02 +#define WSA884X_PA_FSM_BYP0_CLK_WD_EN_SHIFT 1 +#define WSA884X_PA_FSM_BYP0_BG_EN_MASK 0x04 +#define WSA884X_PA_FSM_BYP0_BG_EN_SHIFT 2 +#define WSA884X_PA_FSM_BYP0_BOOST_EN_MASK 0x08 +#define WSA884X_PA_FSM_BYP0_BOOST_EN_SHIFT 3 +#define WSA884X_PA_FSM_BYP0_PA_EN_MASK 0x10 +#define WSA884X_PA_FSM_BYP0_PA_EN_SHIFT 4 +#define WSA884X_PA_FSM_BYP0_D_UNMUTE_MASK 0x20 +#define WSA884X_PA_FSM_BYP0_D_UNMUTE_SHIFT 5 +#define WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_MASK 0x40 +#define WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_SHIFT 6 +#define WSA884X_PA_FSM_BYP0_TSADC_EN_MASK 0x80 +#define WSA884X_PA_FSM_BYP0_TSADC_EN_SHIFT 7 #define WSA884X_PA_FSM_BYP1 (WSA884X_DIG_CTRL0_BASE + 0x3e) #define WSA884X_TADC_VALUE_CTL (WSA884X_DIG_CTRL0_BASE + 0x50) +#define WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK 0x01 +#define WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_SHIFT 0 +#define WSA884X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_MASK 0x02 +#define WSA884X_TADC_VALUE_CTL_VBAT_VALUE_RD_EN_SHIFT 1 #define WSA884X_TEMP_DETECT_CTL (WSA884X_DIG_CTRL0_BASE + 0x51) #define WSA884X_TEMP_DIN_MSB (WSA884X_DIG_CTRL0_BASE + 0x52) #define WSA884X_TEMP_DIN_LSB (WSA884X_DIG_CTRL0_BASE + 0x53) @@ -691,6 +714,17 @@ SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +/* Two-point trimming for temperature calibration */ +#define WSA884X_T1_TEMP -10L +#define WSA884X_T2_TEMP 150L + +/* + * Device will report senseless data in many cases, so discard any measurements + * outside of valid range. + */ +#define WSA884X_LOW_TEMP_THRESHOLD 5 +#define WSA884X_HIGH_TEMP_THRESHOLD 45 + struct wsa884x_priv { struct regmap *regmap; struct device *dev; @@ -706,6 +740,13 @@ struct wsa884x_priv { int active_ports; int dev_mode; bool hw_init; + /* + * Protects temperature reading code (related to speaker protection) and + * fields: temperature and pa_on. + */ + struct mutex sp_lock; + unsigned int temperature; + bool pa_on; }; enum { @@ -1660,6 +1701,10 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_POST_PMU: + mutex_lock(&wsa884x->sp_lock); + wsa884x->pa_on = true; + mutex_unlock(&wsa884x->sp_lock); + wsa884x_spkr_post_pmu(component, wsa884x); snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL, @@ -1671,6 +1716,10 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL, WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, 0x0); + + mutex_lock(&wsa884x->sp_lock); + wsa884x->pa_on = false; + mutex_unlock(&wsa884x->sp_lock); break; } @@ -1810,6 +1859,144 @@ static struct snd_soc_dai_driver wsa884x_dais[] = { }, }; +static int wsa884x_get_temp(struct wsa884x_priv *wsa884x, long *temp) +{ + unsigned int d1_msb = 0, d1_lsb = 0, d2_msb = 0, d2_lsb = 0; + unsigned int dmeas_msb = 0, dmeas_lsb = 0; + int d1, d2, dmeas; + unsigned int mask; + long val; + int ret; + + guard(mutex)(&wsa884x->sp_lock); + + if (wsa884x->pa_on) { + /* + * Reading temperature is possible only when Power Amplifier is + * off. Report last cached data. + */ + *temp = wsa884x->temperature; + return 0; + } + + ret = pm_runtime_resume_and_get(wsa884x->dev); + if (ret < 0) + return ret; + + mask = WSA884X_PA_FSM_BYP0_DC_CAL_EN_MASK | + WSA884X_PA_FSM_BYP0_CLK_WD_EN_MASK | + WSA884X_PA_FSM_BYP0_BG_EN_MASK | + WSA884X_PA_FSM_BYP0_D_UNMUTE_MASK | + WSA884X_PA_FSM_BYP0_SPKR_PROT_EN_MASK | + WSA884X_PA_FSM_BYP0_TSADC_EN_MASK; + /* + * Here and further do not care about read or update failures. + * For example, before turning on Power Amplifier for the first + * time, reading WSA884X_TEMP_DIN_MSB will always return 0. + * Instead, check if returned value is within reasonable + * thresholds. + */ + regmap_update_bits(wsa884x->regmap, WSA884X_PA_FSM_BYP0, mask, mask); + + regmap_update_bits(wsa884x->regmap, WSA884X_TADC_VALUE_CTL, + WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, + FIELD_PREP(WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x0)); + + regmap_read(wsa884x->regmap, WSA884X_TEMP_DIN_MSB, &dmeas_msb); + regmap_read(wsa884x->regmap, WSA884X_TEMP_DIN_LSB, &dmeas_lsb); + + regmap_update_bits(wsa884x->regmap, WSA884X_TADC_VALUE_CTL, + WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, + FIELD_PREP(WSA884X_TADC_VALUE_CTL_TEMP_VALUE_RD_EN_MASK, 0x1)); + + regmap_read(wsa884x->regmap, WSA884X_OTP_REG_1, &d1_msb); + regmap_read(wsa884x->regmap, WSA884X_OTP_REG_2, &d1_lsb); + regmap_read(wsa884x->regmap, WSA884X_OTP_REG_3, &d2_msb); + regmap_read(wsa884x->regmap, WSA884X_OTP_REG_4, &d2_lsb); + + regmap_update_bits(wsa884x->regmap, WSA884X_PA_FSM_BYP0, mask, 0x0); + + dmeas = (((dmeas_msb & 0xff) << 0x8) | (dmeas_lsb & 0xff)) >> 0x6; + d1 = (((d1_msb & 0xff) << 0x8) | (d1_lsb & 0xff)) >> 0x6; + d2 = (((d2_msb & 0xff) << 0x8) | (d2_lsb & 0xff)) >> 0x6; + + if (d1 == d2) { + /* Incorrect data in OTP? */ + ret = -EINVAL; + goto out; + } + + val = WSA884X_T1_TEMP + (((dmeas - d1) * (WSA884X_T2_TEMP - WSA884X_T1_TEMP))/(d2 - d1)); + + dev_dbg(wsa884x->dev, "Measured temp %ld (dmeas=%d, d1=%d, d2=%d)\n", + val, dmeas, d1, d2); + + if ((val > WSA884X_LOW_TEMP_THRESHOLD) && + (val < WSA884X_HIGH_TEMP_THRESHOLD)) { + wsa884x->temperature = val; + *temp = val; + ret = 0; + } else { + ret = -EAGAIN; + } + +out: + pm_runtime_mark_last_busy(wsa884x->dev); + pm_runtime_put_autosuspend(wsa884x->dev); + + return ret; +} + +static umode_t wsa884x_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + break; + } + + return 0; +} + +static int wsa884x_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + int ret; + + switch (attr) { + case hwmon_temp_input: + ret = wsa884x_get_temp(dev_get_drvdata(dev), temp); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static const struct hwmon_channel_info *const wsa884x_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops wsa884x_hwmon_ops = { + .is_visible = wsa884x_hwmon_is_visible, + .read = wsa884x_hwmon_read, +}; + +static const struct hwmon_chip_info wsa884x_hwmon_chip_info = { + .ops = &wsa884x_hwmon_ops, + .info = wsa884x_hwmon_info, +}; + static void wsa884x_reset_powerdown(void *data) { struct wsa884x_priv *wsa884x = data; @@ -1866,6 +2053,8 @@ static int wsa884x_probe(struct sdw_slave *pdev, if (!wsa884x) return -ENOMEM; + mutex_init(&wsa884x->sp_lock); + for (i = 0; i < WSA884X_SUPPLIES_NUM; i++) wsa884x->supplies[i].supply = wsa884x_supply_name[i]; @@ -1923,6 +2112,18 @@ static int wsa884x_probe(struct sdw_slave *pdev, regcache_cache_only(wsa884x->regmap, true); wsa884x->hw_init = true; + if (IS_REACHABLE(CONFIG_HWMON)) { + struct device *hwmon; + + hwmon = devm_hwmon_device_register_with_info(dev, "wsa884x", + wsa884x, + &wsa884x_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon)) + return dev_err_probe(dev, PTR_ERR(hwmon), + "Failed to register hwmon sensor\n"); + } + pm_runtime_set_autosuspend_delay(dev, 3000); pm_runtime_use_autosuspend(dev); pm_runtime_mark_last_busy(dev); |