Compare commits
No commits in common. "combined-stable" and "audio" have entirely different histories.
combined-s
...
audio
16 changed files with 19 additions and 3269 deletions
|
|
@ -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";
|
||||
|
||||
|
|
@ -237,34 +209,13 @@
|
|||
sound {
|
||||
compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard";
|
||||
model = "Fairphone (Gen. 6)";
|
||||
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";
|
||||
// audio-routing = ...
|
||||
|
||||
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";
|
||||
|
||||
|
|
@ -282,29 +233,6 @@
|
|||
};
|
||||
};
|
||||
|
||||
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 {
|
||||
pm8008-thermal {
|
||||
polling-delay-passive = <100>;
|
||||
|
|
@ -770,28 +698,9 @@
|
|||
};
|
||||
|
||||
&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 {
|
||||
|
|
@ -1175,56 +1084,11 @@
|
|||
};
|
||||
};
|
||||
|
||||
&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";
|
||||
|
|
@ -1239,13 +1103,6 @@
|
|||
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";
|
||||
|
|
@ -1313,17 +1170,6 @@
|
|||
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";
|
||||
|
|
@ -1365,13 +1211,6 @@
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <jorijnvdgraaf@catcrafts.net>
|
||||
*
|
||||
* Datasheet available at
|
||||
* <https://qstcorp.com/upload/pdf/202202/13-52-15%20QMC6308%20Datasheet%20Rev.%20F(1).pdf>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#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 <jorijnvdgraaf@catcrafts.net>");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
|
|
@ -122,47 +122,11 @@ 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;
|
||||
|
|
@ -188,14 +152,13 @@ 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, enum s3fwrn5_variant variant)
|
||||
const struct s3fwrn5_phy_ops *phy_ops)
|
||||
{
|
||||
struct s3fwrn5_info *info;
|
||||
int ret;
|
||||
|
|
@ -207,7 +170,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -23,76 +23,9 @@ 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;
|
||||
|
|
@ -213,7 +146,6 @@ out:
|
|||
|
||||
static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
enum s3fwrn5_variant variant;
|
||||
struct s3fwrn5_i2c_phy *phy;
|
||||
int ret;
|
||||
|
||||
|
|
@ -240,61 +172,15 @@ 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.
|
||||
*
|
||||
* 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.
|
||||
* Make sure the clock is running before starting S3FWRN5.
|
||||
*/
|
||||
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);
|
||||
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");
|
||||
|
||||
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, variant);
|
||||
&i2c_phy_ops);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -319,17 +205,13 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
|
|||
}
|
||||
|
||||
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
|
||||
{ .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL },
|
||||
{ .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
|
||||
{ }
|
||||
{ S3FWRN5_I2C_DRIVER_NAME },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
|
||||
|
||||
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, },
|
||||
static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
|
||||
{ .compatible = "samsung,s3fwrn5-i2c", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
||||
|
|
@ -337,7 +219,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_s3fwrn5_i2c_match,
|
||||
.of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
|
||||
},
|
||||
.probe = s3fwrn5_i2c_probe,
|
||||
.remove = s3fwrn5_i2c_remove,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include "s3fwrn5.h"
|
||||
#include "nci.h"
|
||||
|
|
@ -23,7 +20,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[5] = {
|
||||
const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
NCI_PROP_SET_RFREG),
|
||||
|
|
@ -44,11 +41,6 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
|
|||
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
|
||||
|
|
@ -125,112 +117,3 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,6 @@ 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;
|
||||
|
|
@ -57,30 +50,7 @@ struct nci_prop_fw_cfg_rsp {
|
|||
__u8 status;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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];
|
||||
extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
|
||||
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_ */
|
||||
|
|
|
|||
|
|
@ -21,17 +21,6 @@ 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);
|
||||
|
|
@ -47,7 +36,6 @@ struct s3fwrn5_info {
|
|||
const struct s3fwrn5_phy_ops *phy_ops;
|
||||
|
||||
struct s3fwrn5_fw_info fw_info;
|
||||
enum s3fwrn5_variant variant;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
|
@ -90,7 +78,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, enum s3fwrn5_variant variant);
|
||||
const struct s3fwrn5_phy_ops *phy_ops);
|
||||
void s3fwrn5_remove(struct nci_dev *ndev);
|
||||
|
||||
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
||||
|
|
|
|||
|
|
@ -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, S3FWRN5_VARIANT_FWDL);
|
||||
&uart_phy_ops);
|
||||
if (ret < 0)
|
||||
goto err_serdev;
|
||||
|
||||
|
|
|
|||
|
|
@ -976,20 +976,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -301,7 +301,6 @@ 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
|
||||
|
|
@ -2432,24 +2431,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -356,8 +356,6 @@ 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
|
||||
|
|
@ -798,11 +796,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,495 +0,0 @@
|
|||
// 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 <linux/component.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_registers.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#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");
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,246 +0,0 @@
|
|||
/* 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 <linux/regmap.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
/* 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__ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue