Compare commits
13 commits
mic
...
combined-s
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e9adc633e | |||
| cf98406408 | |||
| 50cd4e823f | |||
| 066348d67b | |||
| 82284cc84a | |||
| 2931c83762 | |||
| d4c5ebbeee | |||
| 52c785c315 | |||
| d571dd42d3 | |||
| 79c7597fc5 | |||
| 692c0cd1a4 | |||
| 9cd00a65e8 | |||
| d837afcfd6 |
14 changed files with 1539 additions and 310 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";
|
||||
|
||||
|
|
@ -237,7 +265,6 @@
|
|||
};
|
||||
};
|
||||
|
||||
/*
|
||||
i2s-dai-link {
|
||||
link-name = "Senary MI2S Playback";
|
||||
|
||||
|
|
@ -253,7 +280,6 @@
|
|||
sound-dai = <&q6apm>;
|
||||
};
|
||||
};
|
||||
*/
|
||||
};
|
||||
|
||||
wcd9378: audio-codec {
|
||||
|
|
@ -744,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 {
|
||||
|
|
@ -1165,6 +1210,21 @@
|
|||
<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";
|
||||
|
|
@ -1179,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";
|
||||
|
|
@ -1246,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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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