diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml index 2fb95544db8b..42d05334bd39 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml @@ -21,6 +21,47 @@ properties: '#sound-dai-cells': const: 1 + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +# Digital Audio Interfaces +patternProperties: + '^dai@[0-9a-f]+$': + type: object + description: + Q6DSP Digital Audio Interfaces. + + properties: + reg: + maxItems: 1 + description: + Digital Audio Interface ID + + clocks: + minItems: 1 + items: + - description: MI2S master clock + - description: MI2S bit clock + - description: MI2S external bit clock + + clock-names: + minItems: 1 + items: + - const: mclk + - const: bclk + - const: eclk + + dependencies: + clocks: [clock-names] + + required: + - reg + + additionalProperties: false + required: - compatible - '#sound-dai-cells' @@ -29,7 +70,22 @@ unevaluatedProperties: false examples: - | + #include + dais { compatible = "qcom,q6apm-lpass-dais"; #sound-dai-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + dai@16 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_MCLK_1 + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_PRI_MI2S_EBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", "bclk", "eclk"; + }; }; diff --git a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts index 7bbcb280b05f..2e90892cbc1f 100644 --- a/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts +++ b/arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts @@ -27,34 +27,6 @@ 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"; @@ -1046,6 +1018,17 @@ status = "okay"; }; +&q6apmbedai { + #address-cells = <1>; + #size-cells = <0>; + + dai@147 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_SEN_MI2S_IBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk"; + }; +}; + &qup_uart11_cts { /* * Configure a bias-bus-hold on CTS to lower power @@ -1217,14 +1200,6 @@ 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"; diff --git a/arch/arm64/boot/dts/qcom/milos.dtsi b/arch/arm64/boot/dts/qcom/milos.dtsi index 288e0e658629..5394f51bdd31 100644 --- a/arch/arm64/boot/dts/qcom/milos.dtsi +++ b/arch/arm64/boot/dts/qcom/milos.dtsi @@ -548,6 +548,14 @@ no-map; }; + adsp_rpc_heap_mem: adsp-rpc-heap { + compatible = "shared-dma-pool"; + alloc-ranges = <0x0 0x0 0x0 0xffffffff>; + reusable; + alignment = <0x0 0x400000>; + size = <0x0 0xc00000>; + }; + pvm_fw_mem: pvm-fw-region@824a0000 { reg = <0x0 0x824a0000 0x0 0x100000>; no-map; @@ -1398,6 +1406,9 @@ qcom,glink-channels = "fastrpcglink-apps-dsp"; label = "adsp"; qcom,non-secure-domain; + memory-region = <&adsp_rpc_heap_mem>; + qcom,vmids = ; #address-cells = <1>; #size-cells = <0>; diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 7cd4d751e1d2..fb313e591e85 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -198,17 +198,6 @@ 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 59ea5cf17287..5bd227f8c120 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -26,8 +26,6 @@ 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 deleted file mode 100644 index ceb4b98402bb..000000000000 --- a/drivers/iio/magnetometer/qmc6308.c +++ /dev/null @@ -1,590 +0,0 @@ -// 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/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index db823177e636..aae337572be7 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -520,6 +520,54 @@ int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len) qrtr_node_assign(node, le32_to_cpu(pkt->server.node)); } + /* The DSPs are star-connected through this node: forward packets + * destined to another node onto that node's endpoint (e.g. the + * modem's voice stack talking to the ADSP's audio service). Only + * DATA and RESUME_TX transit; control packets keep going to the + * local ns, which does its own mesh-wide redistribution. RESUME_TX + * additionally releases this hop's flow-control token on the + * arrival link: per-hop counters advance in lockstep with the + * end-to-end ones since every packet of the flow transits here. + */ + if (cb->dst_node != qrtr_local_nid && + cb->dst_node != QRTR_NODE_BCAST && + (cb->type == QRTR_TYPE_DATA || cb->type == QRTR_TYPE_RESUME_TX)) { + struct sockaddr_qrtr from = {AF_QIPCRTR, + cb->src_node, cb->src_port}; + struct sockaddr_qrtr to = {AF_QIPCRTR, + cb->dst_node, cb->dst_port}; + struct qrtr_node *dst; + + dst = qrtr_node_lookup(cb->dst_node); + if (!dst || dst == node) { + if (dst) + qrtr_node_release(dst); + goto err; + } + + if (cb->type == QRTR_TYPE_RESUME_TX) { + struct sk_buff *clone; + + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + qrtr_tx_resume(node, clone); + } + + pr_debug("qrtr: fwd %u:%u -> %u:%u type %d len %zu\n", + cb->src_node, cb->src_port, + cb->dst_node, cb->dst_port, cb->type, size); + + if (skb_cow_head(skb, sizeof(struct qrtr_hdr_v1))) { + qrtr_node_release(dst); + goto err; + } + + qrtr_node_enqueue(dst, skb, cb->type, &from, &to); + qrtr_node_release(dst); + + return 0; + } + if (cb->type == QRTR_TYPE_RESUME_TX) { qrtr_tx_resume(node, skb); } else { diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 67267304e7e9..d650e0b0ba5b 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only snd-q6dsp-common-y := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o -snd-q6apm-y := q6apm.o audioreach.o topology.o +snd-q6apm-y := q6apm.o audioreach.o topology.o q6apm-voice-proto.o obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 06946add653a..461e65526506 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -2,10 +2,12 @@ // Copyright (c) 2021, Linaro Limited #include +#include #include #include #include #include +#include #include #include #include @@ -15,13 +17,22 @@ #include "q6dsp-common.h" #include "audioreach.h" #include "q6apm.h" +#include "q6prm.h" #define AUDIOREACH_BE_PCM_BASE 16 +struct q6apm_dai_priv_data { + struct clk *mclk; + struct clk *bclk; + struct clk *eclk; + bool mclk_enabled, bclk_enabled, eclk_enabled; +}; + struct q6apm_lpass_dai_data { struct q6apm_graph *graph[APM_PORT_MAX]; bool is_port_started[APM_PORT_MAX]; struct audioreach_module_config module_config[APM_PORT_MAX]; + struct q6apm_dai_priv_data priv[APM_PORT_MAX]; }; static int q6dma_set_channel_map(struct snd_soc_dai *dai, @@ -224,21 +235,6 @@ 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) { @@ -266,6 +262,73 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s return 0; } +static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return q6apm_lpass_dai_startup(substream, dai); +} + +static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + + if (dai_data->priv[dai->id].mclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].mclk); + dai_data->priv[dai->id].mclk_enabled = false; + } + + if (dai_data->priv[dai->id].bclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].bclk); + dai_data->priv[dai->id].bclk_enabled = false; + } + + if (dai_data->priv[dai->id].eclk_enabled) { + clk_disable_unprepare(dai_data->priv[dai->id].eclk); + dai_data->priv[dai->id].eclk_enabled = false; + } + q6apm_lpass_dai_shutdown(substream, dai); +} + +static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct clk *sysclk = NULL; + bool *enabled = NULL; + int ret = 0; + + switch (clk_id) { + case LPAIF_MI2S_MCLK: + sysclk = dai_data->priv[dai->id].mclk; + enabled = &dai_data->priv[dai->id].mclk_enabled; + break; + case LPAIF_MI2S_BCLK: + sysclk = dai_data->priv[dai->id].bclk; + enabled = &dai_data->priv[dai->id].bclk_enabled; + break; + case LPAIF_MI2S_ECLK: + sysclk = dai_data->priv[dai->id].eclk; + enabled = &dai_data->priv[dai->id].eclk_enabled; + break; + default: + return -EINVAL; + } + + if (sysclk) { + if (*enabled) + return 0; + + clk_set_rate(sysclk, freq); + ret = clk_prepare_enable(sysclk); + if (ret) { + dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); + return ret; + } + + *enabled = true; + } + + return ret; +} + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); @@ -287,11 +350,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { static const struct snd_soc_dai_ops q6i2s_ops = { .prepare = q6apm_lpass_dai_prepare, - .startup = q6apm_lpass_dai_startup, - .shutdown = q6apm_lpass_dai_shutdown, + .startup = q6i2s_dai_startup, + .shutdown = q6i2s_lpass_dai_shutdown, .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, .set_fmt = q6i2s_set_fmt, + .set_sysclk = q6i2s_set_sysclk, .trigger = q6apm_lpass_dai_trigger, }; @@ -312,6 +376,73 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { .remove_order = SND_SOC_COMP_ORDER_FIRST, }; +static int of_q6apm_parse_dai_data(struct device *dev, + struct q6apm_lpass_dai_data *data) +{ + int ret; + + for_each_child_of_node_scoped(dev->of_node, node) { + struct q6apm_dai_priv_data *priv; + int id; + + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0 || id >= APM_PORT_MAX) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + switch (id) { + /* MI2S specific properties */ + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + priv = &data->priv[id]; + priv->mclk = of_clk_get_by_name(node, "mclk"); + if (IS_ERR(priv->mclk)) { + if (PTR_ERR(priv->mclk) == -EPROBE_DEFER) + return dev_err_probe(dev, PTR_ERR(priv->mclk), + "unable to get mi2s mclk\n"); + priv->mclk = NULL; + } + + priv->bclk = of_clk_get_by_name(node, "bclk"); + if (IS_ERR(priv->bclk)) { + if (PTR_ERR(priv->bclk) == -EPROBE_DEFER) { + if (priv->mclk) { + clk_put(priv->mclk); + priv->mclk = NULL; + } + return dev_err_probe(dev, PTR_ERR(priv->bclk), + "unable to get mi2s bclk\n"); + } + priv->bclk = NULL; + } + + priv->eclk = of_clk_get_by_name(node, "eclk"); + if (IS_ERR(priv->eclk)) { + if (PTR_ERR(priv->eclk) == -EPROBE_DEFER) { + if (priv->mclk) { + clk_put(priv->mclk); + priv->mclk = NULL; + } + if (priv->bclk) { + clk_put(priv->bclk); + priv->bclk = NULL; + } + return dev_err_probe(dev, PTR_ERR(priv->eclk), + "unable to get mi2s eclk\n"); + } + priv->eclk = NULL; + } + break; + default: + break; + } + } + + return 0; +} + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) { struct q6dsp_audio_port_dai_driver_config cfg; @@ -319,12 +450,16 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) struct snd_soc_dai_driver *dais; struct device *dev = &pdev->dev; int num_dais; + int ret; dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); if (!dai_data) return -ENOMEM; dev_set_drvdata(dev, dai_data); + ret = of_q6apm_parse_dai_data(dev, dai_data); + if (ret) + return ret; memset(&cfg, 0, sizeof(cfg)); cfg.q6i2s_ops = &q6i2s_ops; diff --git a/sound/soc/qcom/qdsp6/q6apm-voice-proto.c b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c new file mode 100644 index 000000000000..76817a881e8c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6apm-voice-proto.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Voice-call bring-up prototype for the Fairphone (Gen. 6): replay + * pre-generated APM command sequences through the q6apm GPR channel. + * + * The blobs reproduce, byte for byte, the GRAPH_OPEN/SET_CFG/PREPARE/START + * sequence the stock Android PAL/AGM/GSL stack sends to set up the + * handset voice-call graphs (mined from the device ACDB; generated by + * utilities/mkvoiceblobs.py in the bring-up repo). The modem's voice + * engine and the ADSP VCPM service wire up the vocoder themselves once + * these graphs are running. + * + * Local prototype only, NOT for upstream. Interface: + * echo > /sys/kernel/debug/q6apm-voice-proto/play + * where is relative to /lib/firmware. Playback aborts on the + * first command the DSP rejects. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audioreach.h" +#include "q6apm.h" + +#define Q6VP_MAGIC 0x50563651 /* "Q6VP" little-endian */ +#define Q6VP_VERSION 1 + +/* + * A record opcode with this bit set is sent out-of-band: the payload is + * placed in an ADSP-mapped shared-memory region and the GPR command carries + * only an apm_cmd_header referencing it. Stock GSL sends every voice + * GRAPH_OPEN this way; it is mandatory because a voice graph must be opened + * atomically as one command (all subgraphs + cross-subgraph module + * connections and control links), and the whole payload (~4.3-4.7 KB) + * exceeds the ~4 KiB in-band GPR/GLINK intent limit. Splitting the open + * per-subgraph to fit in-band makes the cross-referencing TX DevicePP + * subgraph invalid (rejected at open) — see journal/calls.md 2026-07-08. + */ +#define Q6VP_OOB_FLAG 0x80000000 +#define Q6VP_GRAPH_ID 0xf000 /* private mem-map handle owner */ +#define Q6VP_BUF_SZ 0x40000 /* 256 KiB scratch region */ +#define Q6VP_SID_MASK 0xf + +struct q6vp_hdr { + __le32 magic; + __le32 version; + __le32 num_records; +} __packed; + +struct q6vp_rec { + __le32 opcode; + __le32 size; +} __packed; + +static struct q6apm *q6vp_apm; +static struct dentry *q6vp_dir; +static struct device *q6vp_dais_dev; +static struct audioreach_graph_info *q6vp_info; +static void *q6vp_buf; +static dma_addr_t q6vp_buf_iova; +static phys_addr_t q6vp_buf_dsp_addr; + +static int q6vp_send(uint32_t opcode, const void *payload, uint32_t size) +{ + struct gpr_pkt *pkt; + int ret; + + pkt = audioreach_alloc_apm_cmd_pkt(size, opcode, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + memcpy((void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE, payload, size); + + ret = q6apm_send_cmd_sync(q6vp_apm, pkt, 0); + kfree(pkt); + + return ret; +} + +/* + * Lazily set up the shared-memory region used for OOB commands: allocate a + * DMA buffer against the q6apm-dais child device (it carries the iommus + * mapping the ADSP expects, same as the PCM data path), register a private + * graph_info so the APM_CMD_RSP_SHARED_MEM_MAP_REGIONS callback has a slot + * to store the handle in, and map the region with the DSP. The mapping is + * kept until the module goes away. + */ +static int q6vp_oob_init(void) +{ + struct device *dev = q6vp_apm->dev; + struct of_phandle_args args; + struct platform_device *pdev; + struct device_node *np; + u64 sid = 0; + u32 graph_id = Q6VP_GRAPH_ID; + int ret; + + if (q6vp_info && q6vp_info->mem_map_handle) + return 0; + + if (!q6vp_dais_dev) { + np = of_get_compatible_child(dev->of_node, "qcom,q6apm-dais"); + if (!np) { + dev_err(dev, "voice-proto: no q6apm-dais node\n"); + return -ENODEV; + } + if (!of_parse_phandle_with_fixed_args(np, "iommus", 1, 0, + &args)) { + sid = args.args[0] & Q6VP_SID_MASK; + of_node_put(args.np); + } + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) { + dev_err(dev, "voice-proto: no q6apm-dais device\n"); + return -ENODEV; + } + q6vp_dais_dev = &pdev->dev; + + q6vp_buf = dma_alloc_coherent(q6vp_dais_dev, Q6VP_BUF_SZ, + &q6vp_buf_iova, GFP_KERNEL); + if (!q6vp_buf) + return -ENOMEM; + q6vp_buf_dsp_addr = q6vp_buf_iova | (sid << 32); + } + + if (!q6vp_info) { + q6vp_info = kzalloc(sizeof(*q6vp_info), GFP_KERNEL); + if (!q6vp_info) + return -ENOMEM; + INIT_LIST_HEAD(&q6vp_info->sg_list); + q6vp_info->id = Q6VP_GRAPH_ID; + ret = idr_alloc_u32(&q6vp_apm->graph_info_idr, q6vp_info, + &graph_id, graph_id, GFP_KERNEL); + if (ret < 0) { + kfree(q6vp_info); + q6vp_info = NULL; + return ret; + } + } + + ret = q6apm_map_memory_fixed_region(q6vp_dais_dev, Q6VP_GRAPH_ID, + q6vp_buf_dsp_addr, Q6VP_BUF_SZ); + if (ret) { + dev_err(dev, "voice-proto: mem map failed (%d)\n", ret); + return ret; + } + + dev_info(dev, "voice-proto: OOB region mapped, handle 0x%x\n", + q6vp_info->mem_map_handle); + return 0; +} + +static int q6vp_send_oob(uint32_t opcode, const void *payload, uint32_t size) +{ + struct apm_cmd_header *cmd_header; + struct gpr_pkt *pkt; + int ret; + + if (size > Q6VP_BUF_SZ) + return -EFBIG; + + ret = q6vp_oob_init(); + if (ret) + return ret; + + memcpy(q6vp_buf, payload, size); + + pkt = audioreach_alloc_apm_cmd_pkt(0, opcode, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + /* + * The region is mapped in absolute-address mode (mainline's + * q6apm_map_memory_fixed_region passes property_flag 0, unlike GSL + * which maps with IS_OFFSET_MODE and then references offset 0), so + * the header carries the full DSP-visible address of the buffer. + */ + cmd_header = (void *)pkt + GPR_HDR_SIZE; + cmd_header->payload_address_lsw = lower_32_bits(q6vp_buf_dsp_addr); + cmd_header->payload_address_msw = upper_32_bits(q6vp_buf_dsp_addr); + cmd_header->mem_map_handle = q6vp_info->mem_map_handle; + cmd_header->payload_size = size; + + ret = q6apm_send_cmd_sync(q6vp_apm, pkt, 0); + kfree(pkt); + + return ret; +} + +static int q6vp_play(const char *name) +{ + const struct firmware *fw; + const struct q6vp_hdr *hdr; + const struct q6vp_rec *rec; + struct device *dev = q6vp_apm->dev; + size_t off; + uint32_t i, num, opcode, size; + int ret, err = 0; + + ret = request_firmware(&fw, name, dev); + if (ret) { + dev_err(dev, "voice-proto: cannot load %s (%d)\n", name, ret); + return ret; + } + + hdr = (const struct q6vp_hdr *)fw->data; + if (fw->size < sizeof(*hdr) || + le32_to_cpu(hdr->magic) != Q6VP_MAGIC || + le32_to_cpu(hdr->version) != Q6VP_VERSION) { + dev_err(dev, "voice-proto: %s: bad header\n", name); + ret = -EINVAL; + goto out; + } + + num = le32_to_cpu(hdr->num_records); + off = sizeof(*hdr); + for (i = 0; i < num; i++) { + if (off + sizeof(*rec) > fw->size) { + ret = -EINVAL; + goto out; + } + rec = (const struct q6vp_rec *)(fw->data + off); + opcode = le32_to_cpu(rec->opcode); + size = le32_to_cpu(rec->size); + off += sizeof(*rec); + if (off + size > fw->size) { + ret = -EINVAL; + goto out; + } + + if (opcode & Q6VP_OOB_FLAG) + ret = q6vp_send_oob(opcode & ~Q6VP_OOB_FLAG, + fw->data + off, size); + else + ret = q6vp_send(opcode, fw->data + off, size); + dev_info(dev, "voice-proto: %s[%u] opcode 0x%08x size %u -> %d\n", + name, i, opcode & ~Q6VP_OOB_FLAG, size, ret); + /* keep going on DSP rejections; each result is logged */ + if (ret) + err = ret; + + off += ALIGN(size, 4); + } + +out: + release_firmware(fw); + return ret ? ret : err; +} + +static ssize_t q6vp_play_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char name[128]; + int ret; + + if (!q6vp_apm) + return -ENODEV; + if (count >= sizeof(name)) + return -EINVAL; + if (copy_from_user(name, ubuf, count)) + return -EFAULT; + name[count] = '\0'; + strim(name); + + ret = q6vp_play(name); + + return ret ? ret : count; +} + +static const struct file_operations q6vp_play_fops = { + .open = simple_open, + .write = q6vp_play_write, + .llseek = default_llseek, +}; + +void q6apm_voice_proto_init(struct q6apm *apm) +{ + q6vp_apm = apm; + q6vp_dir = debugfs_create_dir("q6apm-voice-proto", NULL); + debugfs_create_file("play", 0200, q6vp_dir, NULL, &q6vp_play_fops); +} + +void q6apm_voice_proto_exit(void) +{ + debugfs_remove_recursive(q6vp_dir); + q6vp_dir = NULL; + + if (q6vp_info) { + if (q6vp_info->mem_map_handle) + q6apm_unmap_memory_fixed_region(q6vp_dais_dev, + Q6VP_GRAPH_ID); + idr_remove(&q6vp_apm->graph_info_idr, Q6VP_GRAPH_ID); + kfree(q6vp_info); + q6vp_info = NULL; + } + if (q6vp_buf) { + dma_free_coherent(q6vp_dais_dev, Q6VP_BUF_SZ, q6vp_buf, + q6vp_buf_iova); + q6vp_buf = NULL; + } + q6vp_dais_dev = NULL; + q6vp_apm = NULL; +} diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 2ab378fb5032..cd7a9a1e0b9b 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -764,6 +764,8 @@ static int apm_probe(gpr_device_t *gdev) g_apm = apm; + q6apm_voice_proto_init(apm); + q6apm_get_apm_state(apm); ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0); @@ -781,6 +783,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + q6apm_voice_proto_exit(); of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 376a36700c53..736a2b53f303 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -157,4 +157,8 @@ int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id); int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir); + +/* FP6 voice-call bring-up prototype (q6apm-voice-proto.c) */ +void q6apm_voice_proto_init(struct q6apm *apm); +void q6apm_voice_proto_exit(void); #endif /* __APM_GRAPH_ */ diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h index a988a32086fe..bbbe6d368a41 100644 --- a/sound/soc/qcom/qdsp6/q6prm.h +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -3,6 +3,10 @@ #ifndef __Q6PRM_H__ #define __Q6PRM_H__ +#define LPAIF_MI2S_MCLK 1 +#define LPAIF_MI2S_BCLK 2 +#define LPAIF_MI2S_ECLK 3 + /* Clock ID for Primary I2S IBIT */ #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 /* Clock ID for Primary I2S EBIT */ diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index ce28bfac329a..9546d9233d9b 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -12,17 +12,78 @@ #include #include #include "qdsp6/q6afe.h" +#include "qdsp6/q6apm.h" +#include "qdsp6/q6prm.h" #include "common.h" #include "sdw.h" +#define I2S_MCLKFS 256 + +#define I2S_MCLK_RATE(rate) \ + ((rate) * (I2S_MCLKFS)) +#define I2S_BIT_RATE(rate, channels, format) \ + ((rate) * (channels) * (format)) + +static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("DP0 Jack", NULL), + SND_SOC_DAPM_SPK("DP1 Jack", NULL), + SND_SOC_DAPM_SPK("DP2 Jack", NULL), + SND_SOC_DAPM_SPK("DP3 Jack", NULL), + SND_SOC_DAPM_SPK("DP4 Jack", NULL), + SND_SOC_DAPM_SPK("DP5 Jack", NULL), + SND_SOC_DAPM_SPK("DP6 Jack", NULL), + SND_SOC_DAPM_SPK("DP7 Jack", NULL), +}; + +struct snd_soc_common { + const char *driver_name; + const struct snd_soc_dapm_widget *dapm_widgets; + int num_dapm_widgets; + const struct snd_soc_dapm_route *dapm_routes; + int num_dapm_routes; + const struct snd_kcontrol_new *controls; + int num_controls; + unsigned int codec_dai_fmt; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + bool wcd_jack; +}; + struct sc8280xp_snd_data { bool stream_prepared[AFE_PORT_MAX]; struct snd_soc_card *card; struct snd_soc_jack jack; struct snd_soc_jack dp_jack[8]; + struct snd_soc_common *snd_soc_common_priv; bool jack_setup; }; +static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + + switch (rate) { + case 11025: + case 44100: + case 88200: + return I2S_MCLK_RATE(44100); + default: + break; + } + + return I2S_MCLK_RATE(rate); +} + +static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) +{ + return I2S_BIT_RATE(params_rate(params), + params_channels(params), + snd_pcm_format_width(params_format(params))); +} + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) { struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); @@ -32,11 +93,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) int dp_pcm_id = 0; 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: case WSA_CODEC_DMA_RX_1: /* @@ -65,7 +121,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (data->snd_soc_common_priv->wcd_jack) + return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + + return 0; } static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -97,6 +156,63 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } +static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + int mclk_freq = sc8280xp_get_mclk_freq(params); + int bclk_freq = sc8280xp_get_bclk_freq(params); + int ret; + + 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: + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); + if (ret && ret != -ENOTSUPP) + return ret; + + if (data->snd_soc_common_priv->codec_dai_fmt) { + ret = snd_soc_dai_set_fmt(codec_dai, + data->snd_soc_common_priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + + if (data->snd_soc_common_priv->mi2s_mclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_MCLK, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (data->snd_soc_common_priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (data->snd_soc_common_priv->codec_sysclk_set) { + ret = snd_soc_dai_set_sysclk(codec_dai, + 0, mclk_freq, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + break; + default: + break; + } + + return 0; +} + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -118,6 +234,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) static const struct snd_soc_ops sc8280xp_be_ops = { .startup = qcom_snd_sdw_startup, .shutdown = qcom_snd_sdw_shutdown, + .hw_params = sc8280xp_snd_hw_params, .hw_free = sc8280xp_snd_hw_free, .prepare = sc8280xp_snd_prepare, }; @@ -128,7 +245,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card) int i; for_each_card_prelinks(card, i, link) { - if (link->no_pcm == 1) { + if (link->no_pcm == 1 || link->num_codecs > 0) { link->init = sc8280xp_snd_init; link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup; link->ops = &sc8280xp_be_ops; @@ -146,38 +263,133 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; - card->owner = THIS_MODULE; + /* Allocate the private data */ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; + data->snd_soc_common_priv = (struct snd_soc_common *)of_device_get_match_data(dev); + if (!data->snd_soc_common_priv) + return -ENODEV; + + card->owner = THIS_MODULE; card->dev = dev; dev_set_drvdata(dev, card); snd_soc_card_set_drvdata(card, data); + card->dapm_widgets = data->snd_soc_common_priv->dapm_widgets; + card->num_dapm_widgets = data->snd_soc_common_priv->num_dapm_widgets; + card->dapm_routes = data->snd_soc_common_priv->dapm_routes; + card->num_dapm_routes = data->snd_soc_common_priv->num_dapm_routes; + card->controls = data->snd_soc_common_priv->controls; + card->num_controls = data->snd_soc_common_priv->num_controls; + ret = qcom_snd_parse_of(card); if (ret) return ret; - card->driver_name = of_device_get_match_data(dev); + card->driver_name = data->snd_soc_common_priv->driver_name; sc8280xp_add_be_ops(card); return devm_snd_soc_register_card(dev, card); } +static struct snd_soc_common kaanapali_priv_data = { + .driver_name = "kaanapali", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common milos_priv_data = { + .driver_name = "milos", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .mi2s_bclk_enable = true, + .wcd_jack = true, +}; + +static struct snd_soc_common qcs9100_priv_data = { + .driver_name = "sa8775p", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), +}; + +static struct snd_soc_common qcs615_priv_data = { + .driver_name = "qcs615", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .mi2s_mclk_enable = true, +}; + +static struct snd_soc_common qcm6490_priv_data = { + .driver_name = "qcm6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common qcs6490_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common qcs8275_priv_data = { + .driver_name = "qcs8300", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), +}; + +static struct snd_soc_common sc8280xp_priv_data = { + .driver_name = "sc8280xp", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common sm8450_priv_data = { + .driver_name = "sm8450", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common sm8550_priv_data = { + .driver_name = "sm8550", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common sm8650_priv_data = { + .driver_name = "sm8650", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static struct snd_soc_common sm8750_priv_data = { + .driver_name = "sm8750", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + static const struct of_device_id snd_sc8280xp_dt_match[] = { - {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, - {.compatible = "qcom,milos-sndcard", "milos"}, - {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, - {.compatible = "qcom,qcs615-sndcard", "qcs615"}, - {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, - {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, - {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, - {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, - {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, - {.compatible = "qcom,sm8450-sndcard", "sm8450"}, - {.compatible = "qcom,sm8550-sndcard", "sm8550"}, - {.compatible = "qcom,sm8650-sndcard", "sm8650"}, - {.compatible = "qcom,sm8750-sndcard", "sm8750"}, + {.compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data}, + {.compatible = "qcom,milos-sndcard", .data = &milos_priv_data}, + {.compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data}, + {.compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data}, + {.compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data}, + {.compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data}, + {.compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data}, + {.compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data}, + {.compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data}, + {.compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data}, + {.compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data}, + {.compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data}, + {.compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data}, {} };