Compare commits

..

13 commits

Author SHA1 Message Date
2e9adc633e Merge branch 'sensors' into combined-stable
# Conflicts:
#	arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
2026-07-16 15:41:12 +02:00
cf98406408 arm64: dts: qcom: milos-fairphone-fp6: add sensor i2c bus and magnetometer
The FP6 sensor suite (QMC6308 magnetometer @ 0x2c, SPL07 barometer
@ 0x76, STK3BCx ALS/proximity @ 0x48) sits on an I2C bus wired to
TLMM gpio153/gpio154. Those pads are eGPIOs with no AP serial-engine
function: on the stock OS the bus is driven by an island QUP owned by
the Snapdragon Sensor Core on the ADSP. From the AP they are only
usable as software GPIOs, so describe the bus as i2c-gpio, and add the
QMC6308 magnetometer node: powered from the always-on vreg_l10b (per
the schematics), mount matrix from the vendor SSC registry
(volcano_qmc630x_0.json .orient: sensor x -> device +y, sensor y ->
device -x, z -> z), validated against Earth's field with a four-point
cardinal rotation.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-14 22:17:50 +02:00
50cd4e823f iio: magnetometer: add support for QST QMC6308
The QST QMC6308 is a 3-axis AMR magnetometer on I2C, a single-supply
4-pin WLCSP part with no interrupt/DRDY pin, found e.g. in the
Fairphone 6. Its register map differs from the QMC5883L (chip ID at
0x00 instead of 0x0D, data at 0x01..0x06, and the range field living
in control register 2), so add a separate driver rather than extending
the QMC5883L driver.

Support raw X/Y/Z reads, output data rates 10/50/100/200 Hz, field
ranges +-30/12/8/2 Gauss, filter oversampling ratios (OSR1) 8/4/2/1,
the mount matrix, and runtime PM. The second-stage decimation filter
(OSR2) is left at its power-on default. The package has no DRDY pin,
so there is no trigger support.

Run measurements in the chip's periodic "normal" mode paced by the
DRDY flag rather than in its one-shot "single" mode: the datasheet
specifies no conversion time that could bound a one-shot wait, while
normal mode is paced by the specified output data rates, which also
keeps the sampling_frequency ABI meaningful.

Runtime PM puts the chip into its suspend mode after 500 ms without a
reading, dropping supply current from tens-to-hundreds of microamps to
2-3 uA (datasheet Table 2). The suspended chip retains its registers
and keeps responding on I2C, so resuming only rewrites the mode field
and discards one stale sample, and configuration changes can be
applied even while suspended. VDD is left enabled across runtime
suspend: the on-chip suspend draw is already negligible, and register
retention is what keeps the resume path trivial.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-14 22:17:49 +02:00
066348d67b Merge branch 'mic' into combined 2026-07-06 20:53:40 +02:00
82284cc84a Merge branch 'mic' into combined 2026-07-06 02:18:05 +02:00
2931c83762 Merge branch 'mic' into combined
# Conflicts:
#	arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
2026-07-06 01:14:20 +02:00
d4c5ebbeee Merge branches 'audio' and 'nfc' into combined
combined = flashable working image: v2 speaker audio + NFC reader.
Excludes mic (WCD9378 capture, WIP) and nfc-ce (card emulation, broken).
2026-07-05 21:33:08 +02:00
52c785c315 arm64: dts: qcom: milos-fairphone-fp6: enable Senary MI2S dai-link
Uncomment the Senary MI2S speaker dai-link. The sc8280xp machine driver
gained Senary MI2S support and the aw88261 power-up check is fixed (both
carried on this branch), so the link is functional: both AW88261 amps
load their ACF firmware and play.

This restores the enablement that was lost when the audio carries were
rebased from the v7.0.8 stack onto v7.1.2-milos: only the two driver
patches were carried over, leaving the sound card with zero dai-links
(the card registers but stays empty - no PCMs, no controls).

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 21:32:11 +02:00
d571dd42d3 ASoC: qcom: q6apm-lpass-dais: start the graph at prepare
The DAPM power-up sequence runs during snd_pcm prepare, but the BE
port graph is only started at trigger time. A codec that powers up
synchronously from a DAPM widget event and needs a running bit clock
at that point - such as aw88261 since commit caea99ac809d ("ASoC:
codecs: aw88261: remove async start") - can therefore never see a
live clock: its power-up check runs before the trigger and fails on
every stream start.

Start the graph at the end of prepare instead, mirroring what
q6afe_dai_prepare() does on the legacy stack, so the interface
clocks already run when DAPM powers up the codec. The FE side
already starts its own graph at prepare in q6apm_dai_prepare();
only the BE waited for trigger. The trigger-time start is kept as
a fallback, guarded by is_port_started.

Tested on the Fairphone (Gen. 6) - 2x aw88261 on Senary MI2S:
without this the amplifiers fail to power up with SYSST reporting
"no clock" on every stream start; with it they start synchronously,
including for the first short stream of the boot.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 21:32:11 +02:00
79c7597fc5 ASoC: codecs: aw88261: backport mainline format negotiation + power-up fix
The v7.1.2-milos base predates two aw88261 changes this branch needs:
the hw_params format-negotiation series (Val Packett's work, in broonie
for-7.2) and our power-up SYSST fix. The Senary MI2S carry drives the
two AW88261 amps in S16_LE end to end, which relies on the amplifier
negotiating its format; without it the machine driver has to force
32-bit slots. The SYSST fix is also required: aw88261_dev_start()
otherwise fails "check sysst fail" on this firmware profile because it
demands SWS/BSTS while the amp is still muted.

Backport aw88261.c/.h wholesale to the mainline state (matching
work/linux tag audio-mainline-aw88261) so this branch tracks mainline
rather than the older milos base driver. Local carry only; milos
absorbs it on the next rebase past those commits.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 21:32:11 +02:00
692c0cd1a4 ASoC: qcom: sc8280xp: support Senary MI2S
Extend the clock-provider DAI fmt setup to Senary MI2S; without it
q6i2s_set_fmt() is never called, ws_src remains external and the DSP
does not drive the I2S clocks.

On the Fairphone (Gen. 6) the speaker amplifiers sit on this
interface; the board DTS enabling it is headed upstream separately
via linux-arm-msm.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 21:32:11 +02:00
9cd00a65e8 nfc: s3fwrn5: support the S3NRN4V variant
The S3NRN4V (e.g. on the Fairphone 6, SM7635) is an S3FWRN5-family NFC
controller that needs different bring-up, selected with a new
samsung,s3nrn4v compatible:

 - It ships with working firmware behind a bootloader protocol this
   driver does not implement (GET_BOOTINFO times out), so the firmware
   download step is skipped. Its RF registers are (re)loaded with the
   proprietary DUAL_OPTION command (the HW and SW register blobs merged
   into a single stream) instead of the START/SET/STOP_RFREG sequence.

 - Its reference clock speed is configured with the single-byte FW_CFG
   form, sent from the ->setup hook (after CORE_RESET, before CORE_INIT).
   The selector value (0x11) is taken from the vendor configuration for
   this part; its encoding is not documented.

 - It gates its XI clock through a CLK_REQ line: the chip drives it high
   when it needs the clock, notably to synthesise the 13.56 MHz poll
   carrier. Left always-on, the free-running clock never lets the chip's
   TX PLL lock on a fresh start and it cannot poll (it falls back to
   listen only). Service the handshake when a clk-req GPIO is described,
   gating the clock on it; without one the clock stays always-on.

The variant is carried as match data by both the OF and the I2C device
id tables so the two match paths agree, and the OF table is now
referenced unconditionally for its match data, so drop the
of_match_ptr()/__maybe_unused annotations from it.

The error policy differs between the two configuration steps on purpose:
a clock misconfiguration is fatal (a ->setup failure aborts CORE_INIT),
whereas an RF-register update failure is only warned about and bring-up
continues, since the chip falls back to the RF registers programmed in
its flash and NFC may still work.

Unlike the host-endian word read in the legacy rfreg path, the
DUAL_OPTION checksum is accumulated with get_unaligned_le32() and emitted
little-endian explicitly, so it is correct regardless of CPU endianness.

Existing S3FWRN5 / S3FWRN82 setups keep the firmware-download path and
the always-on clock, unchanged.

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 20:37:57 +02:00
d837afcfd6 arm64: dts: qcom: milos-fairphone-fp6: add NFC
Add the Samsung S3NRN4V NFC + eSE controller on i2c1. Its XI clock is
provided by the RF_CLK2 PMIC buffer and gated through the controller's
CLK_REQ line on tlmm GPIO6.

The enable line is routed to the chip's power-down input, which is
asserted high to turn the chip off (the downstream driver treats VEN as
active-low on this design) -- hence GPIO_ACTIVE_HIGH, unlike the
exynos5433-tm2 wiring of the same driver. The pin is pulled up so the
chip stays off while the line is not driven.

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-05 18:02:28 +02:00
14 changed files with 1539 additions and 310 deletions

View file

@ -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";

View file

@ -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

View file

@ -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

View 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, &reg);
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");

View file

@ -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);

View file

@ -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,

View file

@ -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;
}

View file

@ -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_ */

View file

@ -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,

View file

@ -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;

View file

@ -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, &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, &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);

View file

@ -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;
};

View file

@ -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) {

View file

@ -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: