Compare commits
21 commits
v7.1.2-mil
...
combined-s
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e9adc633e | |||
| cf98406408 | |||
| 50cd4e823f | |||
| 066348d67b | |||
| 08f135ee41 | |||
| 82284cc84a | |||
| 82c43dd3f1 | |||
| 2931c83762 | |||
| 5694c4a5f3 | |||
| d4c5ebbeee | |||
| 52c785c315 | |||
| d571dd42d3 | |||
| 79c7597fc5 | |||
| 692c0cd1a4 | |||
| 9cd00a65e8 | |||
| d837afcfd6 | |||
| e9a6b531cf | |||
| 9df8d66027 | |||
| 5c9c899ba9 | |||
| 2fd346efe3 | |||
| 8ed47c2b0a |
20 changed files with 3792 additions and 311 deletions
|
|
@ -27,6 +27,34 @@
|
|||
serial1 = &uart11;
|
||||
};
|
||||
|
||||
/*
|
||||
* The sensor I2C bus sits on TLMM eGPIO pads that have no AP QUP
|
||||
* function; on the stock OS it is driven by an island QUP of the
|
||||
* Snapdragon Sensor Core (ADSP). From the AP the pads are only
|
||||
* usable as software GPIOs, so bit-bang the bus.
|
||||
* Devices on the bus: QMC6308 magnetometer @ 0x2c, SPL07
|
||||
* barometer @ 0x76, STK3BCx ALS/proximity @ 0x48.
|
||||
*/
|
||||
i2c-sensors {
|
||||
compatible = "i2c-gpio";
|
||||
sda-gpios = <&tlmm 153 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
scl-gpios = <&tlmm 154 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
i2c-gpio,delay-us = <2>;
|
||||
pinctrl-0 = <&sensor_i2c_default>;
|
||||
pinctrl-names = "default";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
magnetometer@2c {
|
||||
compatible = "qstcorp,qmc6308";
|
||||
reg = <0x2c>;
|
||||
vdd-supply = <&vreg_l10b>;
|
||||
mount-matrix = "0", "-1", "0",
|
||||
"1", "0", "0",
|
||||
"0", "0", "1";
|
||||
};
|
||||
};
|
||||
|
||||
gpio-keys {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
|
|
@ -209,14 +237,34 @@
|
|||
sound {
|
||||
compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard";
|
||||
model = "Fairphone (Gen. 6)";
|
||||
// audio-routing = ...
|
||||
audio-routing = "AMIC1", "MIC BIAS1",
|
||||
"AMIC2", "MIC BIAS2",
|
||||
"AMIC3", "MIC BIAS3",
|
||||
"TX SWR_INPUT0", "ADC1_OUTPUT",
|
||||
"TX SWR_INPUT1", "ADC2_OUTPUT",
|
||||
"TX SWR_INPUT2", "ADC3_OUTPUT";
|
||||
|
||||
pinctrl-0 = <&lpi_i2s2_active>;
|
||||
pinctrl-1 = <&lpi_i2s2_sleep>;
|
||||
pinctrl-names = "default",
|
||||
"sleep";
|
||||
|
||||
/*
|
||||
wcd-capture-dai-link {
|
||||
link-name = "WCD Capture";
|
||||
|
||||
codec {
|
||||
sound-dai = <&wcd9378 1>, <&swr2 0>, <&lpass_txmacro 0>;
|
||||
};
|
||||
|
||||
cpu {
|
||||
sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>;
|
||||
};
|
||||
|
||||
platform {
|
||||
sound-dai = <&q6apm>;
|
||||
};
|
||||
};
|
||||
|
||||
i2s-dai-link {
|
||||
link-name = "Senary MI2S Playback";
|
||||
|
||||
|
|
@ -232,7 +280,29 @@
|
|||
sound-dai = <&q6apm>;
|
||||
};
|
||||
};
|
||||
*/
|
||||
};
|
||||
|
||||
wcd9378: audio-codec {
|
||||
compatible = "qcom,wcd9378-codec";
|
||||
|
||||
pinctrl-0 = <&wcd_reset_n_active>;
|
||||
pinctrl-names = "default";
|
||||
|
||||
reset-gpios = <&tlmm 162 GPIO_ACTIVE_LOW>;
|
||||
|
||||
vdd-buck-supply = <&vreg_l8b>;
|
||||
vdd-rxtx-supply = <&vreg_l7b>;
|
||||
vdd-io-supply = <&vreg_l7b>;
|
||||
vdd-mic-bias-supply = <&vreg_bob>;
|
||||
|
||||
qcom,micbias1-microvolt = <1800000>;
|
||||
qcom,micbias2-microvolt = <1800000>;
|
||||
qcom,micbias3-microvolt = <1800000>;
|
||||
|
||||
qcom,rx-device = <&wcd9378_rx>;
|
||||
qcom,tx-device = <&wcd9378_tx>;
|
||||
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
||||
|
||||
thermal-zones {
|
||||
|
|
@ -700,9 +770,28 @@
|
|||
};
|
||||
|
||||
&i2c1 {
|
||||
/* Samsung NFC @ 0x27 */
|
||||
|
||||
status = "okay";
|
||||
|
||||
/*
|
||||
* Samsung S3NRN4V NFC controller. XI is driven by the RF_CLK2 PMIC
|
||||
* buffer; the chip has no oscillator of its own and gates the clock
|
||||
* via its CLK_REQ line, so the clock must be voted on in response
|
||||
* to it.
|
||||
*/
|
||||
nfc@27 {
|
||||
compatible = "samsung,s3nrn4v";
|
||||
reg = <0x27>;
|
||||
|
||||
clk-req-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
|
||||
clocks = <&rpmhcc RPMH_RF_CLK2>;
|
||||
en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
|
||||
interrupt-parent = <&tlmm>;
|
||||
interrupts = <31 IRQ_TYPE_EDGE_RISING>;
|
||||
pinctrl-0 = <&nfc_clk_req_default>, <&nfc_irq_default>,
|
||||
<&nfc_pd_default>;
|
||||
pinctrl-names = "default";
|
||||
wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c3 {
|
||||
|
|
@ -1086,11 +1175,56 @@
|
|||
};
|
||||
};
|
||||
|
||||
&swr1 {
|
||||
status = "okay";
|
||||
|
||||
/* WCD9378 RX */
|
||||
wcd9378_rx: codec@0,4 {
|
||||
compatible = "sdw20217011000";
|
||||
reg = <0 4>;
|
||||
qcom,rx-port-mapping = <1 2 3 4 5>;
|
||||
};
|
||||
};
|
||||
|
||||
&swr2 {
|
||||
status = "okay";
|
||||
|
||||
/* WCD9378 TX */
|
||||
wcd9378_tx: codec@0,3 {
|
||||
compatible = "sdw20217011000";
|
||||
reg = <0 3>;
|
||||
|
||||
/*
|
||||
* WCD9378 TX port 1 (ADC1) <=> SWR2 port 1 (SWRM_TX1)
|
||||
* WCD9378 TX port 2 (ADC2) <=> SWR2 port 1
|
||||
* WCD9378 TX port 3 (ADC3) <=> SWR2 port 1
|
||||
* WCD9378 TX port 4 (DMIC0,1, MBHC) <=> SWR2 port 2
|
||||
* WCD9378 TX port 5 (DMIC2..5) <=> SWR2 port 3
|
||||
*/
|
||||
qcom,tx-port-mapping = <1 1 1 2 3>;
|
||||
};
|
||||
};
|
||||
|
||||
&tlmm {
|
||||
gpio-reserved-ranges = <8 4>, /* Fingerprint SPI */
|
||||
<13 1>, /* NC */
|
||||
<63 2>; /* WLAN UART */
|
||||
|
||||
nfc_clk_req_default: nfc-clk-req-default-state {
|
||||
pins = "gpio6";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-disable;
|
||||
};
|
||||
|
||||
sensor_i2c_default: sensor-i2c-default-state {
|
||||
/* SDA, SCL; external pull-ups to vreg_l10b */
|
||||
pins = "gpio153", "gpio154";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-disable;
|
||||
};
|
||||
|
||||
ts_active: ts-irq-active-state {
|
||||
pins = "gpio19";
|
||||
function = "gpio";
|
||||
|
|
@ -1105,6 +1239,13 @@
|
|||
bias-disable;
|
||||
};
|
||||
|
||||
nfc_irq_default: nfc-irq-default-state {
|
||||
pins = "gpio31";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-disable;
|
||||
};
|
||||
|
||||
qup_uart11_sleep_cts: qup-uart11-sleep-cts-state {
|
||||
pins = "gpio48";
|
||||
function = "gpio";
|
||||
|
|
@ -1172,6 +1313,17 @@
|
|||
bias-pull-down;
|
||||
};
|
||||
|
||||
/*
|
||||
* Pulled up so the NFC chip stays powered down while the line is
|
||||
* not driven (it is the chip's active-high power-down input).
|
||||
*/
|
||||
nfc_pd_default: nfc-pd-default-state {
|
||||
pins = "gpio56";
|
||||
function = "gpio";
|
||||
drive-strength = <2>;
|
||||
bias-pull-up;
|
||||
};
|
||||
|
||||
sdc2_card_det_n: sdc2-card-det-state {
|
||||
pins = "gpio65";
|
||||
function = "gpio";
|
||||
|
|
@ -1213,6 +1365,13 @@
|
|||
drive-strength = <2>;
|
||||
bias-pull-down;
|
||||
};
|
||||
|
||||
wcd_reset_n_active: wcd-reset-n-active-state {
|
||||
pins = "gpio162";
|
||||
function = "gpio";
|
||||
drive-strength = <16>;
|
||||
output-high;
|
||||
};
|
||||
};
|
||||
|
||||
&uart5 {
|
||||
|
|
|
|||
|
|
@ -198,6 +198,17 @@ config INFINEON_TLV493D
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called tlv493d.
|
||||
|
||||
config QMC6308
|
||||
tristate "QST QMC6308 3-Axis Magnetic Sensor"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y here to add support for the QST QMC6308 3-Axis
|
||||
Magnetic Sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called qmc6308.
|
||||
|
||||
config SENSORS_HMC5843
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o
|
|||
|
||||
obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o
|
||||
|
||||
obj-$(CONFIG_QMC6308) += qmc6308.o
|
||||
|
||||
obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o
|
||||
obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o
|
||||
obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o
|
||||
|
|
|
|||
590
drivers/iio/magnetometer/qmc6308.c
Normal file
590
drivers/iio/magnetometer/qmc6308.c
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
|
||||
/*
|
||||
* Support for QST QMC6308 3-Axis Magnetic Sensor on I2C bus.
|
||||
*
|
||||
* Copyright (C) 2026 Jorijn van der Graaf <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,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int s3fwrn5_nci_setup(struct nci_dev *ndev)
|
||||
{
|
||||
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||
|
||||
/*
|
||||
* Runs after CORE_RESET, before CORE_INIT. The S3NRN4V needs its
|
||||
* reference clock configured here (the downstream stack does it in the
|
||||
* bootloader, before CORE_RESET, but this is the earliest hook the NCI
|
||||
* core offers and the chip accepts it).
|
||||
*/
|
||||
if (info->variant == S3FWRN5_VARIANT_S3NRN4V)
|
||||
return s3fwrn5_nci_clk_cfg(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
|
||||
{
|
||||
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||
int ret;
|
||||
|
||||
if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
|
||||
/*
|
||||
* The S3NRN4V ships with working firmware behind a bootloader
|
||||
* protocol this driver does not implement, so there is no
|
||||
* download step; the NCI core has already done CORE_RESET +
|
||||
* CORE_INIT. Just (re)load the RF registers via DUAL_OPTION.
|
||||
*/
|
||||
ret = s3fwrn5_nci_rf_configure_dual(info, "sec_s3nrn4v_hwreg.bin",
|
||||
"sec_s3nrn4v_swreg.bin");
|
||||
/*
|
||||
* Keep going even if the blobs could not be loaded: the chip
|
||||
* still enumerates and falls back to the RF registers programmed
|
||||
* in its flash, so NFC may work anyway.
|
||||
*/
|
||||
if (ret < 0)
|
||||
dev_warn(&ndev->nfc_dev->dev,
|
||||
"rfreg configure failed (%d)\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (s3fwrn5_firmware_init(info)) {
|
||||
//skip bootloader mode
|
||||
return 0;
|
||||
|
|
@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = {
|
|||
.open = s3fwrn5_nci_open,
|
||||
.close = s3fwrn5_nci_close,
|
||||
.send = s3fwrn5_nci_send,
|
||||
.setup = s3fwrn5_nci_setup,
|
||||
.post_setup = s3fwrn5_nci_post_setup,
|
||||
.prop_ops = s3fwrn5_nci_prop_ops,
|
||||
.n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops),
|
||||
};
|
||||
|
||||
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||
const struct s3fwrn5_phy_ops *phy_ops)
|
||||
const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
|
||||
{
|
||||
struct s3fwrn5_info *info;
|
||||
int ret;
|
||||
|
|
@ -170,6 +207,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
|||
info->phy_id = phy_id;
|
||||
info->pdev = pdev;
|
||||
info->phy_ops = phy_ops;
|
||||
info->variant = variant;
|
||||
mutex_init(&info->mutex);
|
||||
|
||||
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||
|
|
|
|||
|
|
@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy {
|
|||
struct i2c_client *i2c_dev;
|
||||
struct clk *clk;
|
||||
|
||||
/*
|
||||
* Optional hardware clock-request handshake. When a CLK_REQ GPIO is
|
||||
* wired, the chip drives it high while it needs its XI clock -- notably
|
||||
* to generate the poll/reader carrier -- and the clock is gated on it
|
||||
* instead of being left always-on (which never lets the chip's TX PLL
|
||||
* lock on a fresh clock start, leaving it unable to poll).
|
||||
*/
|
||||
struct gpio_desc *gpio_clk_req;
|
||||
bool clk_on;
|
||||
struct mutex clk_lock; /* serialises clk_on against the CLK_REQ irq */
|
||||
|
||||
unsigned int irq_skip:1;
|
||||
};
|
||||
|
||||
static void s3fwrn5_i2c_clk_set_locked(struct s3fwrn5_i2c_phy *phy, bool on)
|
||||
{
|
||||
lockdep_assert_held(&phy->clk_lock);
|
||||
|
||||
if (on && !phy->clk_on) {
|
||||
int ret = clk_prepare_enable(phy->clk);
|
||||
|
||||
if (ret == 0)
|
||||
phy->clk_on = true;
|
||||
else
|
||||
dev_warn_once(&phy->i2c_dev->dev,
|
||||
"failed to enable clock (%d); NFC may not poll\n",
|
||||
ret);
|
||||
} else if (!on && phy->clk_on) {
|
||||
clk_disable_unprepare(phy->clk);
|
||||
phy->clk_on = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the current CLK_REQ level. Reading the GPIO under clk_lock makes
|
||||
* concurrent callers (the CLK_REQ irq thread and the probe-time seeding)
|
||||
* safe: whoever runs last applies a level read after the earlier update,
|
||||
* never a stale one.
|
||||
*/
|
||||
static void s3fwrn5_i2c_clk_sync(struct s3fwrn5_i2c_phy *phy)
|
||||
{
|
||||
int level;
|
||||
|
||||
mutex_lock(&phy->clk_lock);
|
||||
level = gpiod_get_value_cansleep(phy->gpio_clk_req);
|
||||
if (level >= 0)
|
||||
s3fwrn5_i2c_clk_set_locked(phy, level > 0);
|
||||
else
|
||||
dev_warn_once(&phy->i2c_dev->dev,
|
||||
"failed to read CLK_REQ (%d); keeping clock state\n",
|
||||
level);
|
||||
mutex_unlock(&phy->clk_lock);
|
||||
}
|
||||
|
||||
static void s3fwrn5_i2c_clk_disable_action(void *data)
|
||||
{
|
||||
struct s3fwrn5_i2c_phy *phy = data;
|
||||
|
||||
mutex_lock(&phy->clk_lock);
|
||||
s3fwrn5_i2c_clk_set_locked(phy, false);
|
||||
mutex_unlock(&phy->clk_lock);
|
||||
}
|
||||
|
||||
static irqreturn_t s3fwrn5_i2c_clk_req_thread(int irq, void *phy_id)
|
||||
{
|
||||
s3fwrn5_i2c_clk_sync(phy_id);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
|
||||
{
|
||||
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||
|
|
@ -146,6 +213,7 @@ out:
|
|||
|
||||
static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
enum s3fwrn5_variant variant;
|
||||
struct s3fwrn5_i2c_phy *phy;
|
||||
int ret;
|
||||
|
||||
|
|
@ -172,15 +240,61 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
|||
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.
|
||||
* Depending on the hardware configuration this could be an always-on
|
||||
* oscillator or some external clock that must be explicitly enabled.
|
||||
* Make sure the clock is running before starting S3FWRN5.
|
||||
*
|
||||
* If a CLK_REQ GPIO is wired, the chip gates the clock itself (driving
|
||||
* CLK_REQ high when it needs XI); service that handshake. Otherwise just
|
||||
* make sure the clock is running before starting S3FWRN5.
|
||||
*/
|
||||
phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
mutex_init(&phy->clk_lock);
|
||||
phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(phy->gpio_clk_req))
|
||||
return PTR_ERR(phy->gpio_clk_req);
|
||||
|
||||
if (phy->gpio_clk_req) {
|
||||
int clk_req_irq;
|
||||
|
||||
phy->clk = devm_clk_get_optional(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
|
||||
/*
|
||||
* Unlike the always-on branch below, this clock is enabled by
|
||||
* hand from the CLK_REQ handler, so devm will not disable it on
|
||||
* unbind. Gate it off explicitly if it is still on at teardown.
|
||||
*/
|
||||
ret = devm_add_action_or_reset(&client->dev,
|
||||
s3fwrn5_i2c_clk_disable_action,
|
||||
phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_req_irq = gpiod_to_irq(phy->gpio_clk_req);
|
||||
if (clk_req_irq < 0)
|
||||
return clk_req_irq;
|
||||
|
||||
ret = devm_request_threaded_irq(&client->dev, clk_req_irq, NULL,
|
||||
s3fwrn5_i2c_clk_req_thread,
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_ONESHOT,
|
||||
"s3fwrn5_clk_req", phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Seed the clock state from the current CLK_REQ level. */
|
||||
s3fwrn5_i2c_clk_sync(phy);
|
||||
} else {
|
||||
phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
|
||||
if (IS_ERR(phy->clk))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
||||
"failed to get clock\n");
|
||||
}
|
||||
|
||||
variant = (uintptr_t)i2c_get_match_data(client);
|
||||
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
|
||||
&i2c_phy_ops);
|
||||
&i2c_phy_ops, variant);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -205,13 +319,17 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
|
|||
}
|
||||
|
||||
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
|
||||
{ S3FWRN5_I2C_DRIVER_NAME },
|
||||
{}
|
||||
{ .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL },
|
||||
{ .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
|
||||
|
||||
static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
|
||||
{ .compatible = "samsung,s3fwrn5-i2c", },
|
||||
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
|
||||
{ .compatible = "samsung,s3fwrn5-i2c",
|
||||
.data = (void *)S3FWRN5_VARIANT_FWDL, },
|
||||
{ .compatible = "samsung,s3nrn4v",
|
||||
.data = (void *)S3FWRN5_VARIANT_S3NRN4V, },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
||||
|
|
@ -219,7 +337,7 @@ MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
|
|||
static struct i2c_driver s3fwrn5_i2c_driver = {
|
||||
.driver = {
|
||||
.name = S3FWRN5_I2C_DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
|
||||
.of_match_table = of_s3fwrn5_i2c_match,
|
||||
},
|
||||
.probe = s3fwrn5_i2c_probe,
|
||||
.remove = s3fwrn5_i2c_remove,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
#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"
|
||||
|
|
@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
||||
const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
NCI_PROP_SET_RFREG),
|
||||
|
|
@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
|||
NCI_PROP_FW_CFG),
|
||||
.rsp = s3fwrn5_nci_prop_rsp,
|
||||
},
|
||||
{
|
||||
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||
NCI_PROP_DUAL_OPTION),
|
||||
.rsp = s3fwrn5_nci_prop_rsp,
|
||||
},
|
||||
};
|
||||
|
||||
#define S3FWRN5_RFREG_SECTION_SIZE 252
|
||||
|
|
@ -117,3 +125,112 @@ out:
|
|||
release_firmware(fw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure the reference clock. The S3NRN4V expects the single-byte FW_CFG
|
||||
* form (just the clock-speed selector). The downstream stack sends this in the
|
||||
* bootloader before CORE_RESET; the earliest the mainline NCI core lets us in
|
||||
* is the ->setup hook (after CORE_RESET, before CORE_INIT), which works.
|
||||
*/
|
||||
int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
|
||||
{
|
||||
u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
|
||||
|
||||
return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
|
||||
}
|
||||
|
||||
/*
|
||||
* S3NRN4V RF register update. The HW and SW register blobs are merged into a
|
||||
* single stream (HW first) and pushed via the DUAL_OPTION command:
|
||||
* START_UPDATE, one SET_OPTION per 252-byte section, then STOP_UPDATE carrying
|
||||
* a 16-bit checksum (running sum of the merged stream as 32-bit words).
|
||||
*/
|
||||
int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
|
||||
const char *hw_name, const char *sw_name)
|
||||
{
|
||||
const struct firmware *hw_fw = NULL, *sw_fw = NULL;
|
||||
struct nci_prop_dual_set_option_cmd set_option;
|
||||
struct device *dev = &info->ndev->nfc_dev->dev;
|
||||
size_t merged_size, i, len;
|
||||
u8 *merged = NULL;
|
||||
u8 stop_cmd[3];
|
||||
u32 checksum;
|
||||
u8 sub_oid;
|
||||
int ret;
|
||||
|
||||
ret = request_firmware(&hw_fw, hw_name, dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = request_firmware(&sw_fw, sw_name, dev);
|
||||
if (ret < 0)
|
||||
goto out_hw;
|
||||
|
||||
merged_size = hw_fw->size + sw_fw->size;
|
||||
|
||||
/*
|
||||
* The stream is checksummed as 32-bit words and pushed in at most 256
|
||||
* sections (the section index is a single byte); reject blobs that
|
||||
* would silently break either.
|
||||
*/
|
||||
if (merged_size % 4 ||
|
||||
merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
|
||||
dev_err(dev, "invalid rfreg blob size (%zu)\n", merged_size);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
merged = kmalloc(merged_size, GFP_KERNEL);
|
||||
if (!merged) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
memcpy(merged, hw_fw->data, hw_fw->size);
|
||||
memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
|
||||
|
||||
/* Running sum of the merged stream as little-endian 32-bit words. */
|
||||
checksum = 0;
|
||||
for (i = 0; i + 4 <= merged_size; i += 4)
|
||||
checksum += get_unaligned_le32(merged + i);
|
||||
|
||||
dev_dbg(dev, "rfreg dual-option update: %s + %s\n", hw_name, sw_name);
|
||||
|
||||
/* START_UPDATE */
|
||||
sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Unable to start rfreg update\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* SET_OPTION per section */
|
||||
set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
|
||||
set_option.index = 0;
|
||||
for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
|
||||
len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
|
||||
memcpy(set_option.data, merged + i, len);
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
|
||||
len + 2, (__u8 *)&set_option);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "rfreg update error (code=%d)\n", ret);
|
||||
goto out;
|
||||
}
|
||||
set_option.index++;
|
||||
}
|
||||
|
||||
/* STOP_UPDATE with checksum */
|
||||
stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
|
||||
put_unaligned_le16(checksum, &stop_cmd[1]);
|
||||
ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Unable to stop rfreg update\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "rfreg dual-option update: success\n");
|
||||
out:
|
||||
kfree(merged);
|
||||
release_firmware(sw_fw);
|
||||
out_hw:
|
||||
release_firmware(hw_fw);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ struct nci_prop_stop_rfreg_rsp {
|
|||
|
||||
#define NCI_PROP_FW_CFG 0x28
|
||||
|
||||
/*
|
||||
* Single-byte FW_CFG payload (clock-speed selector) for the S3NRN4V reference
|
||||
* clock. Taken from the vendor configuration for this part (the encoding is
|
||||
* not documented).
|
||||
*/
|
||||
#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
|
||||
|
||||
struct nci_prop_fw_cfg_cmd {
|
||||
__u8 clk_type;
|
||||
__u8 clk_speed;
|
||||
|
|
@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp {
|
|||
__u8 status;
|
||||
};
|
||||
|
||||
extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
|
||||
/*
|
||||
* The S3NRN4V updates its RF registers through a single "dual option" command
|
||||
* (a sub-OID selects the operation) instead of the START/SET/STOP_RFREG
|
||||
* opcodes above, and expects the HW and SW register blobs merged into one
|
||||
* stream.
|
||||
*/
|
||||
#define NCI_PROP_DUAL_OPTION 0x2a
|
||||
|
||||
#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
|
||||
#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
|
||||
#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
|
||||
|
||||
#define NCI_PROP_DUAL_SECTION_SIZE 252
|
||||
|
||||
struct nci_prop_dual_set_option_cmd {
|
||||
__u8 sub_oid; /* NCI_PROP_DUAL_SUB_SET_OPTION */
|
||||
__u8 index;
|
||||
__u8 data[NCI_PROP_DUAL_SECTION_SIZE];
|
||||
};
|
||||
|
||||
extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
|
||||
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
|
||||
int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
|
||||
const char *hw_name, const char *sw_name);
|
||||
int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
|
||||
|
||||
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,17 @@ enum s3fwrn5_mode {
|
|||
S3FWRN5_MODE_FW,
|
||||
};
|
||||
|
||||
enum s3fwrn5_variant {
|
||||
/* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
|
||||
S3FWRN5_VARIANT_FWDL,
|
||||
/*
|
||||
* S3NRN4V: ships with working firmware behind a bootloader protocol
|
||||
* this driver does not implement; skip the download, configure the
|
||||
* clock (FW_CFG) and update the RF registers via the DUAL_OPTION cmd.
|
||||
*/
|
||||
S3FWRN5_VARIANT_S3NRN4V,
|
||||
};
|
||||
|
||||
struct s3fwrn5_phy_ops {
|
||||
void (*set_wake)(void *id, bool sleep);
|
||||
void (*set_mode)(void *id, enum s3fwrn5_mode);
|
||||
|
|
@ -36,6 +47,7 @@ struct s3fwrn5_info {
|
|||
const struct s3fwrn5_phy_ops *phy_ops;
|
||||
|
||||
struct s3fwrn5_fw_info fw_info;
|
||||
enum s3fwrn5_variant variant;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
|
@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
|
|||
}
|
||||
|
||||
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
|
||||
const struct s3fwrn5_phy_ops *phy_ops);
|
||||
const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
|
||||
void s3fwrn5_remove(struct nci_dev *ndev);
|
||||
|
||||
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
|
|||
}
|
||||
|
||||
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
|
||||
&uart_phy_ops);
|
||||
&uart_phy_ops, S3FWRN5_VARIANT_FWDL);
|
||||
if (ret < 0)
|
||||
goto err_serdev;
|
||||
|
||||
|
|
|
|||
|
|
@ -976,6 +976,20 @@ static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus,
|
|||
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
|
||||
int ret, i, len;
|
||||
|
||||
if (msg->page) {
|
||||
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page1,
|
||||
msg->dev_num,
|
||||
SDW_SCP_ADDRPAGE1);
|
||||
if (ret)
|
||||
return SDW_CMD_IGNORED;
|
||||
|
||||
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page2,
|
||||
msg->dev_num,
|
||||
SDW_SCP_ADDRPAGE2);
|
||||
if (ret)
|
||||
return SDW_CMD_IGNORED;
|
||||
}
|
||||
|
||||
if (msg->flags == SDW_MSG_FLAG_READ) {
|
||||
for (i = 0; i < msg->len;) {
|
||||
len = min(msg->len - i, QCOM_SWRM_MAX_RD_LEN);
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ config SND_SOC_ALL_CODECS
|
|||
imply SND_SOC_WCD9335
|
||||
imply SND_SOC_WCD934X
|
||||
imply SND_SOC_WCD937X_SDW
|
||||
imply SND_SOC_WCD9378_SDW
|
||||
imply SND_SOC_WCD938X_SDW
|
||||
imply SND_SOC_WCD939X_SDW
|
||||
imply SND_SOC_LPASS_MACRO_COMMON
|
||||
|
|
@ -2431,6 +2432,24 @@ config SND_SOC_WCD937X_SDW
|
|||
via soundwire.
|
||||
To compile this codec driver say Y or m.
|
||||
|
||||
config SND_SOC_WCD9378
|
||||
depends on SND_SOC_WCD9378_SDW
|
||||
tristate
|
||||
depends on SOUNDWIRE || !SOUNDWIRE
|
||||
select SND_SOC_WCD_COMMON
|
||||
|
||||
config SND_SOC_WCD9378_SDW
|
||||
tristate "WCD9378 Codec - SDW"
|
||||
select SND_SOC_WCD9378
|
||||
depends on SOUNDWIRE
|
||||
select REGMAP_SOUNDWIRE
|
||||
help
|
||||
The WCD9378 is an audio codec IC connected to the host over
|
||||
SoundWire, found on Qualcomm SM7635 boards such as the
|
||||
Fairphone 6. It has three ADCs for analog microphones, mic
|
||||
bias supplies, headphone/earpiece outputs and MBHC.
|
||||
To compile this codec driver say Y or m.
|
||||
|
||||
config SND_SOC_WCD938X
|
||||
depends on SND_SOC_WCD938X_SDW
|
||||
tristate
|
||||
|
|
|
|||
|
|
@ -356,6 +356,8 @@ snd-soc-wcd9335-y := wcd9335.o
|
|||
snd-soc-wcd934x-y := wcd934x.o
|
||||
snd-soc-wcd937x-y := wcd937x.o
|
||||
snd-soc-wcd937x-sdw-y := wcd937x-sdw.o
|
||||
snd-soc-wcd9378-y := wcd9378.o
|
||||
snd-soc-wcd9378-sdw-y := wcd9378-sdw.o
|
||||
snd-soc-wcd938x-y := wcd938x.o
|
||||
snd-soc-wcd938x-sdw-y := wcd938x-sdw.o
|
||||
snd-soc-wcd939x-y := wcd939x.o
|
||||
|
|
@ -796,6 +798,11 @@ ifdef CONFIG_SND_SOC_WCD937X_SDW
|
|||
# avoid link failure by forcing sdw code built-in when needed
|
||||
obj-$(CONFIG_SND_SOC_WCD937X) += snd-soc-wcd937x-sdw.o
|
||||
endif
|
||||
obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378.o
|
||||
ifdef CONFIG_SND_SOC_WCD9378_SDW
|
||||
# avoid link failure by forcing sdw code built-in when needed
|
||||
obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378-sdw.o
|
||||
endif
|
||||
obj-$(CONFIG_SND_SOC_WCD938X) += snd-soc-wcd938x.o
|
||||
ifdef CONFIG_SND_SOC_WCD938X_SDW
|
||||
# avoid link failure by forcing sdw code built-in when needed
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@
|
|||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/tlv.h>
|
||||
#include "aw88261.h"
|
||||
#include "aw88395/aw88395_data_type.h"
|
||||
#include "aw88395/aw88395_device.h"
|
||||
|
|
@ -27,64 +30,10 @@ static const struct regmap_config aw88261_remap_config = {
|
|||
|
||||
static void aw88261_dev_set_volume(struct aw_device *aw_dev, unsigned int value)
|
||||
{
|
||||
struct aw_volume_desc *vol_desc = &aw_dev->volume_desc;
|
||||
unsigned int real_value, volume;
|
||||
unsigned int reg_value;
|
||||
unsigned int volume = min(value, (unsigned int)AW88261_MUTE_VOL);
|
||||
|
||||
volume = min((value + vol_desc->init_volume), (unsigned int)AW88261_MUTE_VOL);
|
||||
real_value = DB_TO_REG_VAL(volume);
|
||||
|
||||
regmap_read(aw_dev->regmap, AW88261_SYSCTRL2_REG, ®_value);
|
||||
|
||||
real_value = (real_value | (reg_value & AW88261_VOL_START_MASK));
|
||||
|
||||
dev_dbg(aw_dev->dev, "value 0x%x , real_value:0x%x", value, real_value);
|
||||
|
||||
regmap_write(aw_dev->regmap, AW88261_SYSCTRL2_REG, real_value);
|
||||
}
|
||||
|
||||
static void aw88261_dev_fade_in(struct aw_device *aw_dev)
|
||||
{
|
||||
struct aw_volume_desc *desc = &aw_dev->volume_desc;
|
||||
int fade_in_vol = desc->ctl_volume;
|
||||
int fade_step = aw_dev->fade_step;
|
||||
int i;
|
||||
|
||||
if (fade_step == 0 || aw_dev->fade_in_time == 0) {
|
||||
aw88261_dev_set_volume(aw_dev, fade_in_vol);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = AW88261_MUTE_VOL; i >= fade_in_vol; i -= fade_step) {
|
||||
aw88261_dev_set_volume(aw_dev, i);
|
||||
usleep_range(aw_dev->fade_in_time,
|
||||
aw_dev->fade_in_time + 10);
|
||||
}
|
||||
|
||||
if (i != fade_in_vol)
|
||||
aw88261_dev_set_volume(aw_dev, fade_in_vol);
|
||||
}
|
||||
|
||||
static void aw88261_dev_fade_out(struct aw_device *aw_dev)
|
||||
{
|
||||
struct aw_volume_desc *desc = &aw_dev->volume_desc;
|
||||
int fade_step = aw_dev->fade_step;
|
||||
int i;
|
||||
|
||||
if (fade_step == 0 || aw_dev->fade_out_time == 0) {
|
||||
aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = desc->ctl_volume; i <= AW88261_MUTE_VOL; i += fade_step) {
|
||||
aw88261_dev_set_volume(aw_dev, i);
|
||||
usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10);
|
||||
}
|
||||
|
||||
if (i != AW88261_MUTE_VOL) {
|
||||
aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL);
|
||||
usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10);
|
||||
}
|
||||
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL2_REG,
|
||||
~AW88261_VOL_MASK, DB_TO_REG_VAL(volume));
|
||||
}
|
||||
|
||||
static void aw88261_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag)
|
||||
|
|
@ -120,13 +69,13 @@ static void aw88261_dev_amppd(struct aw_device *aw_dev, bool amppd)
|
|||
static void aw88261_dev_mute(struct aw_device *aw_dev, bool is_mute)
|
||||
{
|
||||
if (is_mute) {
|
||||
aw88261_dev_fade_out(aw_dev);
|
||||
aw88261_dev_set_volume(aw_dev, AW88261_MUTE_VOL);
|
||||
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
||||
~AW88261_HMUTE_MASK, AW88261_HMUTE_ENABLE_VALUE);
|
||||
} else {
|
||||
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
||||
~AW88261_HMUTE_MASK, AW88261_HMUTE_DISABLE_VALUE);
|
||||
aw88261_dev_fade_in(aw_dev);
|
||||
aw88261_dev_set_volume(aw_dev, aw_dev->volume_desc.ctl_volume);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,21 +100,21 @@ static int aw88261_dev_get_iis_status(struct aw_device *aw_dev)
|
|||
if (ret)
|
||||
return ret;
|
||||
if ((reg_val & AW88261_BIT_PLL_CHECK) != AW88261_BIT_PLL_CHECK) {
|
||||
dev_err(aw_dev->dev, "check pll lock fail,reg_val:0x%04x", reg_val);
|
||||
dev_dbg(aw_dev->dev, "check pll lock fail,reg_val:0x%04x", reg_val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aw88261_dev_check_mode1_pll(struct aw_device *aw_dev)
|
||||
static int aw88261_dev_check_pll(struct aw_device *aw_dev)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) {
|
||||
ret = aw88261_dev_get_iis_status(aw_dev);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "mode1 iis signal check error");
|
||||
dev_dbg(aw_dev->dev, "mode1 iis signal check error");
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
} else {
|
||||
return ret;
|
||||
|
|
@ -175,71 +124,74 @@ static int aw88261_dev_check_mode1_pll(struct aw_device *aw_dev)
|
|||
return -EPERM;
|
||||
}
|
||||
|
||||
static int aw88261_dev_check_mode2_pll(struct aw_device *aw_dev)
|
||||
{
|
||||
unsigned int reg_val;
|
||||
int ret, i;
|
||||
|
||||
ret = regmap_read(aw_dev->regmap, AW88261_PLLCTRL1_REG, ®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg_val &= (~AW88261_CCO_MUX_MASK);
|
||||
if (reg_val == AW88261_CCO_MUX_DIVIDED_VALUE) {
|
||||
dev_dbg(aw_dev->dev, "CCO_MUX is already divider");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/* change mode2 */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG,
|
||||
~AW88261_CCO_MUX_MASK, AW88261_CCO_MUX_DIVIDED_VALUE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) {
|
||||
ret = aw88261_dev_get_iis_status(aw_dev);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "mode2 iis signal check error");
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* change mode1 */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG,
|
||||
~AW88261_CCO_MUX_MASK, AW88261_CCO_MUX_BYPASS_VALUE);
|
||||
if (ret == 0) {
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) {
|
||||
ret = aw88261_dev_check_mode1_pll(aw_dev);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error");
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aw88261_dev_check_syspll(struct aw_device *aw_dev)
|
||||
static int aw88261_dev_configure_syspll(struct aw88261 *aw88261)
|
||||
{
|
||||
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||
int ret;
|
||||
|
||||
ret = aw88261_dev_check_mode1_pll(aw_dev);
|
||||
if (ret) {
|
||||
dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check");
|
||||
ret = aw88261_dev_check_mode2_pll(aw_dev);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "mode2 check iis failed");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/* Configure TDM slots (I2S is represented as no slots) */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG,
|
||||
~AW88261_SLOT_NUM_MASK, aw88261->slot_num_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG,
|
||||
~AW88261_I2S_TX_SLOTVLD_MASK,
|
||||
aw88261->tx_slotvld_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG,
|
||||
~AW88261_I2S_RXL_SLOTVLD_MASK,
|
||||
aw88261->rxl_slotvld_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG,
|
||||
~AW88261_I2S_RXR_SLOTVLD_MASK,
|
||||
aw88261->rxr_slotvld_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* PLL divider must be used for 8/16/32 kHz modes */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_PLLCTRL1_REG,
|
||||
~AW88261_CCO_MUX_MASK, aw88261->cco_mux_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The word clock (WCK) defines the beginning of a frame */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG,
|
||||
~AW88261_I2SSR_MASK, aw88261->sr_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The bit clock (BCK) defines the length of a frame */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG,
|
||||
~AW88261_I2SBCK_MASK,
|
||||
(aw88261->tdm_bck_value != AW88261_TDM_BCK_UNSET)
|
||||
? aw88261->tdm_bck_value : aw88261->bck_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The logical frame size is the width of data for 1 slot */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG,
|
||||
~AW88261_I2SFS_MASK, aw88261->fs_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The I2S interface mode (Philips standard, LSB/MSB justified) */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL1_REG,
|
||||
~AW88261_I2SMD_MASK, aw88261->md_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The polarity of the bit clock (BCK) */
|
||||
ret = regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
||||
~AW88261_BCKINV_MASK, aw88261->bck_inv_value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return aw88261_dev_check_pll(aw_dev);
|
||||
}
|
||||
|
||||
static int aw88261_dev_check_sysst(struct aw_device *aw_dev)
|
||||
|
|
@ -254,10 +206,10 @@ static int aw88261_dev_check_sysst(struct aw_device *aw_dev)
|
|||
return ret;
|
||||
|
||||
check_val = reg_val & (~AW88261_BIT_SYSST_CHECK_MASK)
|
||||
& AW88261_BIT_SYSST_CHECK;
|
||||
if (check_val != AW88261_BIT_SYSST_CHECK) {
|
||||
dev_err(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x",
|
||||
reg_val, AW88261_BIT_SYSST_CHECK);
|
||||
& AW88261_BIT_PLL_CHECK;
|
||||
if (check_val != AW88261_BIT_PLL_CHECK) {
|
||||
dev_dbg(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x",
|
||||
reg_val, AW88261_BIT_PLL_CHECK);
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
} else {
|
||||
return 0;
|
||||
|
|
@ -284,22 +236,22 @@ static void aw88261_reg_force_set(struct aw88261 *aw88261)
|
|||
if (aw88261->frcset_en == AW88261_FRCSET_ENABLE) {
|
||||
/* set FORCE_PWM */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL3_REG,
|
||||
AW88261_FORCE_PWM_MASK, AW88261_FORCE_PWM_FORCEMINUS_PWM_VALUE);
|
||||
~AW88261_FORCE_PWM_MASK, AW88261_FORCE_PWM_FORCEMINUS_PWM_VALUE);
|
||||
/* set BOOST_OS_WIDTH */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL5_REG,
|
||||
AW88261_BST_OS_WIDTH_MASK, AW88261_BST_OS_WIDTH_50NS_VALUE);
|
||||
~AW88261_BST_OS_WIDTH_MASK, AW88261_BST_OS_WIDTH_50NS_VALUE);
|
||||
/* set BURST_LOOPR */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL6_REG,
|
||||
AW88261_BST_LOOPR_MASK, AW88261_BST_LOOPR_340K_VALUE);
|
||||
~AW88261_BST_LOOPR_MASK, AW88261_BST_LOOPR_340K_VALUE);
|
||||
/* set RSQN_DLY */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL7_REG,
|
||||
AW88261_RSQN_DLY_MASK, AW88261_RSQN_DLY_35NS_VALUE);
|
||||
~AW88261_RSQN_DLY_MASK, AW88261_RSQN_DLY_35NS_VALUE);
|
||||
/* set BURST_SSMODE */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL8_REG,
|
||||
AW88261_BURST_SSMODE_MASK, AW88261_BURST_SSMODE_FAST_VALUE);
|
||||
~AW88261_BURST_SSMODE_MASK, AW88261_BURST_SSMODE_FAST_VALUE);
|
||||
/* set BST_BURST */
|
||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL9_REG,
|
||||
AW88261_BST_BURST_MASK, AW88261_BST_BURST_30MA_VALUE);
|
||||
~AW88261_BST_BURST_MASK, AW88261_BST_BURST_30MA_VALUE);
|
||||
} else {
|
||||
dev_dbg(aw88261->aw_pa->dev, "needn't set reg value");
|
||||
}
|
||||
|
|
@ -550,7 +502,7 @@ static int aw88261_dev_start(struct aw88261 *aw88261)
|
|||
int ret;
|
||||
|
||||
if (aw_dev->status == AW88261_DEV_PW_ON) {
|
||||
dev_info(aw_dev->dev, "already power on");
|
||||
dev_dbg(aw_dev->dev, "already power on");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -558,9 +510,9 @@ static int aw88261_dev_start(struct aw88261 *aw88261)
|
|||
aw88261_dev_pwd(aw_dev, false);
|
||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||
|
||||
ret = aw88261_dev_check_syspll(aw_dev);
|
||||
ret = aw88261_dev_configure_syspll(aw88261);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "pll check failed cannot start");
|
||||
dev_dbg(aw_dev->dev, "pll check failed");
|
||||
goto pll_check_fail;
|
||||
}
|
||||
|
||||
|
|
@ -571,7 +523,7 @@ static int aw88261_dev_start(struct aw88261 *aw88261)
|
|||
/* check i2s status */
|
||||
ret = aw88261_dev_check_sysst(aw_dev);
|
||||
if (ret) {
|
||||
dev_err(aw_dev->dev, "sysst check failed");
|
||||
dev_dbg(aw_dev->dev, "sysst check failed");
|
||||
goto sysst_check_fail;
|
||||
}
|
||||
|
||||
|
|
@ -672,31 +624,25 @@ static void aw88261_start_pa(struct aw88261 *aw88261)
|
|||
for (i = 0; i < AW88261_START_RETRIES; i++) {
|
||||
ret = aw88261_reg_update(aw88261, aw88261->phase_sync);
|
||||
if (ret) {
|
||||
dev_err(aw88261->aw_pa->dev, "fw update failed, cnt:%d\n", i);
|
||||
dev_dbg(aw88261->aw_pa->dev,
|
||||
"aw88261_reg_update failed, cnt:%d, ret:%d\n", i, ret);
|
||||
continue;
|
||||
}
|
||||
ret = aw88261_dev_start(aw88261);
|
||||
if (ret) {
|
||||
dev_err(aw88261->aw_pa->dev, "aw88261 device start failed. retry = %d", i);
|
||||
dev_dbg(aw88261->aw_pa->dev,
|
||||
"aw88261_dev_start failed, cnt:%d, ret:%d\n", i, ret);
|
||||
continue;
|
||||
} else {
|
||||
dev_info(aw88261->aw_pa->dev, "start success\n");
|
||||
dev_dbg(aw88261->aw_pa->dev, "start success\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret != 0)
|
||||
dev_err(aw88261->aw_pa->dev, "start failure (%d)\n", ret);
|
||||
}
|
||||
|
||||
static void aw88261_startup_work(struct work_struct *work)
|
||||
{
|
||||
struct aw88261 *aw88261 =
|
||||
container_of(work, struct aw88261, start_work.work);
|
||||
|
||||
mutex_lock(&aw88261->lock);
|
||||
aw88261_start_pa(aw88261);
|
||||
mutex_unlock(&aw88261->lock);
|
||||
}
|
||||
|
||||
static void aw88261_start(struct aw88261 *aw88261, bool sync_start)
|
||||
static void aw88261_start(struct aw88261 *aw88261)
|
||||
{
|
||||
if (aw88261->aw_pa->fw_status != AW88261_DEV_FW_OK)
|
||||
return;
|
||||
|
|
@ -704,14 +650,226 @@ static void aw88261_start(struct aw88261 *aw88261, bool sync_start)
|
|||
if (aw88261->aw_pa->status == AW88261_DEV_PW_ON)
|
||||
return;
|
||||
|
||||
if (sync_start == AW88261_SYNC_START)
|
||||
aw88261_start_pa(aw88261);
|
||||
else
|
||||
queue_delayed_work(system_dfl_wq,
|
||||
&aw88261->start_work,
|
||||
AW88261_START_WORK_DELAY_MS);
|
||||
aw88261_start_pa(aw88261);
|
||||
}
|
||||
|
||||
static int aw88261_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
aw88261->bck_inv_value = AW88261_BCKINV_INVERTED_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported invert mode 0x%x\n",
|
||||
fmt & SND_SOC_DAIFMT_INV_MASK);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_MSB:
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
aw88261->md_value = AW88261_I2SMD_MSB_JUSTIFIED_VALUE;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LSB:
|
||||
aw88261->md_value = AW88261_I2SMD_LSB_JUSTIFIED_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported DAI format 0x%x\n",
|
||||
fmt & SND_SOC_DAIFMT_FORMAT_MASK);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
return 0;
|
||||
|
||||
aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE;
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
aw88261->sr_value = AW88261_I2SSR_8KHZ_VALUE;
|
||||
aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE;
|
||||
break;
|
||||
case 11025:
|
||||
aw88261->sr_value = AW88261_I2SSR_11P025KHZ_VALUE;
|
||||
break;
|
||||
case 12000:
|
||||
aw88261->sr_value = AW88261_I2SSR_12KHZ_VALUE;
|
||||
break;
|
||||
case 16000:
|
||||
aw88261->sr_value = AW88261_I2SSR_16KHZ_VALUE;
|
||||
aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE;
|
||||
break;
|
||||
case 22050:
|
||||
aw88261->sr_value = AW88261_I2SSR_22P05KHZ_VALUE;
|
||||
break;
|
||||
case 24000:
|
||||
aw88261->sr_value = AW88261_I2SSR_24KHZ_VALUE;
|
||||
break;
|
||||
case 32000:
|
||||
aw88261->sr_value = AW88261_I2SSR_32KHZ_VALUE;
|
||||
aw88261->cco_mux_value = AW88261_CCO_MUX_DIVIDED_VALUE;
|
||||
break;
|
||||
case 44100:
|
||||
aw88261->sr_value = AW88261_I2SSR_44P1KHZ_VALUE;
|
||||
break;
|
||||
case 48000:
|
||||
aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE;
|
||||
break;
|
||||
case 96000:
|
||||
aw88261->sr_value = AW88261_I2SSR_96KHZ_VALUE;
|
||||
break;
|
||||
case 192000:
|
||||
aw88261->sr_value = AW88261_I2SSR_192KHZ_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported sample rate %d\n",
|
||||
params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_width(params)) {
|
||||
case 16:
|
||||
aw88261->fs_value = AW88261_I2SFS_16_BITS_VALUE;
|
||||
break;
|
||||
case 20:
|
||||
aw88261->fs_value = AW88261_I2SFS_20_BITS_VALUE;
|
||||
break;
|
||||
case 24:
|
||||
aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE;
|
||||
break;
|
||||
case 32:
|
||||
aw88261->fs_value = AW88261_I2SFS_32_BITS_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported bit width %d\n",
|
||||
params_width(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_physical_width(params)) {
|
||||
case 16:
|
||||
aw88261->bck_value = AW88261_I2SBCK_32FS_VALUE;
|
||||
break;
|
||||
case 24:
|
||||
aw88261->bck_value = AW88261_I2SBCK_48FS_VALUE;
|
||||
break;
|
||||
case 32:
|
||||
aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported physical bit width %d\n",
|
||||
params_physical_width(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_set_tdm_slot(struct snd_soc_dai *dai,
|
||||
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
int chan;
|
||||
|
||||
switch (slots) {
|
||||
case 0:
|
||||
/* Just reset everything TDM related to I2S values */
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE;
|
||||
aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET;
|
||||
aw88261->tx_slotvld_mask = 0 << AW88261_I2S_TX_SLOTVLD_START_BIT;
|
||||
aw88261->rxl_slotvld_mask = 0 << AW88261_I2S_RXL_SLOTVLD_START_BIT;
|
||||
aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT;
|
||||
return 0;
|
||||
case 1:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM1S_VALUE;
|
||||
break;
|
||||
case 2:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM2S_VALUE;
|
||||
break;
|
||||
case 4:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM4S_VALUE;
|
||||
break;
|
||||
case 6:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM6S_VALUE;
|
||||
break;
|
||||
case 8:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM8S_VALUE;
|
||||
break;
|
||||
case 16:
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_TDM16S_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported slot count %d\n", slots);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (slot_width) {
|
||||
case 16:
|
||||
aw88261->tdm_bck_value = AW88261_I2SBCK_32FS_VALUE;
|
||||
break;
|
||||
case 20:
|
||||
case 24:
|
||||
aw88261->tdm_bck_value = AW88261_I2SBCK_48FS_VALUE;
|
||||
break;
|
||||
case 32:
|
||||
aw88261->tdm_bck_value = AW88261_I2SBCK_64FS_VALUE;
|
||||
break;
|
||||
default:
|
||||
dev_err(aw88261->aw_pa->dev, "unsupported slot width %d\n",
|
||||
slot_width);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (tx_mask != 0) {
|
||||
if ((chan = __ffs(tx_mask)) > 16)
|
||||
return -EINVAL;
|
||||
|
||||
aw88261->tx_slotvld_mask = chan << AW88261_I2S_TX_SLOTVLD_START_BIT;
|
||||
}
|
||||
|
||||
if (rx_mask != 0) {
|
||||
if ((chan = __ffs(rx_mask)) > 16)
|
||||
return -EINVAL;
|
||||
|
||||
aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT;
|
||||
}
|
||||
|
||||
if ((rx_mask & ~BIT(chan)) != 0) {
|
||||
if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16)
|
||||
return -EINVAL;
|
||||
|
||||
aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops aw88261_dai_ops = {
|
||||
.set_fmt = aw88261_set_fmt,
|
||||
.hw_params = aw88261_hw_params,
|
||||
.set_tdm_slot = aw88261_set_tdm_slot,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver aw88261_dai[] = {
|
||||
{
|
||||
.name = "aw88261-aif",
|
||||
|
|
@ -730,78 +888,10 @@ static struct snd_soc_dai_driver aw88261_dai[] = {
|
|||
.rates = AW88261_RATES,
|
||||
.formats = AW88261_FORMATS,
|
||||
},
|
||||
.ops = &aw88261_dai_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int aw88261_get_fade_in_time(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||
|
||||
ucontrol->value.integer.value[0] = aw_dev->fade_in_time;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_set_fade_in_time(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||
int time;
|
||||
|
||||
time = ucontrol->value.integer.value[0];
|
||||
|
||||
if (time < mc->min || time > mc->max)
|
||||
return -EINVAL;
|
||||
|
||||
if (time != aw_dev->fade_in_time) {
|
||||
aw_dev->fade_in_time = time;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_get_fade_out_time(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||
|
||||
ucontrol->value.integer.value[0] = aw_dev->fade_out_time;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_set_fade_out_time(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||
int time;
|
||||
|
||||
time = ucontrol->value.integer.value[0];
|
||||
if (time < mc->min || time > mc->max)
|
||||
return -EINVAL;
|
||||
|
||||
if (time != aw_dev->fade_out_time) {
|
||||
aw_dev->fade_out_time = time;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_dev_set_profile_index(struct aw_device *aw_dev, int index)
|
||||
{
|
||||
/* check the index whether is valid */
|
||||
|
|
@ -880,7 +970,7 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol,
|
|||
|
||||
if (aw88261->aw_pa->status) {
|
||||
aw88261_dev_stop(aw88261->aw_pa);
|
||||
aw88261_start(aw88261, AW88261_SYNC_START);
|
||||
aw88261_start(aw88261);
|
||||
}
|
||||
|
||||
mutex_unlock(&aw88261->lock);
|
||||
|
|
@ -895,7 +985,8 @@ static int aw88261_volume_get(struct snd_kcontrol *kcontrol,
|
|||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec);
|
||||
struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc;
|
||||
|
||||
ucontrol->value.integer.value[0] = vol_desc->ctl_volume;
|
||||
ucontrol->value.integer.value[0] =
|
||||
(AW88261_MUTE_VOL - vol_desc->ctl_volume) / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -908,13 +999,13 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol,
|
|||
struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc;
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int value;
|
||||
|
||||
value = ucontrol->value.integer.value[0];
|
||||
int value = ucontrol->value.integer.value[0];
|
||||
|
||||
if (value < mc->min || value > mc->max)
|
||||
return -EINVAL;
|
||||
|
||||
value = AW88261_MUTE_VOL - (value * 2);
|
||||
|
||||
if (vol_desc->ctl_volume != value) {
|
||||
vol_desc->ctl_volume = value;
|
||||
aw88261_dev_set_volume(aw88261->aw_pa, vol_desc->ctl_volume);
|
||||
|
|
@ -925,48 +1016,18 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_get_fade_step(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec);
|
||||
|
||||
ucontrol->value.integer.value[0] = aw88261->aw_pa->fade_step;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw88261_set_fade_step(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec);
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int value;
|
||||
|
||||
value = ucontrol->value.integer.value[0];
|
||||
if (value < mc->min || value > mc->max)
|
||||
return -EINVAL;
|
||||
|
||||
if (aw88261->aw_pa->fade_step != value) {
|
||||
aw88261->aw_pa->fade_step = value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB
|
||||
* which is too precise for TLV (!) so we have to multiply the scale by 2.
|
||||
*
|
||||
* The range is clamped at -90dB to prevent overflowing the 4-bit part.
|
||||
*/
|
||||
static const DECLARE_TLV_DB_SCALE(volume_tlv, -9000, 25, 0);
|
||||
|
||||
static const struct snd_kcontrol_new aw88261_controls[] = {
|
||||
SOC_SINGLE_EXT("PCM Playback Volume", AW88261_SYSCTRL2_REG,
|
||||
6, AW88261_MUTE_VOL, 0, aw88261_volume_get,
|
||||
aw88261_volume_set),
|
||||
SOC_SINGLE_EXT("Fade Step", 0, 0, AW88261_MUTE_VOL, 0,
|
||||
aw88261_get_fade_step, aw88261_set_fade_step),
|
||||
SOC_SINGLE_EXT("Volume Ramp Up Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN,
|
||||
aw88261_get_fade_in_time, aw88261_set_fade_in_time),
|
||||
SOC_SINGLE_EXT("Volume Ramp Down Step", 0, 0, FADE_TIME_MAX, FADE_TIME_MIN,
|
||||
aw88261_get_fade_out_time, aw88261_set_fade_out_time),
|
||||
SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG,
|
||||
6, AW88261_CTL_MAX_VOL, 1,
|
||||
aw88261_volume_get, aw88261_volume_set, volume_tlv),
|
||||
AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info,
|
||||
aw88261_profile_get, aw88261_profile_set),
|
||||
};
|
||||
|
|
@ -980,7 +1041,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w,
|
|||
mutex_lock(&aw88261->lock);
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMU:
|
||||
aw88261_start(aw88261, AW88261_ASYNC_START);
|
||||
aw88261_start(aw88261);
|
||||
break;
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
aw88261_dev_stop(aw88261->aw_pa);
|
||||
|
|
@ -1057,8 +1118,6 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
aw_dev->fade_in_time = AW88261_500_US;
|
||||
aw_dev->fade_out_time = AW88261_500_US;
|
||||
aw_dev->prof_cur = AW88261_INIT_PROFILE;
|
||||
aw_dev->prof_index = AW88261_INIT_PROFILE;
|
||||
|
||||
|
|
@ -1094,6 +1153,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg
|
|||
static int aw88261_request_firmware_file(struct aw88261 *aw88261)
|
||||
{
|
||||
const struct firmware *cont = NULL;
|
||||
struct aw_container *aw_cfg;
|
||||
const char *fw_name;
|
||||
int ret;
|
||||
|
||||
|
|
@ -1111,15 +1171,17 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261)
|
|||
dev_info(aw88261->aw_pa->dev, "loaded %s - size: %zu\n",
|
||||
fw_name, cont ? cont->size : 0);
|
||||
|
||||
aw88261->aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, cont->size + sizeof(int), GFP_KERNEL);
|
||||
if (!aw88261->aw_cfg) {
|
||||
aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL);
|
||||
if (!aw_cfg) {
|
||||
release_firmware(cont);
|
||||
return -ENOMEM;
|
||||
}
|
||||
aw88261->aw_cfg->len = (int)cont->size;
|
||||
memcpy(aw88261->aw_cfg->data, cont->data, cont->size);
|
||||
aw_cfg->len = (int)cont->size;
|
||||
memcpy(aw_cfg->data, cont->data, cont->size);
|
||||
release_firmware(cont);
|
||||
|
||||
aw88261->aw_cfg = aw_cfg;
|
||||
|
||||
ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg);
|
||||
if (ret) {
|
||||
dev_err(aw88261->aw_pa->dev, "load [%s] failed !", fw_name);
|
||||
|
|
@ -1142,8 +1204,6 @@ static int aw88261_codec_probe(struct snd_soc_component *component)
|
|||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||
int ret;
|
||||
|
||||
INIT_DELAYED_WORK(&aw88261->start_work, aw88261_startup_work);
|
||||
|
||||
ret = aw88261_request_firmware_file(aw88261);
|
||||
if (ret)
|
||||
return dev_err_probe(aw88261->aw_pa->dev, ret,
|
||||
|
|
@ -1167,16 +1227,8 @@ static int aw88261_codec_probe(struct snd_soc_component *component)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void aw88261_codec_remove(struct snd_soc_component *aw_codec)
|
||||
{
|
||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(aw_codec);
|
||||
|
||||
cancel_delayed_work_sync(&aw88261->start_work);
|
||||
}
|
||||
|
||||
static const struct snd_soc_component_driver soc_codec_dev_aw88261 = {
|
||||
.probe = aw88261_codec_probe,
|
||||
.remove = aw88261_codec_remove,
|
||||
};
|
||||
|
||||
static void aw88261_parse_channel_dt(struct aw88261 *aw88261)
|
||||
|
|
@ -1208,7 +1260,7 @@ static int aw88261_init(struct aw88261 *aw88261, struct i2c_client *i2c, struct
|
|||
return ret;
|
||||
}
|
||||
if (chip_id != AW88261_CHIP_ID) {
|
||||
dev_err(&i2c->dev, "unsupported device");
|
||||
dev_err(&i2c->dev, "unsupported device id = %x", chip_id);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
|
|
@ -1229,8 +1281,7 @@ static int aw88261_init(struct aw88261 *aw88261, struct i2c_client *i2c, struct
|
|||
aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID;
|
||||
aw_dev->channel = 0;
|
||||
aw_dev->fw_status = AW88261_DEV_FW_FAILED;
|
||||
aw_dev->fade_step = AW88261_VOLUME_STEP_DB;
|
||||
aw_dev->volume_desc.ctl_volume = AW88261_VOL_DEFAULT_VALUE;
|
||||
aw_dev->volume_desc.ctl_volume = AW88261_CTL_DEFAULT_VOL;
|
||||
aw_dev->volume_desc.mute_volume = AW88261_MUTE_VOL;
|
||||
aw88261_parse_channel_dt(aw88261);
|
||||
|
||||
|
|
@ -1249,6 +1300,17 @@ static int aw88261_i2c_probe(struct i2c_client *i2c)
|
|||
if (!aw88261)
|
||||
return -ENOMEM;
|
||||
|
||||
/* set defaults */
|
||||
aw88261->slot_num_value = AW88261_SLOT_NUM_I2S_MODE_VALUE;
|
||||
aw88261->sr_value = AW88261_I2SSR_48KHZ_VALUE;
|
||||
aw88261->cco_mux_value = AW88261_CCO_MUX_BYPASS_VALUE;
|
||||
aw88261->fs_value = AW88261_I2SFS_24_BITS_VALUE;
|
||||
aw88261->bck_value = AW88261_I2SBCK_64FS_VALUE;
|
||||
aw88261->bck_inv_value = AW88261_BCKINV_NOT_INVERT_VALUE;
|
||||
aw88261->tdm_bck_value = AW88261_TDM_BCK_UNSET;
|
||||
aw88261->md_value = AW88261_I2SMD_PHILIPS_STANDARD_VALUE;
|
||||
aw88261->rxr_slotvld_mask = 1 << AW88261_I2S_RXR_SLOTVLD_START_BIT;
|
||||
|
||||
mutex_init(&aw88261->lock);
|
||||
|
||||
i2c_set_clientdata(i2c, aw88261);
|
||||
|
|
@ -1274,7 +1336,7 @@ static int aw88261_i2c_probe(struct i2c_client *i2c)
|
|||
}
|
||||
|
||||
static const struct i2c_device_id aw88261_i2c_id[] = {
|
||||
{ "aw88261" },
|
||||
{ .name = "aw88261" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, aw88261_i2c_id);
|
||||
|
|
|
|||
|
|
@ -116,6 +116,19 @@
|
|||
#define AW88261_VCALK_SHIFT (0)
|
||||
#define AW88261_VCALKL_SHIFT (0)
|
||||
|
||||
#define AW88261_BCKINV_START_BIT (4)
|
||||
#define AW88261_BCKINV_BITS_LEN (1)
|
||||
#define AW88261_BCKINV_MASK \
|
||||
(~(((1<<AW88261_BCKINV_BITS_LEN)-1) << AW88261_BCKINV_START_BIT))
|
||||
|
||||
#define AW88261_BCKINV_NOT_INVERT (0)
|
||||
#define AW88261_BCKINV_NOT_INVERT_VALUE \
|
||||
(AW88261_BCKINV_NOT_INVERT << AW88261_BCKINV_START_BIT)
|
||||
|
||||
#define AW88261_BCKINV_INVERTED (1)
|
||||
#define AW88261_BCKINV_INVERTED_VALUE \
|
||||
(AW88261_BCKINV_INVERTED << AW88261_BCKINV_START_BIT)
|
||||
|
||||
#define AW88261_AMPPD_START_BIT (1)
|
||||
#define AW88261_AMPPD_BITS_LEN (1)
|
||||
#define AW88261_AMPPD_MASK \
|
||||
|
|
@ -181,12 +194,6 @@
|
|||
AW88261_OTHS_OT_VALUE | \
|
||||
AW88261_PLLS_LOCKED_VALUE))
|
||||
|
||||
#define AW88261_BIT_SYSST_CHECK \
|
||||
(AW88261_BSTS_FINISHED_VALUE | \
|
||||
AW88261_SWS_SWITCHING_VALUE | \
|
||||
AW88261_CLKS_STABLE_VALUE | \
|
||||
AW88261_PLLS_LOCKED_VALUE)
|
||||
|
||||
#define AW88261_ULS_HMUTE_START_BIT (14)
|
||||
#define AW88261_ULS_HMUTE_BITS_LEN (1)
|
||||
#define AW88261_ULS_HMUTE_MASK \
|
||||
|
|
@ -249,7 +256,8 @@
|
|||
#define AW88261_VOL_MASK \
|
||||
(~(((1<<AW88261_VOL_BITS_LEN)-1) << AW88261_VOL_START_BIT))
|
||||
|
||||
#define AW88261_VOL_DEFAULT_VALUE (0)
|
||||
#define AW88261_CTL_MAX_VOL (AW88261_MUTE_VOL / 2)
|
||||
#define AW88261_CTL_DEFAULT_VOL (AW88261_CTL_MAX_VOL / 2)
|
||||
|
||||
#define AW88261_I2STXEN_START_BIT (6)
|
||||
#define AW88261_I2STXEN_BITS_LEN (1)
|
||||
|
|
@ -264,7 +272,148 @@
|
|||
#define AW88261_I2STXEN_ENABLE_VALUE \
|
||||
(AW88261_I2STXEN_ENABLE << AW88261_I2STXEN_START_BIT)
|
||||
|
||||
#define AW88261_CCO_MUX_START_BIT (14)
|
||||
#define AW88261_I2SMD_START_BIT (8)
|
||||
#define AW88261_I2SMD_BITS_LEN (2)
|
||||
#define AW88261_I2SMD_MASK \
|
||||
(~(((1<<AW88261_I2SMD_BITS_LEN)-1) << AW88261_I2SMD_START_BIT))
|
||||
|
||||
#define AW88261_I2SMD_PHILIPS_STANDARD (0)
|
||||
#define AW88261_I2SMD_PHILIPS_STANDARD_VALUE \
|
||||
(AW88261_I2SMD_PHILIPS_STANDARD << AW88261_I2SMD_START_BIT)
|
||||
|
||||
#define AW88261_I2SMD_MSB_JUSTIFIED (1)
|
||||
#define AW88261_I2SMD_MSB_JUSTIFIED_VALUE \
|
||||
(AW88261_I2SMD_MSB_JUSTIFIED << AW88261_I2SMD_START_BIT)
|
||||
|
||||
#define AW88261_I2SMD_LSB_JUSTIFIED (2)
|
||||
#define AW88261_I2SMD_LSB_JUSTIFIED_VALUE \
|
||||
(AW88261_I2SMD_LSB_JUSTIFIED << AW88261_I2SMD_START_BIT)
|
||||
|
||||
#define AW88261_I2SFS_START_BIT (6)
|
||||
#define AW88261_I2SFS_BITS_LEN (2)
|
||||
#define AW88261_I2SFS_MASK \
|
||||
(~(((1<<AW88261_I2SFS_BITS_LEN)-1)<<AW88261_I2SFS_START_BIT))
|
||||
|
||||
#define AW88261_I2SFS_16_BITS (0)
|
||||
#define AW88261_I2SFS_16_BITS_VALUE \
|
||||
(AW88261_I2SFS_16_BITS << AW88261_I2SFS_START_BIT)
|
||||
#define AW88261_I2SFS_20_BITS (1)
|
||||
#define AW88261_I2SFS_20_BITS_VALUE \
|
||||
(AW88261_I2SFS_20_BITS << AW88261_I2SFS_START_BIT)
|
||||
#define AW88261_I2SFS_24_BITS (2)
|
||||
#define AW88261_I2SFS_24_BITS_VALUE \
|
||||
(AW88261_I2SFS_24_BITS << AW88261_I2SFS_START_BIT)
|
||||
#define AW88261_I2SFS_32_BITS (3)
|
||||
#define AW88261_I2SFS_32_BITS_VALUE \
|
||||
(AW88261_I2SFS_32_BITS << AW88261_I2SFS_START_BIT)
|
||||
|
||||
#define AW88261_I2SBCK_START_BIT (4)
|
||||
#define AW88261_I2SBCK_BITS_LEN (2)
|
||||
#define AW88261_I2SBCK_MASK \
|
||||
(~(((1<<AW88261_I2SBCK_BITS_LEN)-1) << AW88261_I2SBCK_START_BIT))
|
||||
|
||||
#define AW88261_I2SBCK_32FS (0)
|
||||
#define AW88261_I2SBCK_32FS_VALUE \
|
||||
(AW88261_I2SBCK_32FS << AW88261_I2SBCK_START_BIT)
|
||||
|
||||
#define AW88261_I2SBCK_48FS (1)
|
||||
#define AW88261_I2SBCK_48FS_VALUE \
|
||||
(AW88261_I2SBCK_48FS << AW88261_I2SBCK_START_BIT)
|
||||
|
||||
#define AW88261_I2SBCK_64FS (2)
|
||||
#define AW88261_I2SBCK_64FS_VALUE \
|
||||
(AW88261_I2SBCK_64FS << AW88261_I2SBCK_START_BIT)
|
||||
|
||||
#define AW88261_TDM_BCK_UNSET UINT_MAX
|
||||
|
||||
#define AW88261_I2SSR_START_BIT (0)
|
||||
#define AW88261_I2SSR_BITS_LEN (4)
|
||||
#define AW88261_I2SSR_MASK \
|
||||
(~(((1<<AW88261_I2SSR_BITS_LEN)-1) << AW88261_I2SSR_START_BIT))
|
||||
|
||||
#define AW88261_I2SSR_8KHZ (0)
|
||||
#define AW88261_I2SSR_8KHZ_VALUE \
|
||||
(AW88261_I2SSR_8KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_11P025KHZ (1)
|
||||
#define AW88261_I2SSR_11P025KHZ_VALUE \
|
||||
(AW88261_I2SSR_11P025KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_12KHZ (2)
|
||||
#define AW88261_I2SSR_12KHZ_VALUE \
|
||||
(AW88261_I2SSR_12KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_16KHZ (3)
|
||||
#define AW88261_I2SSR_16KHZ_VALUE \
|
||||
(AW88261_I2SSR_16KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_22P05KHZ (4)
|
||||
#define AW88261_I2SSR_22P05KHZ_VALUE \
|
||||
(AW88261_I2SSR_22P05KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_24KHZ (5)
|
||||
#define AW88261_I2SSR_24KHZ_VALUE \
|
||||
(AW88261_I2SSR_24KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_32KHZ (6)
|
||||
#define AW88261_I2SSR_32KHZ_VALUE \
|
||||
(AW88261_I2SSR_32KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_44P1KHZ (7)
|
||||
#define AW88261_I2SSR_44P1KHZ_VALUE \
|
||||
(AW88261_I2SSR_44P1KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_48KHZ (8)
|
||||
#define AW88261_I2SSR_48KHZ_VALUE \
|
||||
(AW88261_I2SSR_48KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_96KHZ (9)
|
||||
#define AW88261_I2SSR_96KHZ_VALUE \
|
||||
(AW88261_I2SSR_96KHZ << AW88261_I2SSR_START_BIT)
|
||||
#define AW88261_I2SSR_192KHZ (10)
|
||||
#define AW88261_I2SSR_192KHZ_VALUE \
|
||||
(AW88261_I2SSR_192KHZ << AW88261_I2SSR_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_START_BIT (12)
|
||||
#define AW88261_SLOT_NUM_BITS_LEN (3)
|
||||
#define AW88261_SLOT_NUM_MASK \
|
||||
(~(((1<<AW88261_SLOT_NUM_BITS_LEN)-1) << AW88261_SLOT_NUM_START_BIT))
|
||||
|
||||
#define AW88261_SLOT_NUM_I2S_MODE (0)
|
||||
#define AW88261_SLOT_NUM_I2S_MODE_VALUE \
|
||||
(AW88261_SLOT_NUM_I2S_MODE << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM1S (1)
|
||||
#define AW88261_SLOT_NUM_TDM1S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM1S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM2S (2)
|
||||
#define AW88261_SLOT_NUM_TDM2S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM2S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM4S (3)
|
||||
#define AW88261_SLOT_NUM_TDM4S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM4S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM6S (4)
|
||||
#define AW88261_SLOT_NUM_TDM6S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM6S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM8S (5)
|
||||
#define AW88261_SLOT_NUM_TDM8S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM8S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_SLOT_NUM_TDM16S (6)
|
||||
#define AW88261_SLOT_NUM_TDM16S_VALUE \
|
||||
(AW88261_SLOT_NUM_TDM16S << AW88261_SLOT_NUM_START_BIT)
|
||||
|
||||
#define AW88261_I2S_TX_SLOTVLD_START_BIT (8)
|
||||
#define AW88261_I2S_TX_SLOTVLD_BITS_LEN (4)
|
||||
#define AW88261_I2S_TX_SLOTVLD_MASK \
|
||||
(~(((1<<AW88261_I2S_TX_SLOTVLD_BITS_LEN)-1) << AW88261_I2S_TX_SLOTVLD_START_BIT))
|
||||
|
||||
#define AW88261_I2S_RXR_SLOTVLD_START_BIT (4)
|
||||
#define AW88261_I2S_RXR_SLOTVLD_BITS_LEN (4)
|
||||
#define AW88261_I2S_RXR_SLOTVLD_MASK \
|
||||
(~(((1<<AW88261_I2S_RXR_SLOTVLD_BITS_LEN)-1) << AW88261_I2S_RXR_SLOTVLD_START_BIT))
|
||||
|
||||
#define AW88261_I2S_RXL_SLOTVLD_START_BIT (0)
|
||||
#define AW88261_I2S_RXL_SLOTVLD_BITS_LEN (4)
|
||||
#define AW88261_I2S_RXL_SLOTVLD_MASK \
|
||||
(~(((1<<AW88261_I2S_RXL_SLOTVLD_BITS_LEN)-1) << AW88261_I2S_RXL_SLOTVLD_START_BIT))
|
||||
|
||||
#define AW88261_CCO_MUX_START_BIT (6)
|
||||
#define AW88261_CCO_MUX_BITS_LEN (1)
|
||||
#define AW88261_CCO_MUX_MASK \
|
||||
(~(((1<<AW88261_CCO_MUX_BITS_LEN)-1) << AW88261_CCO_MUX_START_BIT))
|
||||
|
|
@ -370,21 +519,20 @@
|
|||
#define AW88261_START_RETRIES (5)
|
||||
#define AW88261_START_WORK_DELAY_MS (0)
|
||||
|
||||
/* NOTE: 192000 has a reg value donwstream but not listed in datasheet */
|
||||
#define AW88261_RATES (SNDRV_PCM_RATE_8000_48000 | \
|
||||
SNDRV_PCM_RATE_12000 | \
|
||||
SNDRV_PCM_RATE_24000 | \
|
||||
SNDRV_PCM_RATE_96000)
|
||||
#define AW88261_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
#define FADE_TIME_MAX 100000
|
||||
#define FADE_TIME_MIN 0
|
||||
|
||||
#define AW88261_DEV_DEFAULT_CH (0)
|
||||
#define AW88261_ACF_FILE "aw88261_acf.bin"
|
||||
#define AW88261_DEV_SYSST_CHECK_MAX (10)
|
||||
#define AW88261_SOFT_RESET_VALUE (0x55aa)
|
||||
#define AW88261_REG_TO_DB (0x3f)
|
||||
#define AW88261_VOL_START_MASK (0xfc00)
|
||||
#define AW88261_INIT_PROFILE (0)
|
||||
|
||||
#define REG_VAL_TO_DB(value) ((((value) >> AW88261_VOL_6DB_START) * \
|
||||
|
|
@ -403,11 +551,6 @@
|
|||
.put = profile_set, \
|
||||
}
|
||||
|
||||
enum {
|
||||
AW88261_SYNC_START = 0,
|
||||
AW88261_ASYNC_START,
|
||||
};
|
||||
|
||||
enum aw88261_id {
|
||||
AW88261_CHIP_ID = 0x2113,
|
||||
};
|
||||
|
|
@ -442,7 +585,6 @@ struct aw88261 {
|
|||
struct aw_device *aw_pa;
|
||||
struct mutex lock;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct delayed_work start_work;
|
||||
struct regmap *regmap;
|
||||
struct aw_container *aw_cfg;
|
||||
|
||||
|
|
@ -451,6 +593,19 @@ struct aw88261 {
|
|||
unsigned int mute_st;
|
||||
unsigned int amppd_st;
|
||||
|
||||
unsigned int sr_value;
|
||||
unsigned int cco_mux_value;
|
||||
unsigned int fs_value;
|
||||
unsigned int bck_value;
|
||||
unsigned int bck_inv_value;
|
||||
unsigned int tdm_bck_value;
|
||||
unsigned int md_value;
|
||||
|
||||
unsigned int slot_num_value;
|
||||
unsigned int tx_slotvld_mask;
|
||||
unsigned int rxl_slotvld_mask;
|
||||
unsigned int rxr_slotvld_mask;
|
||||
|
||||
bool phase_sync;
|
||||
};
|
||||
|
||||
|
|
|
|||
495
sound/soc/codecs/wcd9378-sdw.c
Normal file
495
sound/soc/codecs/wcd9378-sdw.c
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2026, Jorijn van der Graaf
|
||||
*
|
||||
* SoundWire slave driver for the Qualcomm WCD9378 audio codec.
|
||||
*
|
||||
* The codec presents two SoundWire slaves (RX and TX, mfg 0x0217 part
|
||||
* 0x0110); the SDCA control space is a 32-bit paged register map accessed
|
||||
* through the TX slave.
|
||||
*/
|
||||
|
||||
#include <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");
|
||||
1390
sound/soc/codecs/wcd9378.c
Normal file
1390
sound/soc/codecs/wcd9378.c
Normal file
File diff suppressed because it is too large
Load diff
246
sound/soc/codecs/wcd9378.h
Normal file
246
sound/soc/codecs/wcd9378.h
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2026, Jorijn van der Graaf
|
||||
*
|
||||
* Register map for the Qualcomm WCD9378 audio codec.
|
||||
*
|
||||
* The codec exposes its control registers in the SoundWire SDCA control
|
||||
* address space (bit 30 set, SDCA function number in bits 25:22), accessed
|
||||
* through the TX SoundWire slave. The analog core registers (function 0,
|
||||
* implementation-defined region at +0x180000) are layout-compatible with
|
||||
* the WCD937x family; on top of that the chip adds SDCA-style functions
|
||||
* (SmartMIC0/1/2, SmartJACK, SmartAMP) whose sequencers drive the analog
|
||||
* power-up autonomously.
|
||||
*/
|
||||
|
||||
#ifndef __WCD9378_H__
|
||||
#define __WCD9378_H__
|
||||
|
||||
#include <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__ */
|
||||
|
|
@ -224,6 +224,21 @@ static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct s
|
|||
dev_err(dai->dev, "Failed to prepare Graph %d\n", rc);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start the port already at prepare, like q6afe does: this starts
|
||||
* the interface clocks before the DAPM power-up sequence runs, so
|
||||
* codecs that need a live BCLK at power-up (e.g. aw88261) can
|
||||
* start synchronously. The trigger callback keeps its start as a
|
||||
* no-op fallback via is_port_started.
|
||||
*/
|
||||
rc = q6apm_graph_start(dai_data->graph[dai->id]);
|
||||
if (rc < 0) {
|
||||
dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
|
||||
goto err;
|
||||
}
|
||||
dai_data->is_port_started[dai->id] = true;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
|
|||
switch (cpu_dai->id) {
|
||||
case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX:
|
||||
case QUINARY_MI2S_RX...QUINARY_MI2S_TX:
|
||||
case SENARY_MI2S_RX...SENARY_MI2S_TX:
|
||||
snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
|
||||
break;
|
||||
case WSA_CODEC_DMA_RX_0:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue