diff --git a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts index e5944a1b255f..7bbcb280b05f 100644 --- a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts +++ b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts @@ -27,6 +27,34 @@ serial1 = &uart11; }; + /* + * The sensor I2C bus sits on TLMM eGPIO pads that have no AP QUP + * function; on the stock OS it is driven by an island QUP of the + * Snapdragon Sensor Core (ADSP). From the AP the pads are only + * usable as software GPIOs, so bit-bang the bus. + * Devices on the bus: QMC6308 magnetometer @ 0x2c, SPL07 + * barometer @ 0x76, STK3BCx ALS/proximity @ 0x48. + */ + i2c-sensors { + compatible = "i2c-gpio"; + sda-gpios = <&tlmm 153 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + scl-gpios = <&tlmm 154 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + i2c-gpio,delay-us = <2>; + pinctrl-0 = <&sensor_i2c_default>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + + magnetometer@2c { + compatible = "qstcorp,qmc6308"; + reg = <0x2c>; + vdd-supply = <&vreg_l10b>; + mount-matrix = "0", "-1", "0", + "1", "0", "0", + "0", "0", "1"; + }; + }; + gpio-keys { compatible = "gpio-keys"; @@ -209,14 +237,34 @@ sound { compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard"; model = "Fairphone (Gen. 6)"; - // audio-routing = ... + audio-routing = "AMIC1", "MIC BIAS1", + "AMIC2", "MIC BIAS2", + "AMIC3", "MIC BIAS3", + "TX SWR_INPUT0", "ADC1_OUTPUT", + "TX SWR_INPUT1", "ADC2_OUTPUT", + "TX SWR_INPUT2", "ADC3_OUTPUT"; pinctrl-0 = <&lpi_i2s2_active>; pinctrl-1 = <&lpi_i2s2_sleep>; pinctrl-names = "default", "sleep"; - /* + wcd-capture-dai-link { + link-name = "WCD Capture"; + + codec { + sound-dai = <&wcd9378 1>, <&swr2 0>, <&lpass_txmacro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + i2s-dai-link { link-name = "Senary MI2S Playback"; @@ -232,7 +280,29 @@ sound-dai = <&q6apm>; }; }; - */ + }; + + wcd9378: audio-codec { + compatible = "qcom,wcd9378-codec"; + + pinctrl-0 = <&wcd_reset_n_active>; + pinctrl-names = "default"; + + reset-gpios = <&tlmm 162 GPIO_ACTIVE_LOW>; + + vdd-buck-supply = <&vreg_l8b>; + vdd-rxtx-supply = <&vreg_l7b>; + vdd-io-supply = <&vreg_l7b>; + vdd-mic-bias-supply = <&vreg_bob>; + + qcom,micbias1-microvolt = <1800000>; + qcom,micbias2-microvolt = <1800000>; + qcom,micbias3-microvolt = <1800000>; + + qcom,rx-device = <&wcd9378_rx>; + qcom,tx-device = <&wcd9378_tx>; + + #sound-dai-cells = <1>; }; thermal-zones { @@ -700,9 +770,28 @@ }; &i2c1 { - /* Samsung NFC @ 0x27 */ - status = "okay"; + + /* + * Samsung S3NRN4V NFC controller. XI is driven by the RF_CLK2 PMIC + * buffer; the chip has no oscillator of its own and gates the clock + * via its CLK_REQ line, so the clock must be voted on in response + * to it. + */ + nfc@27 { + compatible = "samsung,s3nrn4v"; + reg = <0x27>; + + clk-req-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>; + clocks = <&rpmhcc RPMH_RF_CLK2>; + en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&tlmm>; + interrupts = <31 IRQ_TYPE_EDGE_RISING>; + pinctrl-0 = <&nfc_clk_req_default>, <&nfc_irq_default>, + <&nfc_pd_default>; + pinctrl-names = "default"; + wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>; + }; }; &i2c3 { @@ -1086,11 +1175,56 @@ }; }; +&swr1 { + status = "okay"; + + /* WCD9378 RX */ + wcd9378_rx: codec@0,4 { + compatible = "sdw20217011000"; + reg = <0 4>; + qcom,rx-port-mapping = <1 2 3 4 5>; + }; +}; + +&swr2 { + status = "okay"; + + /* WCD9378 TX */ + wcd9378_tx: codec@0,3 { + compatible = "sdw20217011000"; + reg = <0 3>; + + /* + * WCD9378 TX port 1 (ADC1) <=> SWR2 port 1 (SWRM_TX1) + * WCD9378 TX port 2 (ADC2) <=> SWR2 port 1 + * WCD9378 TX port 3 (ADC3) <=> SWR2 port 1 + * WCD9378 TX port 4 (DMIC0,1, MBHC) <=> SWR2 port 2 + * WCD9378 TX port 5 (DMIC2..5) <=> SWR2 port 3 + */ + qcom,tx-port-mapping = <1 1 1 2 3>; + }; +}; + &tlmm { gpio-reserved-ranges = <8 4>, /* Fingerprint SPI */ <13 1>, /* NC */ <63 2>; /* WLAN UART */ + nfc_clk_req_default: nfc-clk-req-default-state { + pins = "gpio6"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + sensor_i2c_default: sensor-i2c-default-state { + /* SDA, SCL; external pull-ups to vreg_l10b */ + pins = "gpio153", "gpio154"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + ts_active: ts-irq-active-state { pins = "gpio19"; function = "gpio"; @@ -1105,6 +1239,13 @@ bias-disable; }; + nfc_irq_default: nfc-irq-default-state { + pins = "gpio31"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + qup_uart11_sleep_cts: qup-uart11-sleep-cts-state { pins = "gpio48"; function = "gpio"; @@ -1172,6 +1313,17 @@ bias-pull-down; }; + /* + * Pulled up so the NFC chip stays powered down while the line is + * not driven (it is the chip's active-high power-down input). + */ + nfc_pd_default: nfc-pd-default-state { + pins = "gpio56"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + sdc2_card_det_n: sdc2-card-det-state { pins = "gpio65"; function = "gpio"; @@ -1213,6 +1365,13 @@ drive-strength = <2>; bias-pull-down; }; + + wcd_reset_n_active: wcd-reset-n-active-state { + pins = "gpio162"; + function = "gpio"; + drive-strength = <16>; + output-high; + }; }; &uart5 { diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index fb313e591e85..7cd4d751e1d2 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -198,6 +198,17 @@ config INFINEON_TLV493D To compile this driver as a module, choose M here: the module will be called tlv493d. +config QMC6308 + tristate "QST QMC6308 3-Axis Magnetic Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the QST QMC6308 3-Axis + Magnetic Sensor. + + To compile this driver as a module, choose M here: the + module will be called qmc6308. + config SENSORS_HMC5843 tristate select IIO_BUFFER diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 5bd227f8c120..59ea5cf17287 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -26,6 +26,8 @@ obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o +obj-$(CONFIG_QMC6308) += qmc6308.o + obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o diff --git a/drivers/iio/magnetometer/qmc6308.c b/drivers/iio/magnetometer/qmc6308.c new file mode 100644 index 000000000000..ceb4b98402bb --- /dev/null +++ b/drivers/iio/magnetometer/qmc6308.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Support for QST QMC6308 3-Axis Magnetic Sensor on I2C bus. + * + * Copyright (C) 2026 Jorijn van der Graaf + * + * Datasheet available at + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define QMC6308_REG_ID 0x00 +#define QMC6308_REG_X_LSB 0x01 +#define QMC6308_REG_STATUS 0x09 +#define QMC6308_REG_CTRL1 0x0A +#define QMC6308_REG_CTRL2 0x0B +#define QMC6308_REG_CTRL3 0x0D +#define QMC6308_REG_CTRL4 0x29 + +#define QMC6308_CHIP_ID 0x80 + +/* Control register 1 */ +#define QMC6308_MODE_MASK GENMASK(1, 0) +#define QMC6308_ODR_MASK GENMASK(3, 2) +#define QMC6308_OSR1_MASK GENMASK(5, 4) +#define QMC6308_OSR2_MASK GENMASK(7, 6) + +#define QMC6308_MODE_SUSPEND 0x00 +#define QMC6308_MODE_NORMAL 0x01 + +#define QMC6308_ODR_10HZ 0x00 +#define QMC6308_ODR_50HZ 0x01 +#define QMC6308_ODR_100HZ 0x02 +#define QMC6308_ODR_200HZ 0x03 + +#define QMC6308_OSR1_8 0x00 +#define QMC6308_OSR1_4 0x01 +#define QMC6308_OSR1_2 0x02 +#define QMC6308_OSR1_1 0x03 + +/* Control register 2 */ +#define QMC6308_SET_RESET_MASK GENMASK(1, 0) +#define QMC6308_RNG_MASK GENMASK(3, 2) +#define QMC6308_SELF_TEST BIT(6) +#define QMC6308_SOFT_RST BIT(7) + +#define QMC6308_SET_RESET_ON 0x00 + +#define QMC6308_RNG_30G 0x00 +#define QMC6308_RNG_12G 0x01 +#define QMC6308_RNG_8G 0x02 +#define QMC6308_RNG_2G 0x03 + +/* Status register */ +#define QMC6308_STATUS_DRDY BIT(0) +#define QMC6308_STATUS_OVFL BIT(1) + +/* + * Power-on completion time (datasheet Table 7), also used as a + * conservative bound after soft reset, for which the datasheet + * gives no figure. + */ +#define QMC6308_POR_US 250 + +#define QMC6308_AUTOSUSPEND_DELAY_MS 500 + +struct qmc6308_data { + struct regmap *regmap; + /* + * Protect data->range/odr/osr. + * Protect poll and read during measurement (reading the status + * register clears DRDY). + */ + struct mutex mutex; + struct iio_mount_matrix orientation; + u8 range; + u8 odr; + u8 osr; +}; + +enum qmc6308_axis { + QMC6308_AXIS_X, + QMC6308_AXIS_Y, + QMC6308_AXIS_Z, +}; + +static const int qmc6308_odr_avail[] = { + [QMC6308_ODR_10HZ] = 10, + [QMC6308_ODR_50HZ] = 50, + [QMC6308_ODR_100HZ] = 100, + [QMC6308_ODR_200HZ] = 200, +}; + +static const int qmc6308_osr1_avail[] = { + [QMC6308_OSR1_8] = 8, + [QMC6308_OSR1_4] = 4, + [QMC6308_OSR1_2] = 2, + [QMC6308_OSR1_1] = 1, +}; + +/* + * Sensitivity is 1000/2500/3750/15000 LSB/Gauss for the + * +-30/12/8/2 Gauss ranges respectively. + */ +static const int qmc6308_scales[][2] = { + [QMC6308_RNG_30G] = { 0, 1000000 }, + [QMC6308_RNG_12G] = { 0, 400000 }, + [QMC6308_RNG_8G] = { 0, 266667 }, + [QMC6308_RNG_2G] = { 0, 66667 }, +}; + +static int qmc6308_set_mode(struct qmc6308_data *data, unsigned int mode) +{ + return regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_MODE_MASK, + FIELD_PREP(QMC6308_MODE_MASK, mode)); +} + +static int qmc6308_take_measurement(struct iio_dev *indio_dev, int index, + int *val) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + unsigned int status; + __le16 buf[3]; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + /* EACCES means a read raced runtime PM disable on suspend */ + if (ret != -EACCES) + dev_err(dev, "Failed to power on (%d)\n", ret); + return ret; + } + + scoped_guard(mutex, &data->mutex) { + /* 50ms headroom over the slowest ODR (10Hz) */ + ret = regmap_read_poll_timeout(map, QMC6308_REG_STATUS, + status, + (status & QMC6308_STATUS_DRDY), + 2 * USEC_PER_MSEC, + 150 * USEC_PER_MSEC); + if (ret) + goto out_rpm_put; + + ret = regmap_bulk_read(map, QMC6308_REG_X_LSB, buf, + sizeof(buf)); + if (ret) + goto out_rpm_put; + + if (status & QMC6308_STATUS_OVFL) + ret = -ERANGE; + } + +out_rpm_put: + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + *val = (s16)le16_to_cpu(buf[index]); + + return 0; +} + +static int qmc6308_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = qmc6308_take_measurement(indio_dev, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->mutex); + + *val = qmc6308_scales[data->range][0]; + *val2 = qmc6308_scales[data->range][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->mutex); + + *val = qmc6308_odr_avail[data->odr]; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->mutex); + + *val = qmc6308_osr1_avail[data->osr]; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + unsigned int i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(qmc6308_scales); i++) { + if (val2 == qmc6308_scales[i][1]) + break; + } + if (i == ARRAY_SIZE(qmc6308_scales)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL2, + QMC6308_RNG_MASK, + FIELD_PREP(QMC6308_RNG_MASK, i)); + if (ret) + return ret; + + data->range = i; + + /* + * The data registers still hold (and DRDY still + * advertises) a sample converted at the previous range; + * discard it so that the next read returns data matching + * the new scale. + */ + return regmap_read(data->regmap, QMC6308_REG_STATUS, + &status); + } + case IIO_CHAN_INFO_SAMP_FREQ: { + for (i = 0; i < ARRAY_SIZE(qmc6308_odr_avail); i++) { + if (val == qmc6308_odr_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_odr_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_ODR_MASK, + FIELD_PREP(QMC6308_ODR_MASK, i)); + if (ret) + return ret; + + data->odr = i; + + return 0; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + for (i = 0; i < ARRAY_SIZE(qmc6308_osr1_avail); i++) { + if (val == qmc6308_osr1_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_osr1_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_OSR1_MASK, + FIELD_PREP(QMC6308_OSR1_MASK, i)); + if (ret) + return ret; + + data->osr = i; + + return 0; + } + default: + return -EINVAL; + } +} + +static int qmc6308_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = qmc6308_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_odr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = qmc6308_osr1_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_osr1_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)qmc6308_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(qmc6308_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static const struct iio_mount_matrix * +qmc6308_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info qmc6308_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, qmc6308_get_mount_matrix), + { } +}; + +static const struct iio_info qmc6308_info = { + .read_raw = qmc6308_read_raw, + .write_raw = qmc6308_write_raw, + .read_avail = qmc6308_read_avail, + .write_raw_get_fmt = qmc6308_write_raw_get_fmt, +}; + +static int qmc6308_init(struct qmc6308_data *data) +{ + struct regmap *map = data->regmap; + unsigned int reg; + int ret; + + ret = regmap_read(map, QMC6308_REG_ID, ®); + if (ret) + return ret; + + /* Allow unknown IDs so that fallback compatibles work */ + if (reg != QMC6308_CHIP_ID) + dev_warn(regmap_get_device(map), + "Unknown chip id: 0x%02x, continuing\n", reg); + + /* The SOFT_RST bit is not auto-cleared and must be written back 0 */ + ret = regmap_write(map, QMC6308_REG_CTRL2, QMC6308_SOFT_RST); + if (ret) + return ret; + + fsleep(QMC6308_POR_US); + + data->range = QMC6308_RNG_30G; + + ret = regmap_write(map, QMC6308_REG_CTRL2, + FIELD_PREP(QMC6308_SET_RESET_MASK, + QMC6308_SET_RESET_ON) | + FIELD_PREP(QMC6308_RNG_MASK, data->range)); + if (ret) + return ret; + + data->odr = QMC6308_ODR_50HZ; + data->osr = QMC6308_OSR1_8; + + return regmap_write(map, QMC6308_REG_CTRL1, + FIELD_PREP(QMC6308_MODE_MASK, + QMC6308_MODE_NORMAL) | + FIELD_PREP(QMC6308_ODR_MASK, data->odr) | + FIELD_PREP(QMC6308_OSR1_MASK, data->osr)); +} + +static void qmc6308_power_down_action(void *priv) +{ + struct qmc6308_data *data = priv; + + if (!pm_runtime_status_suspended(regmap_get_device(data->regmap))) + qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static bool qmc6308_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= QMC6308_REG_X_LSB && reg <= QMC6308_REG_STATUS; +} + +static bool qmc6308_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QMC6308_REG_CTRL1: + case QMC6308_REG_CTRL2: + case QMC6308_REG_CTRL3: + case QMC6308_REG_CTRL4: + return true; + default: + return false; + } +} + +static const struct regmap_config qmc6308_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QMC6308_REG_CTRL4, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = qmc6308_volatile_reg, + .writeable_reg = qmc6308_writable_reg, +}; + +#define QMC6308_CHANNEL(_axis) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = QMC6308_AXIS_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = qmc6308_ext_info, \ + } + +static const struct iio_chan_spec qmc6308_channels[] = { + QMC6308_CHANNEL(X), + QMC6308_CHANNEL(Y), + QMC6308_CHANNEL(Z), +}; + +static int qmc6308_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qmc6308_data *data; + struct iio_dev *indio_dev; + struct regmap *map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + map = devm_regmap_init_i2c(client, &qmc6308_regmap_config); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDD regulator\n"); + + fsleep(QMC6308_POR_US); + + data = iio_priv(indio_dev); + data->regmap = map; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read mount matrix\n"); + + indio_dev->name = "qmc6308"; + indio_dev->info = &qmc6308_info; + indio_dev->channels = qmc6308_channels; + indio_dev->num_channels = ARRAY_SIZE(qmc6308_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = qmc6308_init(data); + if (ret) + return dev_err_probe(dev, ret, "qmc6308 init failed\n"); + + pm_runtime_set_active(dev); + + ret = devm_add_action_or_reset(dev, qmc6308_power_down_action, data); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, QMC6308_AUTOSUSPEND_DELAY_MS); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_put_autosuspend(dev); + + return devm_iio_device_register(dev, indio_dev); +} + +static int qmc6308_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + + return qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static int qmc6308_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + int ret; + + ret = qmc6308_set_mode(data, QMC6308_MODE_NORMAL); + if (ret) + return ret; + + /* + * DRDY may still be set for a sample converted before the last + * suspend; clear it so the next read waits for fresh data. + */ + return regmap_read(data->regmap, QMC6308_REG_STATUS, &status); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(qmc6308_pm_ops, qmc6308_runtime_suspend, + qmc6308_runtime_resume, NULL); + +static const struct of_device_id qmc6308_match[] = { + { .compatible = "qstcorp,qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(of, qmc6308_match); + +static const struct i2c_device_id qmc6308_id[] = { + { .name = "qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qmc6308_id); + +static struct i2c_driver qmc6308_driver = { + .driver = { + .name = "qmc6308", + .of_match_table = qmc6308_match, + .pm = pm_ptr(&qmc6308_pm_ops), + }, + .id_table = qmc6308_id, + .probe = qmc6308_probe, +}; +module_i2c_driver(qmc6308_driver); + +MODULE_DESCRIPTION("QST QMC6308 3-Axis Magnetic Sensor driver"); +MODULE_AUTHOR("Jorijn van der Graaf "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c index af0fa8bd970b..59317eaad7ac 100644 --- a/drivers/nfc/s3fwrn5/core.c +++ b/drivers/nfc/s3fwrn5/core.c @@ -122,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb) return 0; } +static int s3fwrn5_nci_setup(struct nci_dev *ndev) +{ + struct s3fwrn5_info *info = nci_get_drvdata(ndev); + + /* + * Runs after CORE_RESET, before CORE_INIT. The S3NRN4V needs its + * reference clock configured here (the downstream stack does it in the + * bootloader, before CORE_RESET, but this is the earliest hook the NCI + * core offers and the chip accepts it). + */ + if (info->variant == S3FWRN5_VARIANT_S3NRN4V) + return s3fwrn5_nci_clk_cfg(info); + + return 0; +} + static int s3fwrn5_nci_post_setup(struct nci_dev *ndev) { struct s3fwrn5_info *info = nci_get_drvdata(ndev); int ret; + if (info->variant == S3FWRN5_VARIANT_S3NRN4V) { + /* + * The S3NRN4V ships with working firmware behind a bootloader + * protocol this driver does not implement, so there is no + * download step; the NCI core has already done CORE_RESET + + * CORE_INIT. Just (re)load the RF registers via DUAL_OPTION. + */ + ret = s3fwrn5_nci_rf_configure_dual(info, "sec_s3nrn4v_hwreg.bin", + "sec_s3nrn4v_swreg.bin"); + /* + * Keep going even if the blobs could not be loaded: the chip + * still enumerates and falls back to the RF registers programmed + * in its flash, so NFC may work anyway. + */ + if (ret < 0) + dev_warn(&ndev->nfc_dev->dev, + "rfreg configure failed (%d)\n", ret); + return 0; + } + if (s3fwrn5_firmware_init(info)) { //skip bootloader mode return 0; @@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = { .open = s3fwrn5_nci_open, .close = s3fwrn5_nci_close, .send = s3fwrn5_nci_send, + .setup = s3fwrn5_nci_setup, .post_setup = s3fwrn5_nci_post_setup, .prop_ops = s3fwrn5_nci_prop_ops, .n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops), }; int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, - const struct s3fwrn5_phy_ops *phy_ops) + const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant) { struct s3fwrn5_info *info; int ret; @@ -170,6 +207,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, info->phy_id = phy_id; info->pdev = pdev; info->phy_ops = phy_ops; + info->variant = variant; mutex_init(&info->mutex); s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c index 91b8d1445efd..7d20e737e402 100644 --- a/drivers/nfc/s3fwrn5/i2c.c +++ b/drivers/nfc/s3fwrn5/i2c.c @@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy { struct i2c_client *i2c_dev; struct clk *clk; + /* + * Optional hardware clock-request handshake. When a CLK_REQ GPIO is + * wired, the chip drives it high while it needs its XI clock -- notably + * to generate the poll/reader carrier -- and the clock is gated on it + * instead of being left always-on (which never lets the chip's TX PLL + * lock on a fresh clock start, leaving it unable to poll). + */ + struct gpio_desc *gpio_clk_req; + bool clk_on; + struct mutex clk_lock; /* serialises clk_on against the CLK_REQ irq */ + unsigned int irq_skip:1; }; +static void s3fwrn5_i2c_clk_set_locked(struct s3fwrn5_i2c_phy *phy, bool on) +{ + lockdep_assert_held(&phy->clk_lock); + + if (on && !phy->clk_on) { + int ret = clk_prepare_enable(phy->clk); + + if (ret == 0) + phy->clk_on = true; + else + dev_warn_once(&phy->i2c_dev->dev, + "failed to enable clock (%d); NFC may not poll\n", + ret); + } else if (!on && phy->clk_on) { + clk_disable_unprepare(phy->clk); + phy->clk_on = false; + } +} + +/* + * Apply the current CLK_REQ level. Reading the GPIO under clk_lock makes + * concurrent callers (the CLK_REQ irq thread and the probe-time seeding) + * safe: whoever runs last applies a level read after the earlier update, + * never a stale one. + */ +static void s3fwrn5_i2c_clk_sync(struct s3fwrn5_i2c_phy *phy) +{ + int level; + + mutex_lock(&phy->clk_lock); + level = gpiod_get_value_cansleep(phy->gpio_clk_req); + if (level >= 0) + s3fwrn5_i2c_clk_set_locked(phy, level > 0); + else + dev_warn_once(&phy->i2c_dev->dev, + "failed to read CLK_REQ (%d); keeping clock state\n", + level); + mutex_unlock(&phy->clk_lock); +} + +static void s3fwrn5_i2c_clk_disable_action(void *data) +{ + struct s3fwrn5_i2c_phy *phy = data; + + mutex_lock(&phy->clk_lock); + s3fwrn5_i2c_clk_set_locked(phy, false); + mutex_unlock(&phy->clk_lock); +} + +static irqreturn_t s3fwrn5_i2c_clk_req_thread(int irq, void *phy_id) +{ + s3fwrn5_i2c_clk_sync(phy_id); + + return IRQ_HANDLED; +} + static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode) { struct s3fwrn5_i2c_phy *phy = phy_id; @@ -146,6 +213,7 @@ out: static int s3fwrn5_i2c_probe(struct i2c_client *client) { + enum s3fwrn5_variant variant; struct s3fwrn5_i2c_phy *phy; int ret; @@ -172,15 +240,61 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client) * S3FWRN5 depends on a clock input ("XI" pin) to function properly. * Depending on the hardware configuration this could be an always-on * oscillator or some external clock that must be explicitly enabled. - * Make sure the clock is running before starting S3FWRN5. + * + * If a CLK_REQ GPIO is wired, the chip gates the clock itself (driving + * CLK_REQ high when it needs XI); service that handshake. Otherwise just + * make sure the clock is running before starting S3FWRN5. */ - phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL); - if (IS_ERR(phy->clk)) - return dev_err_probe(&client->dev, PTR_ERR(phy->clk), - "failed to get clock\n"); + mutex_init(&phy->clk_lock); + phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req", + GPIOD_IN); + if (IS_ERR(phy->gpio_clk_req)) + return PTR_ERR(phy->gpio_clk_req); + if (phy->gpio_clk_req) { + int clk_req_irq; + + phy->clk = devm_clk_get_optional(&client->dev, NULL); + if (IS_ERR(phy->clk)) + return dev_err_probe(&client->dev, PTR_ERR(phy->clk), + "failed to get clock\n"); + + /* + * Unlike the always-on branch below, this clock is enabled by + * hand from the CLK_REQ handler, so devm will not disable it on + * unbind. Gate it off explicitly if it is still on at teardown. + */ + ret = devm_add_action_or_reset(&client->dev, + s3fwrn5_i2c_clk_disable_action, + phy); + if (ret) + return ret; + + clk_req_irq = gpiod_to_irq(phy->gpio_clk_req); + if (clk_req_irq < 0) + return clk_req_irq; + + ret = devm_request_threaded_irq(&client->dev, clk_req_irq, NULL, + s3fwrn5_i2c_clk_req_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "s3fwrn5_clk_req", phy); + if (ret) + return ret; + + /* Seed the clock state from the current CLK_REQ level. */ + s3fwrn5_i2c_clk_sync(phy); + } else { + phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL); + if (IS_ERR(phy->clk)) + return dev_err_probe(&client->dev, PTR_ERR(phy->clk), + "failed to get clock\n"); + } + + variant = (uintptr_t)i2c_get_match_data(client); ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev, - &i2c_phy_ops); + &i2c_phy_ops, variant); if (ret < 0) return ret; @@ -205,13 +319,17 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id s3fwrn5_i2c_id_table[] = { - { S3FWRN5_I2C_DRIVER_NAME }, - {} + { .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL }, + { .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V }, + { } }; MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table); -static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = { - { .compatible = "samsung,s3fwrn5-i2c", }, +static const struct of_device_id of_s3fwrn5_i2c_match[] = { + { .compatible = "samsung,s3fwrn5-i2c", + .data = (void *)S3FWRN5_VARIANT_FWDL, }, + { .compatible = "samsung,s3nrn4v", + .data = (void *)S3FWRN5_VARIANT_S3NRN4V, }, {} }; MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match); @@ -219,7 +337,7 @@ MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match); static struct i2c_driver s3fwrn5_i2c_driver = { .driver = { .name = S3FWRN5_I2C_DRIVER_NAME, - .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match), + .of_match_table = of_s3fwrn5_i2c_match, }, .probe = s3fwrn5_i2c_probe, .remove = s3fwrn5_i2c_remove, diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c index 5a9de11bbece..7034fb810e18 100644 --- a/drivers/nfc/s3fwrn5/nci.c +++ b/drivers/nfc/s3fwrn5/nci.c @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #include "s3fwrn5.h" #include "nci.h" @@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb) return 0; } -const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = { +const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = { { .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, NCI_PROP_SET_RFREG), @@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = { NCI_PROP_FW_CFG), .rsp = s3fwrn5_nci_prop_rsp, }, + { + .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, + NCI_PROP_DUAL_OPTION), + .rsp = s3fwrn5_nci_prop_rsp, + }, }; #define S3FWRN5_RFREG_SECTION_SIZE 252 @@ -117,3 +125,112 @@ out: release_firmware(fw); return ret; } + +/* + * Configure the reference clock. The S3NRN4V expects the single-byte FW_CFG + * form (just the clock-speed selector). The downstream stack sends this in the + * bootloader before CORE_RESET; the earliest the mainline NCI core lets us in + * is the ->setup hook (after CORE_RESET, before CORE_INIT), which works. + */ +int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info) +{ + u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED; + + return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed); +} + +/* + * S3NRN4V RF register update. The HW and SW register blobs are merged into a + * single stream (HW first) and pushed via the DUAL_OPTION command: + * START_UPDATE, one SET_OPTION per 252-byte section, then STOP_UPDATE carrying + * a 16-bit checksum (running sum of the merged stream as 32-bit words). + */ +int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info, + const char *hw_name, const char *sw_name) +{ + const struct firmware *hw_fw = NULL, *sw_fw = NULL; + struct nci_prop_dual_set_option_cmd set_option; + struct device *dev = &info->ndev->nfc_dev->dev; + size_t merged_size, i, len; + u8 *merged = NULL; + u8 stop_cmd[3]; + u32 checksum; + u8 sub_oid; + int ret; + + ret = request_firmware(&hw_fw, hw_name, dev); + if (ret < 0) + return ret; + ret = request_firmware(&sw_fw, sw_name, dev); + if (ret < 0) + goto out_hw; + + merged_size = hw_fw->size + sw_fw->size; + + /* + * The stream is checksummed as 32-bit words and pushed in at most 256 + * sections (the section index is a single byte); reject blobs that + * would silently break either. + */ + if (merged_size % 4 || + merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) { + dev_err(dev, "invalid rfreg blob size (%zu)\n", merged_size); + ret = -EINVAL; + goto out; + } + + merged = kmalloc(merged_size, GFP_KERNEL); + if (!merged) { + ret = -ENOMEM; + goto out; + } + memcpy(merged, hw_fw->data, hw_fw->size); + memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size); + + /* Running sum of the merged stream as little-endian 32-bit words. */ + checksum = 0; + for (i = 0; i + 4 <= merged_size; i += 4) + checksum += get_unaligned_le32(merged + i); + + dev_dbg(dev, "rfreg dual-option update: %s + %s\n", hw_name, sw_name); + + /* START_UPDATE */ + sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE; + ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid); + if (ret < 0) { + dev_err(dev, "Unable to start rfreg update\n"); + goto out; + } + + /* SET_OPTION per section */ + set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION; + set_option.index = 0; + for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) { + len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE); + memcpy(set_option.data, merged + i, len); + ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, + len + 2, (__u8 *)&set_option); + if (ret < 0) { + dev_err(dev, "rfreg update error (code=%d)\n", ret); + goto out; + } + set_option.index++; + } + + /* STOP_UPDATE with checksum */ + stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE; + put_unaligned_le16(checksum, &stop_cmd[1]); + ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd); + if (ret < 0) { + dev_err(dev, "Unable to stop rfreg update\n"); + goto out; + } + + dev_dbg(dev, "rfreg dual-option update: success\n"); +out: + kfree(merged); + release_firmware(sw_fw); +out_hw: + release_firmware(hw_fw); + return ret; +} diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h index bc4bce2bbc4d..23179ba095a1 100644 --- a/drivers/nfc/s3fwrn5/nci.h +++ b/drivers/nfc/s3fwrn5/nci.h @@ -40,6 +40,13 @@ struct nci_prop_stop_rfreg_rsp { #define NCI_PROP_FW_CFG 0x28 +/* + * Single-byte FW_CFG payload (clock-speed selector) for the S3NRN4V reference + * clock. Taken from the vendor configuration for this part (the encoding is + * not documented). + */ +#define NCI_PROP_FW_CFG_CLK_SPEED 0x11 + struct nci_prop_fw_cfg_cmd { __u8 clk_type; __u8 clk_speed; @@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp { __u8 status; }; -extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4]; +/* + * The S3NRN4V updates its RF registers through a single "dual option" command + * (a sub-OID selects the operation) instead of the START/SET/STOP_RFREG + * opcodes above, and expects the HW and SW register blobs merged into one + * stream. + */ +#define NCI_PROP_DUAL_OPTION 0x2a + +#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01 +#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02 +#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03 + +#define NCI_PROP_DUAL_SECTION_SIZE 252 + +struct nci_prop_dual_set_option_cmd { + __u8 sub_oid; /* NCI_PROP_DUAL_SUB_SET_OPTION */ + __u8 index; + __u8 data[NCI_PROP_DUAL_SECTION_SIZE]; +}; + +extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5]; int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name); +int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info, + const char *hw_name, const char *sw_name); +int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info); #endif /* __LOCAL_S3FWRN5_NCI_H_ */ diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h index 2b492236090b..2d8c12091fba 100644 --- a/drivers/nfc/s3fwrn5/s3fwrn5.h +++ b/drivers/nfc/s3fwrn5/s3fwrn5.h @@ -21,6 +21,17 @@ enum s3fwrn5_mode { S3FWRN5_MODE_FW, }; +enum s3fwrn5_variant { + /* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */ + S3FWRN5_VARIANT_FWDL, + /* + * S3NRN4V: ships with working firmware behind a bootloader protocol + * this driver does not implement; skip the download, configure the + * clock (FW_CFG) and update the RF registers via the DUAL_OPTION cmd. + */ + S3FWRN5_VARIANT_S3NRN4V, +}; + struct s3fwrn5_phy_ops { void (*set_wake)(void *id, bool sleep); void (*set_mode)(void *id, enum s3fwrn5_mode); @@ -36,6 +47,7 @@ struct s3fwrn5_info { const struct s3fwrn5_phy_ops *phy_ops; struct s3fwrn5_fw_info fw_info; + enum s3fwrn5_variant variant; struct mutex mutex; }; @@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb) } int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, - const struct s3fwrn5_phy_ops *phy_ops); + const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant); void s3fwrn5_remove(struct nci_dev *ndev); int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c index 540a4ddb0b05..47172d739a41 100644 --- a/drivers/nfc/s3fwrn5/uart.c +++ b/drivers/nfc/s3fwrn5/uart.c @@ -137,7 +137,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev) } ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev, - &uart_phy_ops); + &uart_phy_ops, S3FWRN5_VARIANT_FWDL); if (ret < 0) goto err_serdev; diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 3d8f5a81eff1..9d8aa6a75c48 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -976,6 +976,20 @@ static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus, struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); int ret, i, len; + if (msg->page) { + ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page1, + msg->dev_num, + SDW_SCP_ADDRPAGE1); + if (ret) + return SDW_CMD_IGNORED; + + ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page2, + msg->dev_num, + SDW_SCP_ADDRPAGE2); + if (ret) + return SDW_CMD_IGNORED; + } + if (msg->flags == SDW_MSG_FLAG_READ) { for (i = 0; i < msg->len;) { len = min(msg->len - i, QCOM_SWRM_MAX_RD_LEN); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 269c31ce0814..d902df62b184 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -301,6 +301,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_WCD9335 imply SND_SOC_WCD934X imply SND_SOC_WCD937X_SDW + imply SND_SOC_WCD9378_SDW imply SND_SOC_WCD938X_SDW imply SND_SOC_WCD939X_SDW imply SND_SOC_LPASS_MACRO_COMMON @@ -2431,6 +2432,24 @@ config SND_SOC_WCD937X_SDW via soundwire. To compile this codec driver say Y or m. +config SND_SOC_WCD9378 + depends on SND_SOC_WCD9378_SDW + tristate + depends on SOUNDWIRE || !SOUNDWIRE + select SND_SOC_WCD_COMMON + +config SND_SOC_WCD9378_SDW + tristate "WCD9378 Codec - SDW" + select SND_SOC_WCD9378 + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + help + The WCD9378 is an audio codec IC connected to the host over + SoundWire, found on Qualcomm SM7635 boards such as the + Fairphone 6. It has three ADCs for analog microphones, mic + bias supplies, headphone/earpiece outputs and MBHC. + To compile this codec driver say Y or m. + config SND_SOC_WCD938X depends on SND_SOC_WCD938X_SDW tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 172861d17cfd..73eaf37fd4a5 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -356,6 +356,8 @@ snd-soc-wcd9335-y := wcd9335.o snd-soc-wcd934x-y := wcd934x.o snd-soc-wcd937x-y := wcd937x.o snd-soc-wcd937x-sdw-y := wcd937x-sdw.o +snd-soc-wcd9378-y := wcd9378.o +snd-soc-wcd9378-sdw-y := wcd9378-sdw.o snd-soc-wcd938x-y := wcd938x.o snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o @@ -796,6 +798,11 @@ ifdef CONFIG_SND_SOC_WCD937X_SDW # avoid link failure by forcing sdw code built-in when needed obj-$(CONFIG_SND_SOC_WCD937X) += snd-soc-wcd937x-sdw.o endif +obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378.o +ifdef CONFIG_SND_SOC_WCD9378_SDW +# avoid link failure by forcing sdw code built-in when needed +obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378-sdw.o +endif obj-$(CONFIG_SND_SOC_WCD938X) += snd-soc-wcd938x.o ifdef CONFIG_SND_SOC_WCD938X_SDW # avoid link failure by forcing sdw code built-in when needed diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index a6805d5405cd..dbdf51188cf5 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -10,9 +10,12 @@ #include #include +#include #include #include #include +#include +#include #include "aw88261.h" #include "aw88395/aw88395_data_type.h" #include "aw88395/aw88395_device.h" @@ -27,64 +30,10 @@ static const struct regmap_config aw88261_remap_config = { static void aw88261_dev_set_volume(struct aw_device *aw_dev, unsigned int value) { - struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; - unsigned int real_value, volume; - unsigned int reg_value; + unsigned int volume = min(value, (unsigned int)AW88261_MUTE_VOL); - volume = min((value + vol_desc->init_volume), (unsigned int)AW88261_MUTE_VOL); - real_value = DB_TO_REG_VAL(volume); - - regmap_read(aw_dev->regmap, AW88261_SYSCTRL2_REG, ®_value); - - real_value = (real_value | (reg_value & AW88261_VOL_START_MASK)); - - dev_dbg(aw_dev->dev, "value 0x%x , real_value:0x%x", value, real_value); - - regmap_write(aw_dev->regmap, AW88261_SYSCTRL2_REG, real_value); -} - -static void aw88261_dev_fade_in(struct aw_device *aw_dev) -{ - struct aw_volume_desc *desc = &aw_dev->volume_desc; - int fade_in_vol = desc->ctl_volume; - int fade_step = aw_dev->fade_step; - int i; - - if (fade_step == 0 || aw_dev->fade_in_time == 0) { - aw88261_dev_set_volume(aw_dev, fade_in_vol); - return; - } - - for (i = AW88261_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { - aw88261_dev_set_volume(aw_dev, i); - usleep_range(aw_dev->fade_in_time, - aw_dev->fade_in_time + 10); - } - - if (i != fade_in_vol) - aw88261_dev_set_volume(aw_dev, fade_in_vol); -} - -static void aw88261_dev_fade_out(struct aw_device *aw_dev) -{ - struct aw_volume_desc *desc = &aw_dev->volume_desc; - int fade_step = aw_dev->fade_step; - int i; - - if (fade_step == 0 || aw_dev->fade_out_time == 0) { - aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL); - return; - } - - for (i = desc->ctl_volume; i <= AW88261_MUTE_VOL; i += fade_step) { - aw88261_dev_set_volume(aw_dev, i); - usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); - } - - if (i != AW88261_MUTE_VOL) { - aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL); - usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); - } + regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL2_REG, + ~AW88261_VOL_MASK, DB_TO_REG_VAL(volume)); } static void aw88261_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) @@ -120,13 +69,13 @@ static void aw88261_dev_amppd(struct aw_device *aw_dev, bool amppd) static void aw88261_dev_mute(struct aw_device *aw_dev, bool is_mute) { if (is_mute) { - aw88261_dev_fade_out(aw_dev); + aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL); regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, ~AW88261_HMUTE_MASK, AW88261_HMUTE_ENABLE_VALUE); } else { regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, ~AW88261_HMUTE_MASK, AW88261_HMUTE_DISABLE_VALUE); - aw88261_dev_fade_in(aw_dev); + aw88261_dev_set_volume(aw_dev, aw_dev->volume_desc.ctl_volume); } } @@ -151,21 +100,21 @@ static int aw88261_dev_get_iis_status(struct aw_device *aw_dev) if (ret) return ret; if ((reg_val & AW88261_BIT_PLL_CHECK) != AW88261_BIT_PLL_CHECK) { - dev_err(aw_dev->dev, "check pll lock fail,reg_val:0x%04x", reg_val); + dev_dbg(aw_dev->dev, "check pll lock fail,reg_val:0x%04x", reg_val); return -EINVAL; } return ret; } -static int aw88261_dev_check_mode1_pll(struct aw_device *aw_dev) +static int aw88261_dev_check_pll(struct aw_device *aw_dev) { int ret, i; for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) { ret = aw88261_dev_get_iis_status(aw_dev); if (ret) { - dev_err(aw_dev->dev, "mode1 iis signal check error"); + dev_dbg(aw_dev->dev, "mode1 iis signal check error"); usleep_range(AW88261_2000_US, AW88261_2000_US + 10); } else { return ret; @@ -175,71 +124,74 @@ static int aw88261_dev_check_mode1_pll(struct aw_device *aw_dev) return -EPERM; } -static int aw88261_dev_check_mode2_pll(struct aw_device *aw_dev) -{ - unsigned int reg_val; - int ret, i; - - ret = regmap_read(aw_dev->regmap, AW88261_PLLCTRL1_REG, ®_val); - if (ret) - return ret; - - reg_val &= (~AW88261_CCO_MUX_MASK); - if (reg_val == AW88261_CCO_MUX_DIVIDED_VALUE) { - dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); - return -EPERM; - } - - /* change mode2 */ - ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG, - ~AW88261_CCO_MUX_MASK, AW88261_CCO_MUX_DIVIDED_VALUE); - if (ret) - return ret; - - for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) { - ret = aw88261_dev_get_iis_status(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 iis signal check error"); - usleep_range(AW88261_2000_US, AW88261_2000_US + 10); - } else { - break; - } - } - - /* change mode1 */ - ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG, - ~AW88261_CCO_MUX_MASK, AW88261_CCO_MUX_BYPASS_VALUE); - if (ret == 0) { - usleep_range(AW88261_2000_US, AW88261_2000_US + 10); - for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) { - ret = aw88261_dev_check_mode1_pll(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); - usleep_range(AW88261_2000_US, AW88261_2000_US + 10); - } else { - break; - } - } - } - - return ret; -} - -static int aw88261_dev_check_syspll(struct aw_device *aw_dev) +static int aw88261_dev_configure_syspll(struct aw88261 *aw88261) { + struct aw_device *aw_dev = aw88261->aw_pa; int ret; - ret = aw88261_dev_check_mode1_pll(aw_dev); - if (ret) { - dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); - ret = aw88261_dev_check_mode2_pll(aw_dev); - if (ret) { - dev_err(aw_dev->dev, "mode2 check iis failed"); - return ret; - } - } + /* Configure TDM slots (I2S is represented as no slots) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_SLOT_NUM_MASK, aw88261->slot_num_value); + if (ret) + return ret; - return ret; + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_TX_SLOTVLD_MASK, + aw88261->tx_slotvld_mask); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_RXL_SLOTVLD_MASK, + aw88261->rxl_slotvld_mask); + if (ret) + return ret; + + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG, + ~AW88261_I2S_RXR_SLOTVLD_MASK, + aw88261->rxr_slotvld_mask); + if (ret) + return ret; + + /* PLL divider must be used for 8/16/32 kHz modes */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG, + ~AW88261_CCO_MUX_MASK, aw88261->cco_mux_value); + if (ret) + return ret; + + /* The word clock (WCK) defines the beginning of a frame */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SSR_MASK, aw88261->sr_value); + if (ret) + return ret; + + /* The bit clock (BCK) defines the length of a frame */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SBCK_MASK, + (aw88261->tdm_bck_value != AW88261_TDM_BCK_UNSET) + ? aw88261->tdm_bck_value : aw88261->bck_value); + if (ret) + return ret; + + /* The logical frame size is the width of data for 1 slot */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SFS_MASK, aw88261->fs_value); + if (ret) + return ret; + + /* The I2S interface mode (Philips standard, LSB/MSB justified) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG, + ~AW88261_I2SMD_MASK, aw88261->md_value); + if (ret) + return ret; + + /* The polarity of the bit clock (BCK) */ + ret = regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG, + ~AW88261_BCKINV_MASK, aw88261->bck_inv_value); + if (ret) + return ret; + + return aw88261_dev_check_pll(aw_dev); } static int aw88261_dev_check_sysst(struct aw_device *aw_dev) @@ -254,10 +206,10 @@ static int aw88261_dev_check_sysst(struct aw_device *aw_dev) return ret; check_val = reg_val & (~AW88261_BIT_SYSST_CHECK_MASK) - & AW88261_BIT_SYSST_CHECK; - if (check_val != AW88261_BIT_SYSST_CHECK) { - dev_err(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x", - reg_val, AW88261_BIT_SYSST_CHECK); + & AW88261_BIT_PLL_CHECK; + if (check_val != AW88261_BIT_PLL_CHECK) { + dev_dbg(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x", + reg_val, AW88261_BIT_PLL_CHECK); usleep_range(AW88261_2000_US, AW88261_2000_US + 10); } else { return 0; @@ -284,22 +236,22 @@ static void aw88261_reg_force_set(struct aw88261 *aw88261) if (aw88261->frcset_en == AW88261_FRCSET_ENABLE) { /* set FORCE_PWM */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL3_REG, - AW88261_FORCE_PWM_MASK, AW88261_FORCE_PWM_FORCEMINUS_PWM_VALUE); + ~AW88261_FORCE_PWM_MASK, AW88261_FORCE_PWM_FORCEMINUS_PWM_VALUE); /* set BOOST_OS_WIDTH */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL5_REG, - AW88261_BST_OS_WIDTH_MASK, AW88261_BST_OS_WIDTH_50NS_VALUE); + ~AW88261_BST_OS_WIDTH_MASK, AW88261_BST_OS_WIDTH_50NS_VALUE); /* set BURST_LOOPR */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL6_REG, - AW88261_BST_LOOPR_MASK, AW88261_BST_LOOPR_340K_VALUE); + ~AW88261_BST_LOOPR_MASK, AW88261_BST_LOOPR_340K_VALUE); /* set RSQN_DLY */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL7_REG, - AW88261_RSQN_DLY_MASK, AW88261_RSQN_DLY_35NS_VALUE); + ~AW88261_RSQN_DLY_MASK, AW88261_RSQN_DLY_35NS_VALUE); /* set BURST_SSMODE */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL8_REG, - AW88261_BURST_SSMODE_MASK, AW88261_BURST_SSMODE_FAST_VALUE); + ~AW88261_BURST_SSMODE_MASK, AW88261_BURST_SSMODE_FAST_VALUE); /* set BST_BURST */ regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL9_REG, - AW88261_BST_BURST_MASK, AW88261_BST_BURST_30MA_VALUE); + ~AW88261_BST_BURST_MASK, AW88261_BST_BURST_30MA_VALUE); } else { dev_dbg(aw88261->aw_pa->dev, "needn't set reg value"); } @@ -550,7 +502,7 @@ static int aw88261_dev_start(struct aw88261 *aw88261) int ret; if (aw_dev->status == AW88261_DEV_PW_ON) { - dev_info(aw_dev->dev, "already power on"); + dev_dbg(aw_dev->dev, "already power on"); return 0; } @@ -558,9 +510,9 @@ static int aw88261_dev_start(struct aw88261 *aw88261) aw88261_dev_pwd(aw_dev, false); usleep_range(AW88261_2000_US, AW88261_2000_US + 10); - ret = aw88261_dev_check_syspll(aw_dev); + ret = aw88261_dev_configure_syspll(aw88261); if (ret) { - dev_err(aw_dev->dev, "pll check failed cannot start"); + dev_dbg(aw_dev->dev, "pll check failed"); goto pll_check_fail; } @@ -571,7 +523,7 @@ static int aw88261_dev_start(struct aw88261 *aw88261) /* check i2s status */ ret = aw88261_dev_check_sysst(aw_dev); if (ret) { - dev_err(aw_dev->dev, "sysst check failed"); + dev_dbg(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; } @@ -672,31 +624,25 @@ static void aw88261_start_pa(struct aw88261 *aw88261) for (i = 0; i < AW88261_START_RETRIES; i++) { ret = aw88261_reg_update(aw88261, aw88261->phase_sync); if (ret) { - dev_err(aw88261->aw_pa->dev, "fw update failed, cnt:%d\n", i); + dev_dbg(aw88261->aw_pa->dev, + "aw88261_reg_update failed, cnt:%d, ret:%d\n", i, ret); continue; } ret = aw88261_dev_start(aw88261); if (ret) { - dev_err(aw88261->aw_pa->dev, "aw88261 device start failed. retry = %d", i); + dev_dbg(aw88261->aw_pa->dev, + "aw88261_dev_start failed, cnt:%d, ret:%d\n", i, ret); continue; } else { - dev_info(aw88261->aw_pa->dev, "start success\n"); + dev_dbg(aw88261->aw_pa->dev, "start success\n"); break; } } + if (ret != 0) + dev_err(aw88261->aw_pa->dev, "start failure (%d)\n", ret); } -static void aw88261_startup_work(struct work_struct *work) -{ - struct aw88261 *aw88261 = - container_of(work, struct aw88261, start_work.work); - - mutex_lock(&aw88261->lock); - aw88261_start_pa(aw88261); - mutex_unlock(&aw88261->lock); -} - -static void aw88261_start(struct aw88261 *aw88261, bool sync_start) +static void aw88261_start(struct aw88261 *aw88261) { if (aw88261->aw_pa->fw_status != AW88261_DEV_FW_OK) return; @@ -704,14 +650,226 @@ static void aw88261_start(struct aw88261 *aw88261, bool sync_start) if (aw88261->aw_pa->status == AW88261_DEV_PW_ON) return; - if (sync_start == AW88261_SYNC_START) - aw88261_start_pa(aw88261); - else - queue_delayed_work(system_dfl_wq, - &aw88261->start_work, - AW88261_START_WORK_DELAY_MS); + aw88261_start_pa(aw88261); } +static int aw88261_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE; + break; + case SND_SOC_DAIFMT_IB_NF: + aw88261->bck_inv_value = AW88261_BCKINV_INVERTED_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported invert mode 0x%x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE; + break; + case SND_SOC_DAIFMT_MSB: + case SND_SOC_DAIFMT_DSP_B: + aw88261->md_value = AW88261_I2SMD_MSB_JUSTIFIED_VALUE; + break; + case SND_SOC_DAIFMT_LSB: + aw88261->md_value = AW88261_I2SMD_LSB_JUSTIFIED_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int aw88261_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE; + switch (params_rate(params)) { + case 8000: + aw88261->sr_value = AW88261_I2SSR_8KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 11025: + aw88261->sr_value = AW88261_I2SSR_11P025KHZ_VALUE; + break; + case 12000: + aw88261->sr_value = AW88261_I2SSR_12KHZ_VALUE; + break; + case 16000: + aw88261->sr_value = AW88261_I2SSR_16KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 22050: + aw88261->sr_value = AW88261_I2SSR_22P05KHZ_VALUE; + break; + case 24000: + aw88261->sr_value = AW88261_I2SSR_24KHZ_VALUE; + break; + case 32000: + aw88261->sr_value = AW88261_I2SSR_32KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE; + break; + case 44100: + aw88261->sr_value = AW88261_I2SSR_44P1KHZ_VALUE; + break; + case 48000: + aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE; + break; + case 96000: + aw88261->sr_value = AW88261_I2SSR_96KHZ_VALUE; + break; + case 192000: + aw88261->sr_value = AW88261_I2SSR_192KHZ_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + aw88261->fs_value = AW88261_I2SFS_16_BITS_VALUE; + break; + case 20: + aw88261->fs_value = AW88261_I2SFS_20_BITS_VALUE; + break; + case 24: + aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE; + break; + case 32: + aw88261->fs_value = AW88261_I2SFS_32_BITS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported bit width %d\n", + params_width(params)); + return -EINVAL; + } + + switch (params_physical_width(params)) { + case 16: + aw88261->bck_value = AW88261_I2SBCK_32FS_VALUE; + break; + case 24: + aw88261->bck_value = AW88261_I2SBCK_48FS_VALUE; + break; + case 32: + aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported physical bit width %d\n", + params_physical_width(params)); + return -EINVAL; + } + + return 0; +} + +static int aw88261_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + int chan; + + switch (slots) { + case 0: + /* Just reset everything TDM related to I2S values */ + aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE; + aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET; + aw88261->tx_slotvld_mask = 0 << AW88261_I2S_TX_SLOTVLD_START_BIT; + aw88261->rxl_slotvld_mask = 0 << AW88261_I2S_RXL_SLOTVLD_START_BIT; + aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT; + return 0; + case 1: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM1S_VALUE; + break; + case 2: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM2S_VALUE; + break; + case 4: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM4S_VALUE; + break; + case 6: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM6S_VALUE; + break; + case 8: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM8S_VALUE; + break; + case 16: + aw88261->slot_num_value = AW88261_SLOT_NUM_TDM16S_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported slot count %d\n", slots); + return -EINVAL; + } + + switch (slot_width) { + case 16: + aw88261->tdm_bck_value = AW88261_I2SBCK_32FS_VALUE; + break; + case 20: + case 24: + aw88261->tdm_bck_value = AW88261_I2SBCK_48FS_VALUE; + break; + case 32: + aw88261->tdm_bck_value = AW88261_I2SBCK_64FS_VALUE; + break; + default: + dev_err(aw88261->aw_pa->dev, "unsupported slot width %d\n", + slot_width); + return -EINVAL; + } + + if (tx_mask != 0) { + if ((chan = __ffs(tx_mask)) > 16) + return -EINVAL; + + aw88261->tx_slotvld_mask = chan << AW88261_I2S_TX_SLOTVLD_START_BIT; + } + + if (rx_mask != 0) { + if ((chan = __ffs(rx_mask)) > 16) + return -EINVAL; + + aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT; + } + + if ((rx_mask & ~BIT(chan)) != 0) { + if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) + return -EINVAL; + + aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + } + + return 0; +} + +static const struct snd_soc_dai_ops aw88261_dai_ops = { + .set_fmt = aw88261_set_fmt, + .hw_params = aw88261_hw_params, + .set_tdm_slot = aw88261_set_tdm_slot, +}; + static struct snd_soc_dai_driver aw88261_dai[] = { { .name = "aw88261-aif", @@ -730,78 +888,10 @@ static struct snd_soc_dai_driver aw88261_dai[] = { .rates = AW88261_RATES, .formats = AW88261_FORMATS, }, + .ops = &aw88261_dai_ops, }, }; -static int aw88261_get_fade_in_time(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); - struct aw_device *aw_dev = aw88261->aw_pa; - - ucontrol->value.integer.value[0] = aw_dev->fade_in_time; - - return 0; -} - -static int aw88261_set_fade_in_time(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct aw_device *aw_dev = aw88261->aw_pa; - int time; - - time = ucontrol->value.integer.value[0]; - - if (time < mc->min || time > mc->max) - return -EINVAL; - - if (time != aw_dev->fade_in_time) { - aw_dev->fade_in_time = time; - return 1; - } - - return 0; -} - -static int aw88261_get_fade_out_time(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); - struct aw_device *aw_dev = aw88261->aw_pa; - - ucontrol->value.integer.value[0] = aw_dev->fade_out_time; - - return 0; -} - -static int aw88261_set_fade_out_time(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct aw_device *aw_dev = aw88261->aw_pa; - int time; - - time = ucontrol->value.integer.value[0]; - if (time < mc->min || time > mc->max) - return -EINVAL; - - if (time != aw_dev->fade_out_time) { - aw_dev->fade_out_time = time; - return 1; - } - - return 0; -} - static int aw88261_dev_set_profile_index(struct aw_device *aw_dev, int index) { /* check the index whether is valid */ @@ -880,7 +970,7 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, if (aw88261->aw_pa->status) { aw88261_dev_stop(aw88261->aw_pa); - aw88261_start(aw88261, AW88261_SYNC_START); + aw88261_start(aw88261); } mutex_unlock(&aw88261->lock); @@ -895,7 +985,8 @@ static int aw88261_volume_get(struct snd_kcontrol *kcontrol, struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc; - ucontrol->value.integer.value[0] = vol_desc->ctl_volume; + ucontrol->value.integer.value[0] = + (AW88261_MUTE_VOL - vol_desc->ctl_volume) / 2; return 0; } @@ -908,13 +999,13 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol, struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int value; - - value = ucontrol->value.integer.value[0]; + int value = ucontrol->value.integer.value[0]; if (value < mc->min || value > mc->max) return -EINVAL; + value = AW88261_MUTE_VOL - (value * 2); + if (vol_desc->ctl_volume != value) { vol_desc->ctl_volume = value; aw88261_dev_set_volume(aw88261->aw_pa, vol_desc->ctl_volume); @@ -925,48 +1016,18 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol, return 0; } -static int aw88261_get_fade_step(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); - - ucontrol->value.integer.value[0] = aw88261->aw_pa->fade_step; - - return 0; -} - -static int aw88261_set_fade_step(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - int value; - - value = ucontrol->value.integer.value[0]; - if (value < mc->min || value > mc->max) - return -EINVAL; - - if (aw88261->aw_pa->fade_step != value) { - aw88261->aw_pa->fade_step = value; - return 1; - } - - return 0; -} +/* + * The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB + * which is too precise for TLV (!) so we have to multiply the scale by 2. + * + * The range is clamped at -90dB to prevent overflowing the 4-bit part. + */ +static const DECLARE_TLV_DB_SCALE(volume_tlv, -9000, 25, 0); static const struct snd_kcontrol_new aw88261_controls[] = { - SOC_SINGLE_EXT("PCM Playback Volume", AW88261_SYSCTRL2_REG, - 6, AW88261_MUTE_VOL, 0, aw88261_volume_get, - aw88261_volume_set), - SOC_SINGLE_EXT("Fade Step", 0, 0, AW88261_MUTE_VOL, 0, - aw88261_get_fade_step, aw88261_set_fade_step), - SOC_SINGLE_EXT("Volume Ramp Up Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN, - aw88261_get_fade_in_time, aw88261_set_fade_in_time), - SOC_SINGLE_EXT("Volume Ramp Down Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN, - aw88261_get_fade_out_time, aw88261_set_fade_out_time), + SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG, + 6, AW88261_CTL_MAX_VOL, 1, + aw88261_volume_get, aw88261_volume_set, volume_tlv), AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info, aw88261_profile_get, aw88261_profile_set), }; @@ -980,7 +1041,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, mutex_lock(&aw88261->lock); switch (event) { case SND_SOC_DAPM_PRE_PMU: - aw88261_start(aw88261, AW88261_ASYNC_START); + aw88261_start(aw88261); break; case SND_SOC_DAPM_POST_PMD: aw88261_dev_stop(aw88261->aw_pa); @@ -1057,8 +1118,6 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg if (ret) return ret; - aw_dev->fade_in_time = AW88261_500_US; - aw_dev->fade_out_time = AW88261_500_US; aw_dev->prof_cur = AW88261_INIT_PROFILE; aw_dev->prof_index = AW88261_INIT_PROFILE; @@ -1094,6 +1153,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg static int aw88261_request_firmware_file(struct aw88261 *aw88261) { const struct firmware *cont = NULL; + struct aw_container *aw_cfg; const char *fw_name; int ret; @@ -1111,15 +1171,17 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) dev_info(aw88261->aw_pa->dev, "loaded %s - size: %zu\n", fw_name, cont ? cont->size : 0); - aw88261->aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, cont->size + sizeof(int), GFP_KERNEL); - if (!aw88261->aw_cfg) { + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); + if (!aw_cfg) { release_firmware(cont); return -ENOMEM; } - aw88261->aw_cfg->len = (int)cont->size; - memcpy(aw88261->aw_cfg->data, cont->data, cont->size); + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); release_firmware(cont); + aw88261->aw_cfg = aw_cfg; + ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg); if (ret) { dev_err(aw88261->aw_pa->dev, "load [%s] failed !", fw_name); @@ -1142,8 +1204,6 @@ static int aw88261_codec_probe(struct snd_soc_component *component) struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); int ret; - INIT_DELAYED_WORK(&aw88261->start_work, aw88261_startup_work); - ret = aw88261_request_firmware_file(aw88261); if (ret) return dev_err_probe(aw88261->aw_pa->dev, ret, @@ -1167,16 +1227,8 @@ static int aw88261_codec_probe(struct snd_soc_component *component) return ret; } -static void aw88261_codec_remove(struct snd_soc_component *aw_codec) -{ - struct aw88261 *aw88261 = snd_soc_component_get_drvdata(aw_codec); - - cancel_delayed_work_sync(&aw88261->start_work); -} - static const struct snd_soc_component_driver soc_codec_dev_aw88261 = { .probe = aw88261_codec_probe, - .remove = aw88261_codec_remove, }; static void aw88261_parse_channel_dt(struct aw88261 *aw88261) @@ -1208,7 +1260,7 @@ static int aw88261_init(struct aw88261 *aw88261, struct i2c_client *i2c, struct return ret; } if (chip_id != AW88261_CHIP_ID) { - dev_err(&i2c->dev, "unsupported device"); + dev_err(&i2c->dev, "unsupported device id = %x", chip_id); return -ENXIO; } @@ -1229,8 +1281,7 @@ static int aw88261_init(struct aw88261 *aw88261, struct i2c_client *i2c, struct aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; aw_dev->channel = 0; aw_dev->fw_status = AW88261_DEV_FW_FAILED; - aw_dev->fade_step = AW88261_VOLUME_STEP_DB; - aw_dev->volume_desc.ctl_volume = AW88261_VOL_DEFAULT_VALUE; + aw_dev->volume_desc.ctl_volume = AW88261_CTL_DEFAULT_VOL; aw_dev->volume_desc.mute_volume = AW88261_MUTE_VOL; aw88261_parse_channel_dt(aw88261); @@ -1249,6 +1300,17 @@ static int aw88261_i2c_probe(struct i2c_client *i2c) if (!aw88261) return -ENOMEM; + /* set defaults */ + aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE; + aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE; + aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE; + aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE; + aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE; + aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE; + aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET; + aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE; + aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT; + mutex_init(&aw88261->lock); i2c_set_clientdata(i2c, aw88261); @@ -1274,7 +1336,7 @@ static int aw88261_i2c_probe(struct i2c_client *i2c) } static const struct i2c_device_id aw88261_i2c_id[] = { - { "aw88261" }, + { .name = "aw88261" }, { } }; MODULE_DEVICE_TABLE(i2c, aw88261_i2c_id); diff --git a/sound/soc/codecs/aw88261.h b/sound/soc/codecs/aw88261.h index 1fee589608d6..f16f9f8c40a2 100644 --- a/sound/soc/codecs/aw88261.h +++ b/sound/soc/codecs/aw88261.h @@ -116,6 +116,19 @@ #define AW88261_VCALK_SHIFT (0) #define AW88261_VCALKL_SHIFT (0) +#define AW88261_BCKINV_START_BIT (4) +#define AW88261_BCKINV_BITS_LEN (1) +#define AW88261_BCKINV_MASK \ + (~(((1<> AW88261_VOL_6DB_START) * \ @@ -403,11 +551,6 @@ .put = profile_set, \ } -enum { - AW88261_SYNC_START = 0, - AW88261_ASYNC_START, -}; - enum aw88261_id { AW88261_CHIP_ID = 0x2113, }; @@ -442,7 +585,6 @@ struct aw88261 { struct aw_device *aw_pa; struct mutex lock; struct gpio_desc *reset_gpio; - struct delayed_work start_work; struct regmap *regmap; struct aw_container *aw_cfg; @@ -451,6 +593,19 @@ struct aw88261 { unsigned int mute_st; unsigned int amppd_st; + unsigned int sr_value; + unsigned int cco_mux_value; + unsigned int fs_value; + unsigned int bck_value; + unsigned int bck_inv_value; + unsigned int tdm_bck_value; + unsigned int md_value; + + unsigned int slot_num_value; + unsigned int tx_slotvld_mask; + unsigned int rxl_slotvld_mask; + unsigned int rxr_slotvld_mask; + bool phase_sync; }; diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c new file mode 100644 index 000000000000..d9a99a6db500 --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdw.c @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Jorijn van der Graaf + * + * SoundWire slave driver for the Qualcomm WCD9378 audio codec. + * + * The codec presents two SoundWire slaves (RX and TX, mfg 0x0217 part + * 0x0110); the SDCA control space is a 32-bit paged register map accessed + * through the TX slave. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wcd-common.h" +#include "wcd9378.h" + +#define WCD9378_SDW_CH(id, pn, cmask, mmask) \ + [id] = { \ + .port_num = pn, \ + .ch_mask = cmask, \ + .master_ch_mask = mmask, \ + } + +/* + * Each ADC sits alone on its own TX device port (channel 1); by default + * they land on channels 1/2/3 of the same master port (SWRM_TX1 on the + * FP6). DMIC/MBHC masks per the downstream qcom,tx_swr_ch_map. + */ +static struct wcd_sdw_ch_info wcd9378_sdw_tx_ch_info[] = { + WCD9378_SDW_CH(WCD9378_ADC1, WCD9378_ADC_1_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_ADC2, WCD9378_ADC_2_PORT, BIT(0), BIT(1)), + WCD9378_SDW_CH(WCD9378_ADC3, WCD9378_ADC_3_PORT, BIT(0), BIT(2)), + WCD9378_SDW_CH(WCD9378_DMIC0, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(0)), + WCD9378_SDW_CH(WCD9378_DMIC1, WCD9378_DMIC_0_1_MBHC_PORT, BIT(3), BIT(1)), + WCD9378_SDW_CH(WCD9378_MBHC, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(2)), + WCD9378_SDW_CH(WCD9378_DMIC2, WCD9378_DMIC_2_5_PORT, BIT(0), BIT(2)), + WCD9378_SDW_CH(WCD9378_DMIC3, WCD9378_DMIC_2_5_PORT, BIT(1), BIT(3)), + WCD9378_SDW_CH(WCD9378_DMIC4, WCD9378_DMIC_2_5_PORT, BIT(2), BIT(0)), + WCD9378_SDW_CH(WCD9378_DMIC5, WCD9378_DMIC_2_5_PORT, BIT(3), BIT(1)), +}; + +static struct wcd_sdw_ch_info wcd9378_sdw_rx_ch_info[] = { + WCD9378_SDW_CH(WCD9378_HPH_L, WCD9378_HPH_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_HPH_R, WCD9378_HPH_PORT, BIT(1), BIT(1)), + WCD9378_SDW_CH(WCD9378_CLSH, WCD9378_CLSH_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_COMP_L, WCD9378_COMP_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_COMP_R, WCD9378_COMP_PORT, BIT(1), BIT(1)), + WCD9378_SDW_CH(WCD9378_LO, WCD9378_LO_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_DSD_L, WCD9378_DSD_PORT, BIT(0), BIT(0)), + WCD9378_SDW_CH(WCD9378_DSD_R, WCD9378_DSD_PORT, BIT(1), BIT(1)), +}; + +static struct sdw_dpn_prop wcd9378_dpn_prop[WCD9378_MAX_SWR_PORTS] = { + { + .num = 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 8, + .simple_ch_prep_sm = true, + }, { + .num = 2, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 4, + .simple_ch_prep_sm = true, + }, { + .num = 3, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 4, + .simple_ch_prep_sm = true, + }, { + .num = 4, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 4, + .simple_ch_prep_sm = true, + }, { + .num = 5, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 4, + .simple_ch_prep_sm = true, + } +}; + +int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS]; + unsigned long ch_mask; + int i, j; + + wcd->sconfig.ch_count = 1; + wcd->active_ports = 0; + for (i = 0; i < WCD9378_MAX_SWR_PORTS; i++) { + ch_mask = wcd->port_config[i].ch_mask; + if (!ch_mask) + continue; + + for_each_set_bit(j, &ch_mask, 4) + wcd->sconfig.ch_count++; + + port_config[wcd->active_ports] = wcd->port_config[i]; + wcd->active_ports++; + } + + wcd->sconfig.bps = 1; + wcd->sconfig.frame_rate = params_rate(params); + wcd->sconfig.direction = wcd->is_tx ? SDW_DATA_DIR_TX : SDW_DATA_DIR_RX; + wcd->sconfig.type = SDW_STREAM_PCM; + + return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig, + &port_config[0], wcd->active_ports, + wcd->sruntime); +} +EXPORT_SYMBOL_GPL(wcd9378_sdw_hw_params); + +/* + * Tell the codec the bus clock: base 19.2 MHz plus a scale (div) per bank. + * The downstream driver writes these raw SCP registers on every capture + * start; here the bus_config callback covers bank switches. + */ +static int wcd9378_bus_config(struct sdw_slave *slave, + struct sdw_bus_params *params) +{ + u8 scale; + + switch (params->curr_dr_freq) { + case 4800000: + scale = WCD9378_SWRS_CLK_SCALE_DIV4; + break; + case 9600000: + default: + scale = WCD9378_SWRS_CLK_SCALE_DIV2; + break; + } + + sdw_write(slave, WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(params->next_bank), + 0x01); + sdw_write(slave, WCD9378_SWRS_SCP_BASE_CLK, + WCD9378_SWRS_BASE_CLK_19P2MHZ); + sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0, scale); + sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1, scale); + + return 0; +} + +static const struct sdw_slave_ops wcd9378_slave_ops = { + .update_status = wcd_update_status, + .bus_config = wcd9378_bus_config, +}; + +static const struct reg_default wcd9378_defaults[] = { + { WCD9378_ANA_BIAS, 0x00 }, + { WCD9378_ANA_TX_CH1, 0x20 }, + { WCD9378_ANA_TX_CH2, 0x00 }, + { WCD9378_ANA_TX_CH3, 0x20 }, + { WCD9378_ANA_TX_CH3_HPF, 0x00 }, + { WCD9378_ANA_MICB2_RAMP, 0x00 }, + { WCD9378_BIAS_VBG_FINE_ADJ, 0x55 }, + { WCD9378_MBHC_CTL_SPARE_1, 0x02 }, + { WCD9378_MICB1_TEST_CTL_2, 0x00 }, + { WCD9378_MICB2_TEST_CTL_2, 0x00 }, + { WCD9378_MICB3_TEST_CTL_2, 0x80 }, + { WCD9378_TX_COM_TXFE_DIV_CTL, 0x22 }, + { WCD9378_SLEEP_CTL, 0x16 }, + { WCD9378_TX_NEW_CH12_MUX, 0x11 }, + { WCD9378_TX_NEW_CH34_MUX, 0x23 }, + { WCD9378_TOP_CLK_CFG, 0x00 }, + { WCD9378_CDC_ANA_TX_CLK_CTL, 0x0e }, + { WCD9378_CDC_AMIC_CTL, 0x07 }, + { WCD9378_PDM_WD_CTL0, 0x0f }, + { WCD9378_PDM_WD_CTL1, 0x0f }, + { WCD9378_PLATFORM_CTL, 0x01 }, + { WCD9378_SYS_USAGE_CTRL, 0x00 }, + { WCD9378_HPH_UP_T0, 0x02 }, + { WCD9378_HPH_UP_T9, 0x02 }, + { WCD9378_HPH_DN_T0, 0x05 }, + { WCD9378_MICB_REMAP_TABLE_VAL_3, 0x00 }, + { WCD9378_MICB_REMAP_TABLE_VAL_4, 0x00 }, + { WCD9378_MICB_REMAP_TABLE_VAL_5, 0x00 }, + { WCD9378_SM0_MB_SEL, 0x00 }, + { WCD9378_SM1_MB_SEL, 0x00 }, + { WCD9378_SM2_MB_SEL, 0x00 }, + { WCD9378_MB_PULLUP_EN, 0x00 }, + { WCD9378_SMP_AMP_FUNC_ACT, 0x00 }, + { WCD9378_CMT_GRP_MASK, 0x00 }, + { WCD9378_SMP_JACK_IT31_MICB, 0x00 }, + { WCD9378_SMP_JACK_IT31_USAGE, 0x03 }, + { WCD9378_SMP_JACK_PDE34_REQ_PS, 0x03 }, + { WCD9378_SMP_JACK_FUNC_ACT, 0x00 }, + { WCD9378_SMP_MIC_IT11_MICB(0), 0x00 }, + { WCD9378_SMP_MIC_IT11_USAGE(0), 0x03 }, + { WCD9378_SMP_MIC_PDE11_REQ_PS(0), 0x03 }, + { WCD9378_SMP_MIC_FUNC_ACT(0), 0x00 }, + { WCD9378_SMP_MIC_IT11_MICB(1), 0x00 }, + { WCD9378_SMP_MIC_IT11_USAGE(1), 0x03 }, + { WCD9378_SMP_MIC_PDE11_REQ_PS(1), 0x03 }, + { WCD9378_SMP_MIC_FUNC_ACT(1), 0x00 }, + { WCD9378_SMP_MIC_IT11_MICB(2), 0x00 }, + { WCD9378_SMP_MIC_IT11_USAGE(2), 0x03 }, + { WCD9378_SMP_MIC_PDE11_REQ_PS(2), 0x03 }, + { WCD9378_SMP_MIC_FUNC_ACT(2), 0x00 }, +}; + +static bool wcd9378_rdwr_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WCD9378_ANA_BIAS: + case WCD9378_ANA_TX_CH1: + case WCD9378_ANA_TX_CH2: + case WCD9378_ANA_TX_CH3: + case WCD9378_ANA_TX_CH3_HPF: + case WCD9378_ANA_MICB2_RAMP: + case WCD9378_BIAS_VBG_FINE_ADJ: + case WCD9378_MBHC_CTL_SPARE_1: + case WCD9378_MICB1_TEST_CTL_2: + case WCD9378_MICB2_TEST_CTL_2: + case WCD9378_MICB3_TEST_CTL_2: + case WCD9378_TX_COM_TXFE_DIV_CTL: + case WCD9378_SLEEP_CTL: + case WCD9378_TX_NEW_CH12_MUX: + case WCD9378_TX_NEW_CH34_MUX: + case WCD9378_HPH_RDAC_GAIN_CTL: + case WCD9378_HPH_RDAC_HD2_CTL_L: + case WCD9378_HPH_RDAC_HD2_CTL_R: + case WCD9378_TOP_CLK_CFG: + case WCD9378_CDC_ANA_TX_CLK_CTL: + case WCD9378_CDC_AMIC_CTL: + case WCD9378_PDM_WD_CTL0: + case WCD9378_PDM_WD_CTL1: + case WCD9378_PLATFORM_CTL: + case WCD9378_SYS_USAGE_CTRL: + case WCD9378_HPH_UP_T0: + case WCD9378_HPH_UP_T9: + case WCD9378_HPH_DN_T0: + case WCD9378_MICB_REMAP_TABLE_VAL_3: + case WCD9378_MICB_REMAP_TABLE_VAL_4: + case WCD9378_MICB_REMAP_TABLE_VAL_5: + case WCD9378_SM0_MB_SEL: + case WCD9378_SM1_MB_SEL: + case WCD9378_SM2_MB_SEL: + case WCD9378_MB_PULLUP_EN: + case WCD9378_SMP_AMP_FUNC_STAT: + case WCD9378_SMP_AMP_FUNC_ACT: + case WCD9378_CMT_GRP_MASK: + case WCD9378_SMP_JACK_IT31_MICB: + case WCD9378_SMP_JACK_IT31_USAGE: + case WCD9378_SMP_JACK_PDE34_REQ_PS: + case WCD9378_SMP_JACK_FUNC_STAT: + case WCD9378_SMP_JACK_FUNC_ACT: + case WCD9378_SMP_MIC_IT11_MICB(0): + case WCD9378_SMP_MIC_IT11_USAGE(0): + case WCD9378_SMP_MIC_PDE11_REQ_PS(0): + case WCD9378_SMP_MIC_FUNC_STAT(0): + case WCD9378_SMP_MIC_FUNC_ACT(0): + case WCD9378_SMP_MIC_IT11_MICB(1): + case WCD9378_SMP_MIC_IT11_USAGE(1): + case WCD9378_SMP_MIC_PDE11_REQ_PS(1): + case WCD9378_SMP_MIC_FUNC_STAT(1): + case WCD9378_SMP_MIC_FUNC_ACT(1): + case WCD9378_SMP_MIC_IT11_MICB(2): + case WCD9378_SMP_MIC_IT11_USAGE(2): + case WCD9378_SMP_MIC_PDE11_REQ_PS(2): + case WCD9378_SMP_MIC_FUNC_STAT(2): + case WCD9378_SMP_MIC_FUNC_ACT(2): + return true; + } + + return false; +} + +static bool wcd9378_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WCD9378_FUNC_EXT_ID_0: + case WCD9378_FUNC_EXT_ID_1: + case WCD9378_FUNC_EXT_VER: + case WCD9378_FUNC_STAT: + case WCD9378_DEV_MANU_ID_0: + case WCD9378_DEV_MANU_ID_1: + case WCD9378_DEV_PART_ID_0: + case WCD9378_DEV_PART_ID_1: + case WCD9378_DEV_VER: + case WCD9378_EFUSE_REG_16: + case WCD9378_EFUSE_REG_29: + case WCD9378_SEQ_TX0_STAT: + case WCD9378_SEQ_TX1_STAT: + case WCD9378_SEQ_TX2_STAT: + case WCD9378_SMP_AMP_FUNC_STAT: + case WCD9378_SMP_JACK_FUNC_STAT: + case WCD9378_SMP_JACK_PDE34_ACT_PS: + case WCD9378_SMP_MIC_FUNC_STAT(0): + case WCD9378_SMP_MIC_FUNC_STAT(1): + case WCD9378_SMP_MIC_FUNC_STAT(2): + case WCD9378_SMP_MIC_OT10_USAGE(0): + case WCD9378_SMP_MIC_PDE11_ACT_PS(0): + case WCD9378_SMP_MIC_OT10_USAGE(1): + case WCD9378_SMP_MIC_PDE11_ACT_PS(1): + case WCD9378_SMP_MIC_OT10_USAGE(2): + case WCD9378_SMP_MIC_PDE11_ACT_PS(2): + return true; + } + + return false; +} + +static bool wcd9378_readable_register(struct device *dev, unsigned int reg) +{ + if (wcd9378_volatile_register(dev, reg)) + return true; + + return wcd9378_rdwr_register(dev, reg); +} + +static const struct regmap_config wcd9378_regmap_config = { + .name = "wcd9378_csr", + .reg_bits = 32, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .reg_defaults = wcd9378_defaults, + .num_reg_defaults = ARRAY_SIZE(wcd9378_defaults), + .max_register = WCD9378_MAX_REGISTER, + .readable_reg = wcd9378_readable_register, + .writeable_reg = wcd9378_rdwr_register, + .volatile_reg = wcd9378_volatile_register, +}; + +static int wcd9378_sdw_probe(struct sdw_slave *pdev, + const struct sdw_device_id *id) +{ + struct device *dev = &pdev->dev; + struct wcd9378_sdw_priv *wcd; + u8 master_ch_mask[WCD9378_MAX_SWR_CH_IDS]; + int master_ch_mask_size = 0; + int ret, i; + + wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL); + if (!wcd) + return -ENOMEM; + + /* Port map index starts at 0, however the data ports start at index 1 */ + if (of_property_present(dev->of_node, "qcom,tx-port-mapping")) { + wcd->is_tx = true; + ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping", + &pdev->m_port_map[1], + WCD9378_MAX_TX_SWR_PORTS); + } else { + ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping", + &pdev->m_port_map[1], + WCD9378_MAX_SWR_PORTS); + } + if (ret < 0) + dev_info(dev, "Error getting static port mapping for %s (%d)\n", + wcd->is_tx ? "TX" : "RX", ret); + + wcd->sdev = pdev; + dev_set_drvdata(dev, wcd); + + pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | + SDW_SCP_INT1_PARITY; + pdev->prop.lane_control_support = true; + pdev->prop.simple_clk_stop_capable = true; + /* The SDCA control space sits above the 16-bit address range */ + pdev->prop.paging_support = true; + + memset(master_ch_mask, 0, WCD9378_MAX_SWR_CH_IDS); + + if (wcd->is_tx) { + master_ch_mask_size = of_property_count_u8_elems(dev->of_node, + "qcom,tx-channel-mapping"); + master_ch_mask_size = min_t(int, master_ch_mask_size, + ARRAY_SIZE(wcd9378_sdw_tx_ch_info)); + + if (master_ch_mask_size > 0) + ret = of_property_read_u8_array(dev->of_node, + "qcom,tx-channel-mapping", + master_ch_mask, + master_ch_mask_size); + } else { + master_ch_mask_size = of_property_count_u8_elems(dev->of_node, + "qcom,rx-channel-mapping"); + master_ch_mask_size = min_t(int, master_ch_mask_size, + ARRAY_SIZE(wcd9378_sdw_rx_ch_info)); + + if (master_ch_mask_size > 0) + ret = of_property_read_u8_array(dev->of_node, + "qcom,rx-channel-mapping", + master_ch_mask, + master_ch_mask_size); + } + + if (wcd->is_tx) { + pdev->prop.source_ports = GENMASK(WCD9378_MAX_TX_SWR_PORTS, 1); + pdev->prop.src_dpn_prop = wcd9378_dpn_prop; + wcd->ch_info = &wcd9378_sdw_tx_ch_info[0]; + + for (i = 0; i < master_ch_mask_size; i++) + wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]); + + pdev->prop.wake_capable = true; + + wcd->regmap = devm_regmap_init_sdw(pdev, &wcd9378_regmap_config); + if (IS_ERR(wcd->regmap)) + return dev_err_probe(dev, PTR_ERR(wcd->regmap), + "Regmap init failed\n"); + + /* Start in cache-only until device is enumerated */ + regcache_cache_only(wcd->regmap, true); + } else { + pdev->prop.sink_ports = GENMASK(WCD9378_MAX_SWR_PORTS, 1); + pdev->prop.sink_dpn_prop = wcd9378_dpn_prop; + wcd->ch_info = &wcd9378_sdw_rx_ch_info[0]; + + for (i = 0; i < master_ch_mask_size; i++) + wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]); + } + + ret = component_add(dev, &wcd_sdw_component_ops); + if (ret) + return ret; + + /* Set suspended until aggregate device is bind */ + pm_runtime_set_suspended(dev); + + return 0; +} + +static void wcd9378_sdw_remove(struct sdw_slave *pdev) +{ + struct device *dev = &pdev->dev; + + component_del(dev, &wcd_sdw_component_ops); +} + +static const struct sdw_device_id wcd9378_sdw_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x0110, 0), + { }, +}; +MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id); + +static int wcd9378_sdw_runtime_suspend(struct device *dev) +{ + struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev); + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, true); + regcache_mark_dirty(wcd->regmap); + } + + return 0; +} + +static int wcd9378_sdw_runtime_resume(struct device *dev) +{ + struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev); + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); + regcache_sync(wcd->regmap); + } + + return 0; +} + +static const struct dev_pm_ops wcd9378_sdw_pm_ops = { + RUNTIME_PM_OPS(wcd9378_sdw_runtime_suspend, wcd9378_sdw_runtime_resume, NULL) +}; + +static struct sdw_driver wcd9378_sdw_driver = { + .probe = wcd9378_sdw_probe, + .remove = wcd9378_sdw_remove, + .ops = &wcd9378_slave_ops, + .id_table = wcd9378_sdw_id, + .driver = { + .name = "wcd9378-sdw", + .pm = pm_ptr(&wcd9378_sdw_pm_ops), + } +}; +module_sdw_driver(wcd9378_sdw_driver); + +MODULE_DESCRIPTION("WCD9378 SDW codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wcd9378.c b/sound/soc/codecs/wcd9378.c new file mode 100644 index 000000000000..38acb6b20388 --- /dev/null +++ b/sound/soc/codecs/wcd9378.c @@ -0,0 +1,1390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Jorijn van der Graaf + * + * Qualcomm WCD9378 audio codec driver. + * + * The WCD9378 pairs a WCD937x-compatible analog core with SDCA-style + * function blocks (SmartMIC0/1/2, SmartJACK, SmartAMP) whose built-in + * sequencers perform the analog power-up/down autonomously: capture is + * started by programming the ADC usage mode (ITxx_USAGE), requesting + * power state 0 on the function's PDE, and letting the sequencer ramp + * the micbias selected through SMx_MB_SEL. + * + * TX/capture paths only for now; RX (earpiece/headphone), MBHC and the + * SmartAMP function are not implemented. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wcd-common.h" +#include "wcd9378.h" + +enum { + AIF1_PB = 0, + AIF1_CAP, + NUM_CODEC_DAIS, +}; + +enum { + MIC_BIAS_1 = 1, + MIC_BIAS_2, + MIC_BIAS_3, +}; + +enum { + MICB_PULLUP_ENABLE, + MICB_PULLUP_DISABLE, + MICB_ENABLE, + MICB_DISABLE, +}; + +/* sys-usage capability bits (SYS_USAGE_CTRL profile contents) */ +enum { + WCD9378_SYS_USAGE_CLASS_AB = 0, + WCD9378_SYS_USAGE_TX1_FOR_JACK, + WCD9378_SYS_USAGE_TX2_AMIC4, + WCD9378_SYS_USAGE_TX2_AMIC1, + WCD9378_SYS_USAGE_TX1_AMIC3, + WCD9378_SYS_USAGE_TX1_AMIC2, + WCD9378_SYS_USAGE_TX0_AMIC2, + WCD9378_SYS_USAGE_TX0_AMIC1, + WCD9378_SYS_USAGE_RX2_EAR, + WCD9378_SYS_USAGE_RX2_AUX, + WCD9378_SYS_USAGE_RX1_AUX, + WCD9378_SYS_USAGE_RX0_EAR, + WCD9378_SYS_USAGE_RX0_RX1_HPH, +}; + +/* Capability sets of the 13 canned SYS_USAGE_CTRL profiles */ +static const unsigned int wcd9378_sys_usage_profiles[] = { + 0x0c95, 0x12a7, 0x0c99, 0x1aab, 0x0894, 0x11a6, 0x0898, + 0x11ab, 0x126a, 0x116b, 0x1ca7, 0x1195, 0x1296, +}; + +struct wcd9378_priv { + struct sdw_slave *tx_sdw_dev; + struct wcd9378_sdw_priv *sdw_priv[NUM_CODEC_DAIS]; + struct device *txdev; + struct device *rxdev; + struct device_node *rxnode; + struct device_node *txnode; + struct regmap *regmap; + struct wcd_common common; + /* micbias refcount lock */ + struct mutex micb_lock; + u8 micb_usage_val[WCD9378_MAX_MICBIAS]; + int micb_ref[WCD9378_MAX_MICBIAS]; + int pullup_ref[WCD9378_MAX_MICBIAS]; + unsigned long sys_usage_mask; + /* per-ADC profile bit and target function, latched at power-up */ + int tx_sys_bit[3]; + bool tx_is_jack[3]; + u32 tx_mode[3]; + struct gpio_desc *reset_gpio; +}; + +static const char * const wcd9378_supplies[] = { + "vdd-rxtx", "vdd-io", "vdd-buck", "vdd-mic-bias", +}; + +/* SDCA function block registers driving one ADC */ +struct wcd9378_smp_fn { + u32 usage_reg; + u32 micb_reg; + u32 req_reg; + u32 act_reg; + u32 hpf_reg; + u8 hpf_mask; +}; + +/* ADC1/2/3 through SmartMIC0/1/2 */ +static const struct wcd9378_smp_fn wcd9378_smp_mic[] = { + { + .usage_reg = WCD9378_SMP_MIC_IT11_USAGE(0), + .micb_reg = WCD9378_SMP_MIC_IT11_MICB(0), + .req_reg = WCD9378_SMP_MIC_PDE11_REQ_PS(0), + .act_reg = WCD9378_SMP_MIC_PDE11_ACT_PS(0), + .hpf_reg = WCD9378_ANA_TX_CH2, + .hpf_mask = WCD9378_ANA_TX_CH2_HPF1_INIT, + }, { + .usage_reg = WCD9378_SMP_MIC_IT11_USAGE(1), + .micb_reg = WCD9378_SMP_MIC_IT11_MICB(1), + .req_reg = WCD9378_SMP_MIC_PDE11_REQ_PS(1), + .act_reg = WCD9378_SMP_MIC_PDE11_ACT_PS(1), + .hpf_reg = WCD9378_ANA_TX_CH2, + .hpf_mask = WCD9378_ANA_TX_CH2_HPF2_INIT, + }, { + .usage_reg = WCD9378_SMP_MIC_IT11_USAGE(2), + .micb_reg = WCD9378_SMP_MIC_IT11_MICB(2), + .req_reg = WCD9378_SMP_MIC_PDE11_REQ_PS(2), + .act_reg = WCD9378_SMP_MIC_PDE11_ACT_PS(2), + .hpf_reg = WCD9378_ANA_TX_CH3_HPF, + .hpf_mask = WCD9378_ANA_TX_CH3_HPF3_INIT, + }, +}; + +/* ADC2 fed from AMIC2 runs through the SmartJACK function instead */ +static const struct wcd9378_smp_fn wcd9378_smp_jack_adc2 = { + .usage_reg = WCD9378_SMP_JACK_IT31_USAGE, + .micb_reg = WCD9378_SMP_JACK_IT31_MICB, + .req_reg = WCD9378_SMP_JACK_PDE34_REQ_PS, + .act_reg = WCD9378_SMP_JACK_PDE34_ACT_PS, +}; + +/* + * Measured acoustically on the FP6: the TX gain field steps 1.5 dB per + * code (+6 dB per 4 codes, +30 dB over the full 0..20 range). The + * wcd937x-family drivers claim 0.25 dB steps for the same field. + */ +static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 150, 0); + +static void wcd9378_reset(struct wcd9378_priv *wcd9378) +{ + gpiod_set_value(wcd9378->reset_gpio, 1); + usleep_range(20, 30); + gpiod_set_value(wcd9378->reset_gpio, 0); + usleep_range(20, 30); +} + +/* + * Activate the SDCA function classes. Without FUNC_ACT the sequencer + * ignores all PDE power-state requests. + */ +static void wcd9378_class_load(struct snd_soc_component *component) +{ + int i; + + /* + * Plain writes, not update_bits, so the 0->1 activation edge + * always reaches the hardware regardless of regcache state. + * The engine boots from this edge only on a freshly reset + * codec; once it dies (bus clock-stop) no register write + * revives it, see the TX bus PM hold in wcd9378_bind(). + */ + snd_soc_component_write(component, WCD9378_SMP_AMP_FUNC_ACT, 0x00); + snd_soc_component_write(component, WCD9378_SMP_AMP_FUNC_ACT, 0x01); + usleep_range(20000, 20010); + snd_soc_component_write(component, WCD9378_SMP_AMP_FUNC_STAT, 0xff); + + snd_soc_component_write(component, WCD9378_SMP_JACK_FUNC_ACT, 0x00); + snd_soc_component_write(component, WCD9378_SMP_JACK_FUNC_ACT, 0x01); + usleep_range(30000, 30010); + snd_soc_component_update_bits(component, WCD9378_CMT_GRP_MASK, + 0xff, 0x02); + snd_soc_component_write(component, WCD9378_SMP_JACK_FUNC_STAT, 0xff); + + for (i = 0; i < 3; i++) { + snd_soc_component_write(component, + WCD9378_SMP_MIC_FUNC_ACT(i), 0x00); + snd_soc_component_write(component, + WCD9378_SMP_MIC_FUNC_ACT(i), 0x01); + usleep_range(5000, 5010); + snd_soc_component_write(component, + WCD9378_SMP_MIC_FUNC_STAT(i), 0xff); + } +} + +static void wcd9378_io_init(struct snd_soc_component *component) +{ + u32 efuse; + + /* Bandgap and analog master bias, with precharge pulse */ + efuse = snd_soc_component_read(component, WCD9378_EFUSE_REG_16); + snd_soc_component_update_bits(component, WCD9378_MBHC_CTL_SPARE_1, + 0x03, efuse == 0 ? 0x03 : 0x01); + snd_soc_component_update_bits(component, WCD9378_SLEEP_CTL, + WCD9378_SLEEP_CTL_BG_CTL_MASK, 0x0e); + snd_soc_component_update_bits(component, WCD9378_SLEEP_CTL, + WCD9378_SLEEP_CTL_BG_EN, + WCD9378_SLEEP_CTL_BG_EN); + usleep_range(1000, 1010); + snd_soc_component_update_bits(component, WCD9378_SLEEP_CTL, + WCD9378_SLEEP_CTL_LDOL_BG_SEL, + WCD9378_SLEEP_CTL_LDOL_BG_SEL); + usleep_range(1000, 1010); + snd_soc_component_update_bits(component, WCD9378_BIAS_VBG_FINE_ADJ, + 0xf0, 0xb0); + snd_soc_component_update_bits(component, WCD9378_ANA_BIAS, + WCD9378_ANA_BIAS_ANALOG_BIAS_EN, + WCD9378_ANA_BIAS_ANALOG_BIAS_EN); + snd_soc_component_update_bits(component, WCD9378_ANA_BIAS, + WCD9378_ANA_BIAS_PRECHRG_EN, + WCD9378_ANA_BIAS_PRECHRG_EN); + usleep_range(10000, 10010); + snd_soc_component_update_bits(component, WCD9378_ANA_BIAS, + WCD9378_ANA_BIAS_PRECHRG_EN, 0x00); + + /* TX supporting clocks/dividers */ + snd_soc_component_update_bits(component, WCD9378_CDC_ANA_TX_CLK_CTL, + WCD9378_CDC_ANA_TXSCBIAS_CLK_EN, + WCD9378_CDC_ANA_TXSCBIAS_CLK_EN); + snd_soc_component_update_bits(component, WCD9378_TX_COM_TXFE_DIV_CTL, + WCD9378_TX_COM_TXFE_DIV_SEQ_BYPASS, + WCD9378_TX_COM_TXFE_DIV_SEQ_BYPASS); + snd_soc_component_update_bits(component, WCD9378_PDM_WD_CTL0, + 0x18, 0x10); + snd_soc_component_update_bits(component, WCD9378_PDM_WD_CTL1, + 0x18, 0x10); + + /* Micbias LDO driver bias */ + snd_soc_component_update_bits(component, WCD9378_MICB1_TEST_CTL_2, + 0x07, 0x01); + snd_soc_component_update_bits(component, WCD9378_MICB2_TEST_CTL_2, + 0x07, 0x01); + snd_soc_component_update_bits(component, WCD9378_MICB3_TEST_CTL_2, + 0x07, 0x01); + + /* RX defaults (harmless while RX is unimplemented) */ + snd_soc_component_update_bits(component, WCD9378_HPH_RDAC_HD2_CTL_L, + 0x0f, 0x04); + snd_soc_component_update_bits(component, WCD9378_HPH_RDAC_HD2_CTL_R, + 0x0f, 0x04); + snd_soc_component_update_bits(component, WCD9378_HPH_RDAC_GAIN_CTL, + 0xf0, 0x50); + snd_soc_component_update_bits(component, WCD9378_HPH_UP_T0, + 0x07, 0x05); + snd_soc_component_update_bits(component, WCD9378_HPH_UP_T9, + 0x07, 0x05); + snd_soc_component_update_bits(component, WCD9378_HPH_DN_T0, + 0x07, 0x06); + + /* SmartMIC function N powers the micbias SMx_MB_SEL points at */ + snd_soc_component_update_bits(component, WCD9378_SM0_MB_SEL, + WCD9378_SM_MB_SEL_MASK, 0x01); + snd_soc_component_update_bits(component, WCD9378_SM1_MB_SEL, + WCD9378_SM_MB_SEL_MASK, 0x02); + snd_soc_component_update_bits(component, WCD9378_SM2_MB_SEL, + WCD9378_SM_MB_SEL_MASK, 0x03); + snd_soc_component_update_bits(component, WCD9378_SYS_USAGE_CTRL, + WCD9378_SYS_USAGE_CTRL_MASK, 0x00); + + wcd9378_class_load(component); +} + +/* + * Derive the sys-usage capability bit for an ADC from its input mux. + * Only combinations the canned profiles cover are usable. + */ +static int wcd9378_sys_usage_bit_get(struct snd_soc_component *component, + int adc, bool *is_jack) +{ + u32 val; + + *is_jack = false; + + switch (adc) { + case 0: + val = snd_soc_component_read(component, WCD9378_TX_NEW_CH12_MUX) & + WCD9378_TX_NEW_CH12_MUX_CH1_SEL_MASK; + if (val == 0x01) + return WCD9378_SYS_USAGE_TX0_AMIC1; + if (val == 0x02) + return WCD9378_SYS_USAGE_TX0_AMIC2; + break; + case 1: + val = (snd_soc_component_read(component, WCD9378_TX_NEW_CH12_MUX) & + WCD9378_TX_NEW_CH12_MUX_CH2_SEL_MASK) >> 3; + if (val == 0x02) { + *is_jack = true; + return WCD9378_SYS_USAGE_TX1_AMIC2; + } + if (val == 0x03) + return WCD9378_SYS_USAGE_TX1_AMIC3; + break; + case 2: + val = snd_soc_component_read(component, WCD9378_TX_NEW_CH34_MUX) & + WCD9378_TX_NEW_CH34_MUX_CH3_SEL_MASK; + if (val == 0x01) + return WCD9378_SYS_USAGE_TX2_AMIC1; + if (val == 0x03) + return WCD9378_SYS_USAGE_TX2_AMIC4; + break; + } + + dev_err(component->dev, + "ADC%d input mux selection not supported by any sys-usage profile\n", + adc + 1); + return -EINVAL; +} + +static int wcd9378_sys_usage_update(struct snd_soc_component *component, + int bit, bool enable) +{ + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + int i; + + if (!enable) { + clear_bit(bit, &wcd9378->sys_usage_mask); + return 0; + } + + set_bit(bit, &wcd9378->sys_usage_mask); + + for (i = 0; i < ARRAY_SIZE(wcd9378_sys_usage_profiles); i++) { + if ((wcd9378_sys_usage_profiles[i] & wcd9378->sys_usage_mask) == + wcd9378->sys_usage_mask) + break; + } + + if (i == ARRAY_SIZE(wcd9378_sys_usage_profiles)) { + clear_bit(bit, &wcd9378->sys_usage_mask); + dev_err(component->dev, + "no sys-usage profile covers active paths (mask %#lx)\n", + wcd9378->sys_usage_mask); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WCD9378_SYS_USAGE_CTRL, + WCD9378_SYS_USAGE_CTRL_MASK, i); + + return 0; +} + +static u32 wcd9378_get_mode_val(struct wcd9378_priv *wcd9378, int adc) +{ + switch (wcd9378->tx_mode[adc]) { + case 1: + return WCD9378_ADC_USAGE_HIFI; + case 2: + return WCD9378_ADC_USAGE_LO_HIF; + case 4: + return WCD9378_ADC_USAGE_LP; + case 0: /* ADC_INVALID (unset) */ + case 3: + default: + return WCD9378_ADC_USAGE_NORMAL; + } +} + +/* + * Indicate the bus clock to the codec through the standard SCP + * BusClock_Base/Scale registers. The TX sequencers clock off the + * SoundWire bus clock and stall without this; the downstream driver + * writes these on every capture start and clears them on the last + * teardown. + */ +/* Actual bus clock is half the SoundWire double-rate frequency */ +static unsigned int wcd9378_tx_bus_clk(struct wcd9378_priv *wcd9378) +{ + return wcd9378->tx_sdw_dev->bus->params.curr_dr_freq / 2; +} + +static void wcd9378_swr_clk_indicate(struct wcd9378_priv *wcd9378, bool enable) +{ + struct sdw_slave *tx = wcd9378->tx_sdw_dev; + u8 scale; + + if (enable) { + if (wcd9378_tx_bus_clk(wcd9378) >= 9600000) + scale = WCD9378_SWRS_CLK_SCALE_DIV2; + else + scale = WCD9378_SWRS_CLK_SCALE_DIV4; + + /* + * The downstream master broadcasts HOST_CLK_DIV2_CTL = 0x01 + * (both banks) on every capture start; the sequencer does not + * power up without it. + */ + sdw_write(tx, WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(0), 0x01); + sdw_write(tx, WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(1), 0x01); + sdw_write(tx, WCD9378_SWRS_SCP_BASE_CLK, + WCD9378_SWRS_BASE_CLK_19P2MHZ); + sdw_write(tx, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0, scale); + sdw_write(tx, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1, scale); + sdw_write(tx, SDW_SCP_COMMIT, 0x02); + } else { + sdw_write(tx, WCD9378_SWRS_SCP_BASE_CLK, 0x00); + sdw_write(tx, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0, 0x00); + sdw_write(tx, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1, 0x00); + } +} + +#define WCD9378_SYS_USAGE_TX_MASK (BIT(WCD9378_SYS_USAGE_TX2_AMIC4) | \ + BIT(WCD9378_SYS_USAGE_TX2_AMIC1) | \ + BIT(WCD9378_SYS_USAGE_TX1_AMIC3) | \ + BIT(WCD9378_SYS_USAGE_TX1_AMIC2) | \ + BIT(WCD9378_SYS_USAGE_TX0_AMIC2) | \ + BIT(WCD9378_SYS_USAGE_TX0_AMIC1)) + +static int wcd9378_tx_sequencer_enable(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + const struct wcd9378_smp_fn *fn; + int adc = w->shift; + bool is_jack = false; + int sys_bit, retries; + u32 val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + sys_bit = wcd9378_sys_usage_bit_get(component, adc, &is_jack); + if (sys_bit < 0) + return sys_bit; + + /* + * Latch the profile bit and the target function: the input + * mux can be rewritten while the path is powered, and + * power-down must tear down what was actually powered up. + */ + wcd9378->tx_sys_bit[adc] = sys_bit; + wcd9378->tx_is_jack[adc] = is_jack; + if (is_jack) + fn = &wcd9378_smp_jack_adc2; + else + fn = &wcd9378_smp_mic[adc]; + + if (wcd9378_sys_usage_update(component, sys_bit, true)) + return -EINVAL; + + /* + * NORMAL/HIFI ADC modes need a 9.6 MHz bus clock; on a + * 4.8 MHz bus only the LP mode is valid and anything else + * makes the sequencer refuse to power up. + */ + if (wcd9378_tx_bus_clk(wcd9378) < 9600000) + val = WCD9378_ADC_USAGE_LP; + else + val = wcd9378_get_mode_val(wcd9378, adc); + + snd_soc_component_update_bits(component, fn->usage_reg, 0xff, val); + if (fn->hpf_reg) + snd_soc_component_update_bits(component, fn->hpf_reg, + fn->hpf_mask, fn->hpf_mask); + snd_soc_component_update_bits(component, fn->req_reg, 0xff, + WCD9378_PDE_PS0_ON); + usleep_range(800, 810); + + wcd9378_swr_clk_indicate(wcd9378, true); + + if (fn->hpf_reg) + snd_soc_component_update_bits(component, fn->hpf_reg, + fn->hpf_mask, 0x00); + + /* Wait for the sequencer to reach PS0 */ + retries = 20; + do { + val = snd_soc_component_read(component, fn->act_reg); + if (val == WCD9378_PDE_PS0_ON) + break; + usleep_range(500, 510); + } while (--retries); + if (val != WCD9378_PDE_PS0_ON) + dev_warn(component->dev, + "TX%d sequencer not in PS0 (act_ps %#x, bus %u Hz)\n", + adc, val, + wcd9378->tx_sdw_dev->bus->params.curr_dr_freq); + break; + case SND_SOC_DAPM_POST_PMD: + sys_bit = wcd9378->tx_sys_bit[adc]; + if (sys_bit < 0) + break; + if (wcd9378->tx_is_jack[adc]) + fn = &wcd9378_smp_jack_adc2; + else + fn = &wcd9378_smp_mic[adc]; + + snd_soc_component_update_bits(component, fn->usage_reg, 0xff, + WCD9378_ADC_USAGE_OFF); + if (fn->hpf_reg) + snd_soc_component_update_bits(component, fn->hpf_reg, + fn->hpf_mask, 0x00); + snd_soc_component_update_bits(component, fn->req_reg, 0xff, + WCD9378_PDE_PS3_OFF); + usleep_range(800, 810); + wcd9378_sys_usage_update(component, sys_bit, false); + wcd9378->tx_sys_bit[adc] = -1; + + if (!(wcd9378->sys_usage_mask & WCD9378_SYS_USAGE_TX_MASK)) + wcd9378_swr_clk_indicate(wcd9378, false); + break; + } + + return 0; +} + +static int wcd9378_micbias_control(struct snd_soc_component *component, + int micb_num, int req) +{ + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + int mb_index = micb_num - 1; + u32 usage_reg; + u8 usage_val; + u8 pullup_bit; + + if (micb_num < MIC_BIAS_1 || micb_num > MIC_BIAS_3) + return -EINVAL; + + usage_reg = wcd9378_smp_mic[mb_index].micb_reg; + usage_val = wcd9378->micb_usage_val[mb_index]; + pullup_bit = BIT(mb_index); + + mutex_lock(&wcd9378->micb_lock); + + switch (req) { + case MICB_ENABLE: + wcd9378->micb_ref[mb_index]++; + if (wcd9378->micb_ref[mb_index] == 1) { + if (micb_num == MIC_BIAS_2) { + snd_soc_component_update_bits(component, + WCD9378_ANA_MICB2_RAMP, + WCD9378_ANA_MICB2_RAMP_SHIFT_CTL_MASK, + 0x0c); + snd_soc_component_update_bits(component, + WCD9378_ANA_MICB2_RAMP, + WCD9378_ANA_MICB2_RAMP_EN, 0x00); + } + snd_soc_component_update_bits(component, usage_reg, + 0xff, usage_val); + if (micb_num == MIC_BIAS_2) + snd_soc_component_update_bits(component, + WCD9378_SMP_JACK_IT31_MICB, + 0xff, usage_val); + } + break; + case MICB_DISABLE: + if (wcd9378->micb_ref[mb_index] > 0) + wcd9378->micb_ref[mb_index]--; + if (wcd9378->micb_ref[mb_index] == 0 && + wcd9378->pullup_ref[mb_index] > 0) { + snd_soc_component_update_bits(component, + WCD9378_MB_PULLUP_EN, + pullup_bit, pullup_bit); + snd_soc_component_update_bits(component, usage_reg, 0xff, + WCD9378_MICB_USAGE_1P8V_OR_PULLUP); + if (micb_num == MIC_BIAS_2) + snd_soc_component_update_bits(component, + WCD9378_SMP_JACK_IT31_MICB, 0xff, + WCD9378_MICB_USAGE_1P8V_OR_PULLUP); + } else if (wcd9378->micb_ref[mb_index] == 0) { + snd_soc_component_update_bits(component, usage_reg, + 0xff, WCD9378_MICB_USAGE_OFF); + if (micb_num == MIC_BIAS_2) { + snd_soc_component_update_bits(component, + WCD9378_SMP_JACK_IT31_MICB, + 0xff, WCD9378_MICB_USAGE_OFF); + snd_soc_component_update_bits(component, + WCD9378_ANA_MICB2_RAMP, + WCD9378_ANA_MICB2_RAMP_SHIFT_CTL_MASK, + 0x0c); + snd_soc_component_update_bits(component, + WCD9378_ANA_MICB2_RAMP, + WCD9378_ANA_MICB2_RAMP_EN, + WCD9378_ANA_MICB2_RAMP_EN); + } + } + break; + case MICB_PULLUP_ENABLE: + wcd9378->pullup_ref[mb_index]++; + if (wcd9378->pullup_ref[mb_index] == 1 && + wcd9378->micb_ref[mb_index] == 0) { + snd_soc_component_update_bits(component, + WCD9378_MB_PULLUP_EN, + pullup_bit, pullup_bit); + snd_soc_component_update_bits(component, usage_reg, 0xff, + WCD9378_MICB_USAGE_1P8V_OR_PULLUP); + if (micb_num == MIC_BIAS_2) + snd_soc_component_update_bits(component, + WCD9378_SMP_JACK_IT31_MICB, 0xff, + WCD9378_MICB_USAGE_1P8V_OR_PULLUP); + } + break; + case MICB_PULLUP_DISABLE: + if (wcd9378->pullup_ref[mb_index] > 0) + wcd9378->pullup_ref[mb_index]--; + if (wcd9378->pullup_ref[mb_index] == 0 && + wcd9378->micb_ref[mb_index] == 0) { + snd_soc_component_update_bits(component, + WCD9378_MB_PULLUP_EN, pullup_bit, 0x00); + snd_soc_component_update_bits(component, usage_reg, 0xff, + WCD9378_MICB_USAGE_PULL_DOWN); + if (micb_num == MIC_BIAS_2) + snd_soc_component_update_bits(component, + WCD9378_SMP_JACK_IT31_MICB, 0xff, + WCD9378_MICB_USAGE_PULL_DOWN); + } + break; + } + + mutex_unlock(&wcd9378->micb_lock); + + return 0; +} + +static int wcd9378_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int micb_num = w->shift; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wcd9378_micbias_control(component, micb_num, MICB_ENABLE); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(1000, 1100); + break; + case SND_SOC_DAPM_POST_PMD: + wcd9378_micbias_control(component, micb_num, MICB_DISABLE); + break; + } + + return 0; +} + +static int wcd9378_codec_enable_micbias_pullup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int micb_num = w->shift; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wcd9378_micbias_control(component, micb_num, + MICB_PULLUP_ENABLE); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(1000, 1100); + break; + case SND_SOC_DAPM_POST_PMD: + wcd9378_micbias_control(component, micb_num, + MICB_PULLUP_DISABLE); + break; + } + + return 0; +} + +static int wcd9378_connect_port(struct wcd9378_sdw_priv *wcd, u8 port_idx, + u8 ch_id, bool enable) +{ + struct sdw_port_config *port_config = &wcd->port_config[port_idx - 1]; + const struct wcd_sdw_ch_info *ch_info = &wcd->ch_info[ch_id]; + u8 port_num = ch_info->port_num; + u8 ch_mask = ch_info->ch_mask; + u8 mstr_port_num, mstr_ch_mask; + struct sdw_slave *sdev = wcd->sdev; + + port_config->num = port_num; + + mstr_port_num = sdev->m_port_map[port_num]; + mstr_ch_mask = ch_info->master_ch_mask; + + if (enable) { + port_config->ch_mask |= ch_mask; + wcd->master_channel_map[mstr_port_num] |= mstr_ch_mask; + } else { + port_config->ch_mask &= ~ch_mask; + wcd->master_channel_map[mstr_port_num] &= ~mstr_ch_mask; + } + + return 0; +} + +static int wcd9378_get_swr_port(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(comp); + struct wcd9378_sdw_priv *wcd; + int dai_id = mixer->shift; + int ch_idx = mixer->reg; + int portidx; + + wcd = wcd9378->sdw_priv[dai_id]; + portidx = wcd->ch_info[ch_idx].port_num; + + ucontrol->value.integer.value[0] = wcd->port_enable[portidx]; + + return 0; +} + +static int wcd9378_set_swr_port(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(comp); + struct wcd9378_sdw_priv *wcd; + int dai_id = mixer->shift; + int ch_idx = mixer->reg; + int portidx; + bool enable; + + wcd = wcd9378->sdw_priv[dai_id]; + portidx = wcd->ch_info[ch_idx].port_num; + + enable = ucontrol->value.integer.value[0]; + + if (enable == wcd->port_enable[portidx]) { + wcd9378_connect_port(wcd, portidx, ch_idx, enable); + return 0; + } + + wcd->port_enable[portidx] = enable; + wcd9378_connect_port(wcd, portidx, ch_idx, enable); + + return 1; +} + +static int wcd9378_tx_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int adc = e->shift_l; + + ucontrol->value.enumerated.item[0] = wcd9378->tx_mode[adc]; + + return 0; +} + +static int wcd9378_tx_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int adc = e->shift_l; + u32 mode_val = ucontrol->value.enumerated.item[0]; + + if (mode_val == wcd9378->tx_mode[adc]) + return 0; + + wcd9378->tx_mode[adc] = mode_val; + + return 1; +} + +static const char * const tx_mode_mux_text[] = { + "ADC_INVALID", "ADC_HIFI", "ADC_LO_HIF", "ADC_NORMAL", "ADC_LP", +}; + +static const struct soc_enum tx0_mode_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tx_mode_mux_text), + tx_mode_mux_text); +static const struct soc_enum tx1_mode_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 1, ARRAY_SIZE(tx_mode_mux_text), + tx_mode_mux_text); +static const struct soc_enum tx2_mode_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 2, ARRAY_SIZE(tx_mode_mux_text), + tx_mode_mux_text); + +static const struct snd_kcontrol_new wcd9378_snd_controls[] = { + SOC_SINGLE_TLV("ADC1 Volume", WCD9378_ANA_TX_CH1, 0, 20, 0, + analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", WCD9378_ANA_TX_CH2, 0, 20, 0, + analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", WCD9378_ANA_TX_CH3, 0, 20, 0, + analog_gain), + + SOC_ENUM_EXT("TX0 MODE", tx0_mode_enum, + wcd9378_tx_mode_get, wcd9378_tx_mode_put), + SOC_ENUM_EXT("TX1 MODE", tx1_mode_enum, + wcd9378_tx_mode_get, wcd9378_tx_mode_put), + SOC_ENUM_EXT("TX2 MODE", tx2_mode_enum, + wcd9378_tx_mode_get, wcd9378_tx_mode_put), + + SOC_SINGLE_EXT("ADC1 Switch", WCD9378_ADC1, AIF1_CAP, 1, 0, + wcd9378_get_swr_port, wcd9378_set_swr_port), + SOC_SINGLE_EXT("ADC2 Switch", WCD9378_ADC2, AIF1_CAP, 1, 0, + wcd9378_get_swr_port, wcd9378_set_swr_port), + SOC_SINGLE_EXT("ADC3 Switch", WCD9378_ADC3, AIF1_CAP, 1, 0, + wcd9378_get_swr_port, wcd9378_set_swr_port), +}; + +static const char * const adc1_mux_text[] = { + "CH1_AMIC_DISABLE", "CH1_AMIC1", "CH1_AMIC2", "CH1_AMIC3", "CH1_AMIC4", +}; + +static const char * const adc2_mux_text[] = { + "CH2_AMIC_DISABLE", "CH2_AMIC1", "CH2_AMIC2", "CH2_AMIC3", "CH2_AMIC4", +}; + +static const char * const adc3_mux_text[] = { + "CH3_AMIC_DISABLE", "CH3_AMIC1", "CH3_AMIC3", "CH3_AMIC4", +}; + +static const struct soc_enum adc1_mux_enum = + SOC_ENUM_SINGLE(WCD9378_TX_NEW_CH12_MUX, 0, + ARRAY_SIZE(adc1_mux_text), adc1_mux_text); +static const struct soc_enum adc2_mux_enum = + SOC_ENUM_SINGLE(WCD9378_TX_NEW_CH12_MUX, 3, + ARRAY_SIZE(adc2_mux_text), adc2_mux_text); +static const struct soc_enum adc3_mux_enum = + SOC_ENUM_SINGLE(WCD9378_TX_NEW_CH34_MUX, 0, + ARRAY_SIZE(adc3_mux_text), adc3_mux_text); + +static const struct snd_kcontrol_new adc1_mux = SOC_DAPM_ENUM("ADC1 MUX", adc1_mux_enum); +static const struct snd_kcontrol_new adc2_mux = SOC_DAPM_ENUM("ADC2 MUX", adc2_mux_enum); +static const struct snd_kcontrol_new adc3_mux = SOC_DAPM_ENUM("ADC3 MUX", adc3_mux_enum); + +static const struct snd_kcontrol_new tx0_seq_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); +static const struct snd_kcontrol_new tx1_seq_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); +static const struct snd_kcontrol_new tx2_seq_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0); + +static const struct snd_soc_dapm_widget wcd9378_dapm_widgets[] = { + /* Analog mic inputs */ + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("AMIC3"), + SND_SOC_DAPM_INPUT("AMIC4"), + + /* ADC input muxes */ + SND_SOC_DAPM_MUX("ADC1 MUX", SND_SOC_NOPM, 0, 0, &adc1_mux), + SND_SOC_DAPM_MUX("ADC2 MUX", SND_SOC_NOPM, 0, 0, &adc2_mux), + SND_SOC_DAPM_MUX("ADC3 MUX", SND_SOC_NOPM, 0, 0, &adc3_mux), + + /* SDCA TX sequencers (widget shift = ADC index) */ + SND_SOC_DAPM_MIXER_E("TX0 SEQUENCER", SND_SOC_NOPM, 0, 0, + &tx0_seq_switch, 1, wcd9378_tx_sequencer_enable, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER_E("TX1 SEQUENCER", SND_SOC_NOPM, 1, 0, + &tx1_seq_switch, 1, wcd9378_tx_sequencer_enable, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER_E("TX2 SEQUENCER", SND_SOC_NOPM, 2, 0, + &tx2_seq_switch, 1, wcd9378_tx_sequencer_enable, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Micbias supplies (widget shift = micbias number) */ + SND_SOC_DAPM_SUPPLY("MIC BIAS1", SND_SOC_NOPM, MIC_BIAS_1, 0, + wcd9378_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("MIC BIAS2", SND_SOC_NOPM, MIC_BIAS_2, 0, + wcd9378_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("MIC BIAS3", SND_SOC_NOPM, MIC_BIAS_3, 0, + wcd9378_codec_enable_micbias, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("VA MIC BIAS1", SND_SOC_NOPM, MIC_BIAS_1, 0, + wcd9378_codec_enable_micbias_pullup, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("VA MIC BIAS2", SND_SOC_NOPM, MIC_BIAS_2, 0, + wcd9378_codec_enable_micbias_pullup, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("VA MIC BIAS3", SND_SOC_NOPM, MIC_BIAS_3, 0, + wcd9378_codec_enable_micbias_pullup, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Outputs towards the SoundWire TX bus / LPASS TX macro */ + SND_SOC_DAPM_OUTPUT("ADC1_OUTPUT"), + SND_SOC_DAPM_OUTPUT("ADC2_OUTPUT"), + SND_SOC_DAPM_OUTPUT("ADC3_OUTPUT"), +}; + +static const struct snd_soc_dapm_route wcd9378_audio_map[] = { + { "ADC1_OUTPUT", NULL, "TX0 SEQUENCER" }, + { "TX0 SEQUENCER", "Switch", "ADC1 MUX" }, + { "ADC1 MUX", "CH1_AMIC1", "AMIC1" }, + { "ADC1 MUX", "CH1_AMIC2", "AMIC2" }, + { "ADC1 MUX", "CH1_AMIC3", "AMIC3" }, + { "ADC1 MUX", "CH1_AMIC4", "AMIC4" }, + + { "ADC2_OUTPUT", NULL, "TX1 SEQUENCER" }, + { "TX1 SEQUENCER", "Switch", "ADC2 MUX" }, + { "ADC2 MUX", "CH2_AMIC1", "AMIC1" }, + { "ADC2 MUX", "CH2_AMIC2", "AMIC2" }, + { "ADC2 MUX", "CH2_AMIC3", "AMIC3" }, + { "ADC2 MUX", "CH2_AMIC4", "AMIC4" }, + + { "ADC3_OUTPUT", NULL, "TX2 SEQUENCER" }, + { "TX2 SEQUENCER", "Switch", "ADC3 MUX" }, + { "ADC3 MUX", "CH3_AMIC1", "AMIC1" }, + { "ADC3 MUX", "CH3_AMIC3", "AMIC3" }, + { "ADC3 MUX", "CH3_AMIC4", "AMIC4" }, +}; + +/* + * Map the DT micbias millivolt values onto ITxx_MICB usage codes; + * non-standard voltages go through the sequencer remap table. + */ +static void wcd9378_set_micb_usage_vals(struct snd_soc_component *component) +{ + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + static const u32 remap_regs[] = { + WCD9378_MICB_REMAP_TABLE_VAL_3, + WCD9378_MICB_REMAP_TABLE_VAL_4, + WCD9378_MICB_REMAP_TABLE_VAL_5, + }; + int i, vout; + + for (i = 0; i < WCD9378_MAX_MICBIAS; i++) { + switch (wcd9378->common.micb_mv[i]) { + case 1200: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_1P2V; + break; + case 1800: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_1P8V_OR_PULLUP; + break; + case 2200: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_2P2V; + break; + case 2500: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_2P5V; + break; + case 2700: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_2P7V; + break; + case 2750: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_2P75V; + break; + case 2800: + wcd9378->micb_usage_val[i] = WCD9378_MICB_USAGE_2P8V; + break; + default: + vout = wcd_get_micb_vout_ctl_val(component->dev, + wcd9378->common.micb_mv[i]); + if (vout < 0) { + wcd9378->micb_usage_val[i] = + WCD9378_MICB_USAGE_1P8V_OR_PULLUP; + break; + } + snd_soc_component_update_bits(component, remap_regs[i], + 0xff, vout); + wcd9378->micb_usage_val[i] = + WCD9378_MICB_USAGE_REMAP_TABLE_3 + i; + break; + } + } +} + +static int wcd9378_soc_codec_probe(struct snd_soc_component *component) +{ + struct wcd9378_priv *wcd9378 = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + unsigned int part0 = 0, part1 = 0; + unsigned long time_left; + int ret; + + /* + * Mainline uses sdw_slave_wait_for_init() here; the v7.1.2 base + * predates that helper, open-code it. + */ + time_left = wait_for_completion_timeout(&wcd9378->tx_sdw_dev->initialization_complete, + msecs_to_jiffies(5000)); + if (!time_left) { + dev_err(dev, "soundwire device init timeout\n"); + return -ETIMEDOUT; + } + + snd_soc_component_init_regmap(component, wcd9378->regmap); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + regmap_read(wcd9378->regmap, WCD9378_DEV_PART_ID_0, &part0); + regmap_read(wcd9378->regmap, WCD9378_DEV_PART_ID_1, &part1); + dev_dbg(dev, "WCD9378 part id %#x\n", (part1 << 8) | part0); + + /* + * SDCA interrupt type configuration, mirroring the downstream init + * sequence. Nothing consumes these interrupts yet (no MBHC support); + * kept so the bring-up sequence validated on hardware is unchanged. + */ + sdw_write(wcd9378->tx_sdw_dev, WCD9378_SWRS_SCP_SDCA_INTRTYPE_1, 0xff); + sdw_write(wcd9378->tx_sdw_dev, WCD9378_SWRS_SCP_SDCA_INTRTYPE_2, 0x0b); + sdw_write(wcd9378->tx_sdw_dev, WCD9378_SWRS_SCP_SDCA_INTRTYPE_3, 0xff); + + wcd9378_io_init(component); + wcd9378_set_micb_usage_vals(component); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_wcd9378 = { + .name = "wcd9378_codec", + .probe = wcd9378_soc_codec_probe, + .controls = wcd9378_snd_controls, + .num_controls = ARRAY_SIZE(wcd9378_snd_controls), + .dapm_widgets = wcd9378_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wcd9378_dapm_widgets), + .dapm_routes = wcd9378_audio_map, + .num_dapm_routes = ARRAY_SIZE(wcd9378_audio_map), + .endianness = 1, +}; + +static int wcd9378_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dai->dev); + struct wcd9378_sdw_priv *wcd = wcd9378->sdw_priv[dai->id]; + + return wcd9378_sdw_hw_params(wcd, substream, params, dai); +} + +static int wcd9378_codec_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dai->dev); + struct wcd9378_sdw_priv *wcd = wcd9378->sdw_priv[dai->id]; + + return sdw_stream_remove_slave(wcd->sdev, wcd->sruntime); +} + +static int wcd9378_codec_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dai->dev); + struct wcd9378_sdw_priv *wcd = wcd9378->sdw_priv[dai->id]; + + wcd->sruntime = stream; + + return 0; +} + +static int wcd9378_get_channel_map(const struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dai->dev); + struct wcd9378_sdw_priv *wcd = wcd9378->sdw_priv[dai->id]; + int i; + + switch (dai->id) { + case AIF1_PB: + if (!rx_slot || !rx_num) { + dev_err(dai->dev, "Invalid rx_slot %p or rx_num %p\n", + rx_slot, rx_num); + return -EINVAL; + } + + for (i = 0; i < SDW_MAX_PORTS; i++) + rx_slot[i] = wcd->master_channel_map[i]; + + *rx_num = i; + break; + case AIF1_CAP: + if (!tx_slot || !tx_num) { + dev_err(dai->dev, "Invalid tx_slot %p or tx_num %p\n", + tx_slot, tx_num); + return -EINVAL; + } + + for (i = 0; i < SDW_MAX_PORTS; i++) + tx_slot[i] = wcd->master_channel_map[i]; + + *tx_num = i; + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops wcd9378_sdw_dai_ops = { + .hw_params = wcd9378_codec_hw_params, + .hw_free = wcd9378_codec_free, + .set_stream = wcd9378_codec_set_sdw_stream, + .get_channel_map = wcd9378_get_channel_map, +}; + +#define WCD9378_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define WCD9378_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wcd9378_dais[] = { + [0] = { + .name = "wcd9378-sdw-rx", + .playback = { + .stream_name = "WCD AIF Playback", + .rates = WCD9378_RATES, + .formats = WCD9378_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &wcd9378_sdw_dai_ops, + }, + [1] = { + .name = "wcd9378-sdw-tx", + .capture = { + .stream_name = "WCD AIF Capture", + .rates = WCD9378_RATES, + .formats = WCD9378_FORMATS, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &wcd9378_sdw_dai_ops, + }, +}; + +static int wcd9378_bind(struct device *dev) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dev); + int ret; + + /* Give the SDW subdevices some more time to settle */ + usleep_range(5000, 5010); + + ret = component_bind_all(dev, wcd9378); + if (ret) { + dev_err(dev, "Slave bind failed, ret = %d\n", ret); + return ret; + } + + wcd9378->rxdev = of_sdw_find_device_by_node(wcd9378->rxnode); + if (!wcd9378->rxdev) { + dev_err(dev, "could not find rx slave with matching of node\n"); + ret = -EINVAL; + goto err_component_unbind; + } + + wcd9378->sdw_priv[AIF1_PB] = dev_get_drvdata(wcd9378->rxdev); + wcd9378->sdw_priv[AIF1_PB]->wcd9378 = wcd9378; + + wcd9378->txdev = of_sdw_find_device_by_node(wcd9378->txnode); + if (!wcd9378->txdev) { + dev_err(dev, "could not find tx slave with matching of node\n"); + ret = -EINVAL; + goto err_put_rxdev; + } + + wcd9378->sdw_priv[AIF1_CAP] = dev_get_drvdata(wcd9378->txdev); + wcd9378->sdw_priv[AIF1_CAP]->wcd9378 = wcd9378; + wcd9378->tx_sdw_dev = dev_to_sdw_dev(wcd9378->txdev); + + /* + * As TX is the main CSR reg interface, it should not be suspended + * first. Explicitly add the dependency link. + */ + if (!device_link_add(wcd9378->rxdev, wcd9378->txdev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { + dev_err(dev, "Could not devlink TX and RX\n"); + ret = -EINVAL; + goto err_put_txdev; + } + + if (!device_link_add(dev, wcd9378->txdev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { + dev_err(dev, "Could not devlink WCD and TX\n"); + ret = -EINVAL; + goto err_remove_link1; + } + + if (!device_link_add(dev, wcd9378->rxdev, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME)) { + dev_err(dev, "Could not devlink WCD and RX\n"); + ret = -EINVAL; + goto err_remove_link2; + } + + wcd9378->regmap = wcd9378->sdw_priv[AIF1_CAP]->regmap; + if (!wcd9378->regmap) { + dev_err(dev, "could not get TX device regmap\n"); + ret = -EINVAL; + goto err_remove_link3; + } + + /* + * The SDCA function engine dies when the TX bus enters clock-stop + * and only a codec reset revives it — registers keep their values + * so a regcache sync or a FUNC_ACT re-toggle does not help. The + * downstream stack sidesteps the same problem by marking the TX + * SoundWire master "qcom,is-always-on"; do the equivalent and + * keep the TX slave (and thus its bus) runtime-active while the + * codec is bound. + */ + ret = pm_runtime_resume_and_get(wcd9378->txdev); + if (ret < 0) { + dev_err(dev, "could not resume TX device\n"); + goto err_remove_link3; + } + + ret = snd_soc_register_component(dev, &soc_codec_dev_wcd9378, + wcd9378_dais, ARRAY_SIZE(wcd9378_dais)); + if (ret) { + dev_err(dev, "Codec registration failed\n"); + pm_runtime_put(wcd9378->txdev); + goto err_remove_link3; + } + + return ret; + +err_remove_link3: + device_link_remove(dev, wcd9378->rxdev); +err_remove_link2: + device_link_remove(dev, wcd9378->txdev); +err_remove_link1: + device_link_remove(wcd9378->rxdev, wcd9378->txdev); +err_put_txdev: + put_device(wcd9378->txdev); +err_put_rxdev: + put_device(wcd9378->rxdev); +err_component_unbind: + component_unbind_all(dev, wcd9378); + return ret; +} + +static void wcd9378_unbind(struct device *dev) +{ + struct wcd9378_priv *wcd9378 = dev_get_drvdata(dev); + + snd_soc_unregister_component(dev); + pm_runtime_put(wcd9378->txdev); + device_link_remove(dev, wcd9378->txdev); + device_link_remove(dev, wcd9378->rxdev); + device_link_remove(wcd9378->rxdev, wcd9378->txdev); + component_unbind_all(dev, wcd9378); + mutex_destroy(&wcd9378->micb_lock); + put_device(wcd9378->txdev); + put_device(wcd9378->rxdev); +} + +static const struct component_master_ops wcd9378_comp_ops = { + .bind = wcd9378_bind, + .unbind = wcd9378_unbind, +}; + +static int wcd9378_add_slave_components(struct wcd9378_priv *wcd9378, + struct device *dev, + struct component_match **matchptr) +{ + struct device_node *np = dev->of_node; + + wcd9378->rxnode = of_parse_phandle(np, "qcom,rx-device", 0); + if (!wcd9378->rxnode) { + dev_err(dev, "Couldn't parse phandle to qcom,rx-device!\n"); + return -ENODEV; + } + + component_match_add_release(dev, matchptr, component_release_of, + component_compare_of, wcd9378->rxnode); + + wcd9378->txnode = of_parse_phandle(np, "qcom,tx-device", 0); + if (!wcd9378->txnode) { + dev_err(dev, "Couldn't parse phandle to qcom,tx-device\n"); + return -ENODEV; + } + + component_match_add_release(dev, matchptr, component_release_of, + component_compare_of, wcd9378->txnode); + + return 0; +} + +static int wcd9378_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + struct device *dev = &pdev->dev; + struct wcd9378_priv *wcd9378; + int ret, i; + + wcd9378 = devm_kzalloc(dev, sizeof(*wcd9378), GFP_KERNEL); + if (!wcd9378) + return -ENOMEM; + + dev_set_drvdata(dev, wcd9378); + mutex_init(&wcd9378->micb_lock); + for (i = 0; i < ARRAY_SIZE(wcd9378->tx_sys_bit); i++) + wcd9378->tx_sys_bit[i] = -1; + wcd9378->common.dev = dev; + wcd9378->common.max_bias = WCD9378_MAX_MICBIAS; + + wcd9378->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(wcd9378->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(wcd9378->reset_gpio), + "failed to get reset gpio\n"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(wcd9378_supplies), + wcd9378_supplies); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable supplies\n"); + + ret = wcd_dt_parse_micbias_info(&wcd9378->common); + if (ret) + return dev_err_probe(dev, ret, "Failed to get micbias\n"); + + ret = wcd9378_add_slave_components(wcd9378, dev, &match); + if (ret) + return ret; + + wcd9378_reset(wcd9378); + + ret = component_master_add_with_match(dev, &wcd9378_comp_ops, match); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void wcd9378_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + component_master_del(dev, &wcd9378_comp_ops); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); +} + +static const struct of_device_id wcd9378_of_match[] = { + { .compatible = "qcom,wcd9378-codec" }, + { } +}; +MODULE_DEVICE_TABLE(of, wcd9378_of_match); + +static struct platform_driver wcd9378_codec_driver = { + .probe = wcd9378_probe, + .remove = wcd9378_remove, + .driver = { + .name = "wcd9378_codec", + .of_match_table = wcd9378_of_match, + .suppress_bind_attrs = true, + }, +}; +module_platform_driver(wcd9378_codec_driver); + +MODULE_DESCRIPTION("WCD9378 Codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wcd9378.h b/sound/soc/codecs/wcd9378.h new file mode 100644 index 000000000000..fb9e7925968a --- /dev/null +++ b/sound/soc/codecs/wcd9378.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026, Jorijn van der Graaf + * + * Register map for the Qualcomm WCD9378 audio codec. + * + * The codec exposes its control registers in the SoundWire SDCA control + * address space (bit 30 set, SDCA function number in bits 25:22), accessed + * through the TX SoundWire slave. The analog core registers (function 0, + * implementation-defined region at +0x180000) are layout-compatible with + * the WCD937x family; on top of that the chip adds SDCA-style functions + * (SmartMIC0/1/2, SmartJACK, SmartAMP) whose sequencers drive the analog + * power-up autonomously. + */ + +#ifndef __WCD9378_H__ +#define __WCD9378_H__ + +#include +#include +#include +#include + +/* SDCA function 0 (extension unit): device identity */ +#define WCD9378_FUNC_EXT_ID_0 0x40000048 +#define WCD9378_FUNC_EXT_ID_1 0x40000049 +#define WCD9378_FUNC_EXT_VER 0x40000050 +#define WCD9378_FUNC_STAT 0x40080000 +#define WCD9378_DEV_MANU_ID_0 0x40100060 +#define WCD9378_DEV_MANU_ID_1 0x40100061 +#define WCD9378_DEV_PART_ID_0 0x40100068 +#define WCD9378_DEV_PART_ID_1 0x40100069 +#define WCD9378_DEV_VER 0x40100070 + +/* Analog core (WCD937x-compatible layout), function 0 + 0x180000 */ +#define WCD9378_ANA_PAGE 0x40180000 +#define WCD9378_ANA_BIAS 0x40180001 +#define WCD9378_ANA_BIAS_ANALOG_BIAS_EN BIT(7) +#define WCD9378_ANA_BIAS_PRECHRG_EN BIT(6) +#define WCD9378_ANA_RX_SUPPLIES 0x40180008 +#define WCD9378_ANA_TX_CH1 0x4018000e +#define WCD9378_ANA_TX_CH2 0x4018000f +#define WCD9378_ANA_TX_CH2_HPF1_INIT BIT(6) +#define WCD9378_ANA_TX_CH2_HPF2_INIT BIT(5) +#define WCD9378_ANA_TX_CH3 0x40180010 +#define WCD9378_ANA_TX_CH3_HPF 0x40180011 +#define WCD9378_ANA_TX_CH3_HPF3_INIT BIT(6) +#define WCD9378_ANA_TX_GAIN_MASK GENMASK(4, 0) +#define WCD9378_ANA_MICB1 0x40180022 +#define WCD9378_ANA_MICB2 0x40180023 +#define WCD9378_ANA_MICB2_RAMP 0x40180024 +#define WCD9378_ANA_MICB2_RAMP_SHIFT_CTL_MASK GENMASK(4, 2) +#define WCD9378_ANA_MICB2_RAMP_EN BIT(7) +#define WCD9378_ANA_MICB3 0x40180025 +#define WCD9378_BIAS_VBG_FINE_ADJ 0x40180029 +#define WCD9378_MBHC_CTL_SPARE_1 0x40180058 +#define WCD9378_MICB1_TEST_CTL_2 0x4018006c +#define WCD9378_MICB2_TEST_CTL_2 0x4018006f +#define WCD9378_MICB3_TEST_CTL_2 0x40180072 +#define WCD9378_TX_COM_TXFE_DIV_CTL 0x4018007b +#define WCD9378_TX_COM_TXFE_DIV_SEQ_BYPASS BIT(7) +#define WCD9378_SLEEP_CTL 0x40180103 +#define WCD9378_SLEEP_CTL_BG_CTL_MASK GENMASK(3, 1) +#define WCD9378_SLEEP_CTL_BG_EN BIT(7) +#define WCD9378_SLEEP_CTL_LDOL_BG_SEL BIT(6) +#define WCD9378_TX_NEW_CH12_MUX 0x4018012e +#define WCD9378_TX_NEW_CH12_MUX_CH1_SEL_MASK GENMASK(2, 0) +#define WCD9378_TX_NEW_CH12_MUX_CH2_SEL_MASK GENMASK(5, 3) +#define WCD9378_TX_NEW_CH34_MUX 0x4018012f +#define WCD9378_TX_NEW_CH34_MUX_CH3_SEL_MASK GENMASK(2, 0) +#define WCD9378_HPH_RDAC_GAIN_CTL 0x40180132 +#define WCD9378_HPH_RDAC_HD2_CTL_L 0x40180133 +#define WCD9378_HPH_RDAC_HD2_CTL_R 0x40180136 + +/* Digital page */ +#define WCD9378_TOP_CLK_CFG 0x40180407 +#define WCD9378_CDC_ANA_TX_CLK_CTL 0x40180417 +#define WCD9378_CDC_ANA_TXSCBIAS_CLK_EN BIT(0) +#define WCD9378_CDC_AMIC_CTL 0x4018045a +#define WCD9378_PDM_WD_CTL0 0x40180465 +#define WCD9378_PDM_WD_CTL1 0x40180466 +#define WCD9378_EFUSE_REG_16 0x401804c0 +#define WCD9378_EFUSE_REG_29 0x401804cd +#define WCD9378_PLATFORM_CTL 0x401804f0 + +/* Sequencer block (SEQR) */ +#define WCD9378_SYS_USAGE_CTRL 0x40180501 +#define WCD9378_SYS_USAGE_CTRL_MASK GENMASK(3, 0) +#define WCD9378_HPH_UP_T0 0x40180510 +#define WCD9378_HPH_UP_T9 0x40180519 +#define WCD9378_HPH_DN_T0 0x4018051b +#define WCD9378_SEQ_TX0_STAT 0x40180592 +#define WCD9378_SEQ_TX1_STAT 0x40180593 +#define WCD9378_SEQ_TX2_STAT 0x40180594 +#define WCD9378_MICB_REMAP_TABLE_VAL_3 0x401805a3 +#define WCD9378_MICB_REMAP_TABLE_VAL_4 0x401805a4 +#define WCD9378_MICB_REMAP_TABLE_VAL_5 0x401805a5 +#define WCD9378_SM0_MB_SEL 0x401805b0 +#define WCD9378_SM1_MB_SEL 0x401805b1 +#define WCD9378_SM2_MB_SEL 0x401805b2 +#define WCD9378_SM_MB_SEL_MASK GENMASK(1, 0) +#define WCD9378_MB_PULLUP_EN 0x401805b3 + +/* SmartAMP SDCA function */ +#define WCD9378_SMP_AMP_FUNC_STAT 0x40880000 +#define WCD9378_SMP_AMP_FUNC_ACT 0x40880008 + +/* SmartJACK SDCA function (hosts ADC2 when fed from AMIC2) */ +#define WCD9378_CMT_GRP_MASK 0x40c00008 +#define WCD9378_SMP_JACK_IT31_MICB 0x40c00798 +#define WCD9378_SMP_JACK_IT31_USAGE 0x40c007a0 +#define WCD9378_SMP_JACK_PDE34_REQ_PS 0x40c00808 +#define WCD9378_SMP_JACK_FUNC_STAT 0x40c80000 +#define WCD9378_SMP_JACK_FUNC_ACT 0x40c80008 +#define WCD9378_SMP_JACK_PDE34_ACT_PS 0x40c80800 + +/* SmartMIC0/1/2 SDCA functions (ADC1/ADC2/ADC3 sequencers) */ +#define WCD9378_SMP_MIC_BASE(n) (0x41000000 + (n) * 0x400000) +#define WCD9378_SMP_MIC_IT11_MICB(n) (WCD9378_SMP_MIC_BASE(n) + 0x98) +#define WCD9378_SMP_MIC_IT11_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0xa0) +#define WCD9378_SMP_MIC_PDE11_REQ_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x108) +#define WCD9378_SMP_MIC_OT10_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0x3a0) +#define WCD9378_SMP_MIC_FUNC_STAT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80000) +#define WCD9378_SMP_MIC_FUNC_ACT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80008) +#define WCD9378_SMP_MIC_PDE11_ACT_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x80100) + +#define WCD9378_MAX_REGISTER 0x41900070 + +/* + * Raw (16-bit, non-paged) Qualcomm slave SCP registers, written with + * sdw_write() directly. Bus clock indication towards the codec and + * SDCA interrupt type configuration. + */ +#define WCD9378_SWRS_SCP_BASE_CLK 0x4d +#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0 0x62 +#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1 0x72 +#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_1 0xf4 +#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_2 0xf8 +#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_3 0xfc +#define WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(m) (0xe0 + 0x10 * (m)) +#define WCD9378_SWRS_BASE_CLK_19P2MHZ 0x01 +#define WCD9378_SWRS_CLK_SCALE_DIV2 0x02 /* 9.6 MHz */ +#define WCD9378_SWRS_CLK_SCALE_DIV4 0x03 /* 4.8 MHz */ + +/* ITxx_USAGE ADC mode values */ +#define WCD9378_ADC_USAGE_HIFI 0x01 +#define WCD9378_ADC_USAGE_LO_HIF 0x02 +#define WCD9378_ADC_USAGE_NORMAL 0x03 +#define WCD9378_ADC_USAGE_LP 0x05 +#define WCD9378_ADC_USAGE_OFF 0x00 + +/* ITxx_MICB usage values */ +#define WCD9378_MICB_USAGE_OFF 0x00 +#define WCD9378_MICB_USAGE_PULL_DOWN 0x01 +#define WCD9378_MICB_USAGE_1P2V 0x02 +#define WCD9378_MICB_USAGE_1P8V_OR_PULLUP 0x03 +#define WCD9378_MICB_USAGE_2P5V 0x04 +#define WCD9378_MICB_USAGE_2P75V 0x05 +#define WCD9378_MICB_USAGE_2P2V 0xf0 +#define WCD9378_MICB_USAGE_2P7V 0xf1 +#define WCD9378_MICB_USAGE_2P8V 0xf2 +#define WCD9378_MICB_USAGE_REMAP_TABLE_3 0xf3 +#define WCD9378_MICB_USAGE_REMAP_TABLE_4 0xf4 +#define WCD9378_MICB_USAGE_REMAP_TABLE_5 0xf5 + +/* PDExx_REQ_PS power states */ +#define WCD9378_PDE_PS0_ON 0x00 +#define WCD9378_PDE_PS3_OFF 0x03 + +#define WCD9378_MAX_MICBIAS 3 +#define WCD9378_MAX_SWR_CH_IDS 15 +#define WCD9378_SWRM_CH_MASK(ch_idx) BIT((ch_idx) - 1) + +enum wcd9378_tx_sdw_ports { + WCD9378_ADC_1_PORT = 1, + WCD9378_ADC_2_PORT, + WCD9378_ADC_3_PORT, + WCD9378_DMIC_0_1_MBHC_PORT, + WCD9378_DMIC_2_5_PORT, + WCD9378_MAX_TX_SWR_PORTS = WCD9378_DMIC_2_5_PORT, +}; + +enum wcd9378_rx_sdw_ports { + WCD9378_HPH_PORT = 1, + WCD9378_CLSH_PORT, + WCD9378_COMP_PORT, + WCD9378_LO_PORT, + WCD9378_DSD_PORT, + WCD9378_MAX_SWR_PORTS = WCD9378_DSD_PORT, +}; + +enum wcd9378_tx_sdw_channels { + WCD9378_ADC1, + WCD9378_ADC2, + WCD9378_ADC3, + WCD9378_DMIC0, + WCD9378_DMIC1, + WCD9378_MBHC, + WCD9378_DMIC2, + WCD9378_DMIC3, + WCD9378_DMIC4, + WCD9378_DMIC5, +}; + +enum wcd9378_rx_sdw_channels { + WCD9378_HPH_L, + WCD9378_HPH_R, + WCD9378_CLSH, + WCD9378_COMP_L, + WCD9378_COMP_R, + WCD9378_LO, + WCD9378_DSD_L, + WCD9378_DSD_R, +}; + +struct wcd9378_priv; +struct wcd9378_sdw_priv { + struct sdw_slave *sdev; + struct sdw_stream_config sconfig; + struct sdw_stream_runtime *sruntime; + struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS]; + struct wcd_sdw_ch_info *ch_info; + bool port_enable[WCD9378_MAX_SWR_CH_IDS]; + unsigned int master_channel_map[SDW_MAX_PORTS]; + int active_ports; + bool is_tx; + struct wcd9378_priv *wcd9378; + struct regmap *regmap; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_WCD9378_SDW) +int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); +#else +static inline int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return -EOPNOTSUPP; +} +#endif + +#endif /* __WCD9378_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 006b283484d9..06946add653a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -224,6 +224,21 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s dev_err(dai->dev, "Failed to prepare Graph %d\n", rc); goto err; } + + /* + * Start the port already at prepare, like q6afe does: this starts + * the interface clocks before the DAPM power-up sequence runs, so + * codecs that need a live BCLK at power-up (e.g. aw88261) can + * start synchronously. The trigger callback keeps its start as a + * no-op fallback via is_port_started. + */ + rc = q6apm_graph_start(dai_data->graph[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "Failed to start APM port %d\n", dai->id); + goto err; + } + dai_data->is_port_started[dai->id] = true; + return 0; err: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index a36666999ac1..ce28bfac329a 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -34,6 +34,7 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) switch (cpu_dai->id) { case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX...QUINARY_MI2S_TX: + case SENARY_MI2S_RX...SENARY_MI2S_TX: snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); break; case WSA_CODEC_DMA_RX_0: