Compare commits
35 commits
combined-s
...
combined
| Author | SHA1 | Date | |
|---|---|---|---|
| a4301a82eb | |||
| fb5953a877 | |||
| ff614d787e | |||
| 4f9a6b04d8 | |||
| 806ec77a1f | |||
| 2d3c1ed4cf | |||
| f3eb810dfe | |||
| cfc2b6a276 | |||
| 3c381d76fc | |||
| d36259f54b | |||
| 5f42f9a74a | |||
| 57cbd69e36 | |||
| 0c63174949 | |||
| 3cbf6d6fa8 | |||
| 2626032a50 | |||
| 63b5c43f02 | |||
| 7695d5e6e4 | |||
| 066348d67b | |||
| 08f135ee41 | |||
| 82284cc84a | |||
| 82c43dd3f1 | |||
| 2931c83762 | |||
| 5694c4a5f3 | |||
| d4c5ebbeee | |||
| 52c785c315 | |||
| d571dd42d3 | |||
| 79c7597fc5 | |||
| 692c0cd1a4 | |||
| 9cd00a65e8 | |||
| d837afcfd6 | |||
| e9a6b531cf | |||
| 9df8d66027 | |||
| 5c9c899ba9 | |||
| 2fd346efe3 | |||
| 8ed47c2b0a |
27 changed files with 3852 additions and 320 deletions
|
|
@ -237,14 +237,34 @@
|
||||||
sound {
|
sound {
|
||||||
compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard";
|
compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard";
|
||||||
model = "Fairphone (Gen. 6)";
|
model = "Fairphone (Gen. 6)";
|
||||||
// audio-routing = ...
|
audio-routing = "AMIC1", "MIC BIAS1",
|
||||||
|
"AMIC2", "MIC BIAS2",
|
||||||
|
"AMIC3", "MIC BIAS3",
|
||||||
|
"TX SWR_INPUT0", "ADC1_OUTPUT",
|
||||||
|
"TX SWR_INPUT1", "ADC2_OUTPUT",
|
||||||
|
"TX SWR_INPUT2", "ADC3_OUTPUT";
|
||||||
|
|
||||||
pinctrl-0 = <&lpi_i2s2_active>;
|
pinctrl-0 = <&lpi_i2s2_active>;
|
||||||
pinctrl-1 = <&lpi_i2s2_sleep>;
|
pinctrl-1 = <&lpi_i2s2_sleep>;
|
||||||
pinctrl-names = "default",
|
pinctrl-names = "default",
|
||||||
"sleep";
|
"sleep";
|
||||||
|
|
||||||
/*
|
wcd-capture-dai-link {
|
||||||
|
link-name = "WCD Capture";
|
||||||
|
|
||||||
|
codec {
|
||||||
|
sound-dai = <&wcd9378 1>, <&swr2 0>, <&lpass_txmacro 0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
cpu {
|
||||||
|
sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>;
|
||||||
|
};
|
||||||
|
|
||||||
|
platform {
|
||||||
|
sound-dai = <&q6apm>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
i2s-dai-link {
|
i2s-dai-link {
|
||||||
link-name = "Senary MI2S Playback";
|
link-name = "Senary MI2S Playback";
|
||||||
|
|
||||||
|
|
@ -260,7 +280,29 @@
|
||||||
sound-dai = <&q6apm>;
|
sound-dai = <&q6apm>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
*/
|
};
|
||||||
|
|
||||||
|
wcd9378: audio-codec {
|
||||||
|
compatible = "qcom,wcd9378-codec";
|
||||||
|
|
||||||
|
pinctrl-0 = <&wcd_reset_n_active>;
|
||||||
|
pinctrl-names = "default";
|
||||||
|
|
||||||
|
reset-gpios = <&tlmm 162 GPIO_ACTIVE_LOW>;
|
||||||
|
|
||||||
|
vdd-buck-supply = <&vreg_l8b>;
|
||||||
|
vdd-rxtx-supply = <&vreg_l7b>;
|
||||||
|
vdd-io-supply = <&vreg_l7b>;
|
||||||
|
vdd-mic-bias-supply = <&vreg_bob>;
|
||||||
|
|
||||||
|
qcom,micbias1-microvolt = <1800000>;
|
||||||
|
qcom,micbias2-microvolt = <1800000>;
|
||||||
|
qcom,micbias3-microvolt = <1800000>;
|
||||||
|
|
||||||
|
qcom,rx-device = <&wcd9378_rx>;
|
||||||
|
qcom,tx-device = <&wcd9378_tx>;
|
||||||
|
|
||||||
|
#sound-dai-cells = <1>;
|
||||||
};
|
};
|
||||||
|
|
||||||
thermal-zones {
|
thermal-zones {
|
||||||
|
|
@ -728,9 +770,28 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
&i2c1 {
|
&i2c1 {
|
||||||
/* Samsung NFC @ 0x27 */
|
|
||||||
|
|
||||||
status = "okay";
|
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 {
|
&i2c3 {
|
||||||
|
|
@ -1114,11 +1175,48 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
&swr1 {
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
/* WCD9378 RX */
|
||||||
|
wcd9378_rx: codec@0,4 {
|
||||||
|
compatible = "sdw20217011000";
|
||||||
|
reg = <0 4>;
|
||||||
|
qcom,rx-port-mapping = <1 2 3 4 5>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&swr2 {
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
/* WCD9378 TX */
|
||||||
|
wcd9378_tx: codec@0,3 {
|
||||||
|
compatible = "sdw20217011000";
|
||||||
|
reg = <0 3>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WCD9378 TX port 1 (ADC1) <=> SWR2 port 1 (SWRM_TX1)
|
||||||
|
* WCD9378 TX port 2 (ADC2) <=> SWR2 port 1
|
||||||
|
* WCD9378 TX port 3 (ADC3) <=> SWR2 port 1
|
||||||
|
* WCD9378 TX port 4 (DMIC0,1, MBHC) <=> SWR2 port 2
|
||||||
|
* WCD9378 TX port 5 (DMIC2..5) <=> SWR2 port 3
|
||||||
|
*/
|
||||||
|
qcom,tx-port-mapping = <1 1 1 2 3>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
&tlmm {
|
&tlmm {
|
||||||
gpio-reserved-ranges = <8 4>, /* Fingerprint SPI */
|
gpio-reserved-ranges = <8 4>, /* Fingerprint SPI */
|
||||||
<13 1>, /* NC */
|
<13 1>, /* NC */
|
||||||
<63 2>; /* WLAN UART */
|
<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 {
|
sensor_i2c_default: sensor-i2c-default-state {
|
||||||
/* SDA, SCL; external pull-ups to vreg_l10b */
|
/* SDA, SCL; external pull-ups to vreg_l10b */
|
||||||
pins = "gpio153", "gpio154";
|
pins = "gpio153", "gpio154";
|
||||||
|
|
@ -1141,6 +1239,13 @@
|
||||||
bias-disable;
|
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 {
|
qup_uart11_sleep_cts: qup-uart11-sleep-cts-state {
|
||||||
pins = "gpio48";
|
pins = "gpio48";
|
||||||
function = "gpio";
|
function = "gpio";
|
||||||
|
|
@ -1208,6 +1313,17 @@
|
||||||
bias-pull-down;
|
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 {
|
sdc2_card_det_n: sdc2-card-det-state {
|
||||||
pins = "gpio65";
|
pins = "gpio65";
|
||||||
function = "gpio";
|
function = "gpio";
|
||||||
|
|
@ -1249,6 +1365,13 @@
|
||||||
drive-strength = <2>;
|
drive-strength = <2>;
|
||||||
bias-pull-down;
|
bias-pull-down;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wcd_reset_n_active: wcd-reset-n-active-state {
|
||||||
|
pins = "gpio162";
|
||||||
|
function = "gpio";
|
||||||
|
drive-strength = <16>;
|
||||||
|
output-high;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
&uart5 {
|
&uart5 {
|
||||||
|
|
|
||||||
|
|
@ -548,6 +548,14 @@
|
||||||
no-map;
|
no-map;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
adsp_rpc_heap_mem: adsp-rpc-heap {
|
||||||
|
compatible = "shared-dma-pool";
|
||||||
|
alloc-ranges = <0x0 0x0 0x0 0xffffffff>;
|
||||||
|
reusable;
|
||||||
|
alignment = <0x0 0x400000>;
|
||||||
|
size = <0x0 0xc00000>;
|
||||||
|
};
|
||||||
|
|
||||||
pvm_fw_mem: pvm-fw-region@824a0000 {
|
pvm_fw_mem: pvm-fw-region@824a0000 {
|
||||||
reg = <0x0 0x824a0000 0x0 0x100000>;
|
reg = <0x0 0x824a0000 0x0 0x100000>;
|
||||||
no-map;
|
no-map;
|
||||||
|
|
@ -1398,6 +1406,9 @@
|
||||||
qcom,glink-channels = "fastrpcglink-apps-dsp";
|
qcom,glink-channels = "fastrpcglink-apps-dsp";
|
||||||
label = "adsp";
|
label = "adsp";
|
||||||
qcom,non-secure-domain;
|
qcom,non-secure-domain;
|
||||||
|
memory-region = <&adsp_rpc_heap_mem>;
|
||||||
|
qcom,vmids = <QCOM_SCM_VMID_LPASS
|
||||||
|
QCOM_SCM_VMID_ADSP_HEAP>;
|
||||||
#address-cells = <1>;
|
#address-cells = <1>;
|
||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
|
@ -1422,6 +1433,8 @@
|
||||||
reg = <5>;
|
reg = <5>;
|
||||||
iommus = <&apps_smmu 0x1005 0x0>,
|
iommus = <&apps_smmu 0x1005 0x0>,
|
||||||
<&apps_smmu 0x1065 0x0>;
|
<&apps_smmu 0x1065 0x0>;
|
||||||
|
/* AUDIO_STATICPD bank, per downstream volcano-dsp.dtsi */
|
||||||
|
pd-type = <2>;
|
||||||
dma-coherent;
|
dma-coherent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,13 @@
|
||||||
#define USER_PD (1)
|
#define USER_PD (1)
|
||||||
#define SENSORS_PD (2)
|
#define SENSORS_PD (2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Downstream DT "pd-type" context-bank designations (volcano-dsp.dtsi).
|
||||||
|
* A static PD must be spawned on its designated context bank / SMMU
|
||||||
|
* stream or the DSP treats it as a generic signed PD.
|
||||||
|
*/
|
||||||
|
#define FASTRPC_DT_PD_TYPE_AUDIO_STATICPD 2
|
||||||
|
|
||||||
#define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev)
|
#define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev)
|
||||||
|
|
||||||
struct fastrpc_phy_page {
|
struct fastrpc_phy_page {
|
||||||
|
|
@ -255,6 +262,8 @@ struct fastrpc_session_ctx {
|
||||||
int sid;
|
int sid;
|
||||||
bool used;
|
bool used;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
/* downstream DT pd-type this context bank is dedicated to (0 = generic) */
|
||||||
|
u32 pd_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fastrpc_soc_data {
|
struct fastrpc_soc_data {
|
||||||
|
|
@ -314,6 +323,11 @@ struct fastrpc_user {
|
||||||
struct kref refcount;
|
struct kref refcount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct fastrpc_session_ctx *fastrpc_session_alloc_pd_type(
|
||||||
|
struct fastrpc_user *fl, u32 pd_type);
|
||||||
|
static void fastrpc_session_free(struct fastrpc_channel_ctx *cctx,
|
||||||
|
struct fastrpc_session_ctx *session);
|
||||||
|
|
||||||
/* Extract SMMU PA from consolidated IOVA */
|
/* Extract SMMU PA from consolidated IOVA */
|
||||||
static inline dma_addr_t fastrpc_ipa_to_dma_addr(struct fastrpc_channel_ctx *cctx, dma_addr_t iova)
|
static inline dma_addr_t fastrpc_ipa_to_dma_addr(struct fastrpc_channel_ctx *cctx, dma_addr_t iova)
|
||||||
{
|
{
|
||||||
|
|
@ -1343,7 +1357,7 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
|
||||||
struct fastrpc_phy_page pages[1];
|
struct fastrpc_phy_page pages[1];
|
||||||
char *name;
|
char *name;
|
||||||
int err;
|
int err;
|
||||||
bool scm_done = false;
|
bool scm_done = false, heap_created = false;
|
||||||
struct {
|
struct {
|
||||||
int client_id;
|
int client_id;
|
||||||
u32 namelen;
|
u32 namelen;
|
||||||
|
|
@ -1371,11 +1385,35 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl,
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The audio static PD must live on its designated context bank so the
|
||||||
|
* DSP sees its memory on the audio SMMU stream; on a generic bank the
|
||||||
|
* aDSP enforces module signing in the PD (dlopen of unsigned dynamic
|
||||||
|
* modules fails demanding a testsig). Re-route before anything is
|
||||||
|
* allocated against the session. Falls back to the already-held
|
||||||
|
* generic session if no bank is tagged in DT.
|
||||||
|
*/
|
||||||
|
if (init.namelen >= strlen("audiopd") &&
|
||||||
|
!memcmp(name, "audiopd", strlen("audiopd"))) {
|
||||||
|
struct fastrpc_session_ctx *audio_sess;
|
||||||
|
|
||||||
|
audio_sess = fastrpc_session_alloc_pd_type(fl,
|
||||||
|
FASTRPC_DT_PD_TYPE_AUDIO_STATICPD);
|
||||||
|
if (audio_sess) {
|
||||||
|
fastrpc_session_free(fl->cctx, fl->sctx);
|
||||||
|
fl->sctx = audio_sess;
|
||||||
|
}
|
||||||
|
dev_info(&fl->cctx->rpdev->dev,
|
||||||
|
"audiopd: using session sid %d (%s)\n", fl->sctx->sid,
|
||||||
|
audio_sess ? "dedicated audio CB" : "no audio CB in DT");
|
||||||
|
}
|
||||||
|
|
||||||
if (!fl->cctx->remote_heap) {
|
if (!fl->cctx->remote_heap) {
|
||||||
err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen,
|
err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen,
|
||||||
&fl->cctx->remote_heap);
|
&fl->cctx->remote_heap);
|
||||||
if (err)
|
if (err)
|
||||||
goto err_name;
|
goto err_name;
|
||||||
|
heap_created = true;
|
||||||
|
|
||||||
/* Map if we have any heap VMIDs associated with this ADSP Static Process. */
|
/* Map if we have any heap VMIDs associated with this ADSP Static Process. */
|
||||||
if (fl->cctx->vmcount) {
|
if (fl->cctx->vmcount) {
|
||||||
|
|
@ -1446,8 +1484,16 @@ err_invoke:
|
||||||
&fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err);
|
&fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err);
|
||||||
}
|
}
|
||||||
err_map:
|
err_map:
|
||||||
fastrpc_buf_free(fl->cctx->remote_heap);
|
/*
|
||||||
fl->cctx->remote_heap = NULL;
|
* A heap reused from an earlier spawn is still SCM-assigned to the
|
||||||
|
* remote VMIDs; freeing it here would return protected pages to the
|
||||||
|
* allocator and a later touch crashes with a synchronous external
|
||||||
|
* abort. Keep it for the next spawn attempt.
|
||||||
|
*/
|
||||||
|
if (heap_created) {
|
||||||
|
fastrpc_buf_free(fl->cctx->remote_heap);
|
||||||
|
fl->cctx->remote_heap = NULL;
|
||||||
|
}
|
||||||
err_name:
|
err_name:
|
||||||
kfree(name);
|
kfree(name);
|
||||||
err:
|
err:
|
||||||
|
|
@ -1582,7 +1628,9 @@ static struct fastrpc_session_ctx *fastrpc_session_alloc(
|
||||||
|
|
||||||
spin_lock_irqsave(&cctx->lock, flags);
|
spin_lock_irqsave(&cctx->lock, flags);
|
||||||
for (i = 0; i < cctx->sesscount; i++) {
|
for (i = 0; i < cctx->sesscount; i++) {
|
||||||
if (!cctx->session[i].used && cctx->session[i].valid) {
|
/* dedicated static-PD context banks are assigned by pd_type only */
|
||||||
|
if (!cctx->session[i].used && cctx->session[i].valid &&
|
||||||
|
!cctx->session[i].pd_type) {
|
||||||
cctx->session[i].used = true;
|
cctx->session[i].used = true;
|
||||||
session = &cctx->session[i];
|
session = &cctx->session[i];
|
||||||
/* any non-zero ID will work, session_idx + 1 is the simplest one */
|
/* any non-zero ID will work, session_idx + 1 is the simplest one */
|
||||||
|
|
@ -1595,6 +1643,29 @@ static struct fastrpc_session_ctx *fastrpc_session_alloc(
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct fastrpc_session_ctx *fastrpc_session_alloc_pd_type(
|
||||||
|
struct fastrpc_user *fl, u32 pd_type)
|
||||||
|
{
|
||||||
|
struct fastrpc_channel_ctx *cctx = fl->cctx;
|
||||||
|
struct fastrpc_session_ctx *session = NULL;
|
||||||
|
unsigned long flags;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&cctx->lock, flags);
|
||||||
|
for (i = 0; i < cctx->sesscount; i++) {
|
||||||
|
if (!cctx->session[i].used && cctx->session[i].valid &&
|
||||||
|
cctx->session[i].pd_type == pd_type) {
|
||||||
|
cctx->session[i].used = true;
|
||||||
|
session = &cctx->session[i];
|
||||||
|
fl->client_id = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&cctx->lock, flags);
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
static void fastrpc_session_free(struct fastrpc_channel_ctx *cctx,
|
static void fastrpc_session_free(struct fastrpc_channel_ctx *cctx,
|
||||||
struct fastrpc_session_ctx *session)
|
struct fastrpc_session_ctx *session)
|
||||||
{
|
{
|
||||||
|
|
@ -2251,6 +2322,9 @@ static int fastrpc_cb_probe(struct platform_device *pdev)
|
||||||
if (of_property_read_u32(dev->of_node, "reg", &sess->sid))
|
if (of_property_read_u32(dev->of_node, "reg", &sess->sid))
|
||||||
dev_info(dev, "FastRPC Session ID not specified in DT\n");
|
dev_info(dev, "FastRPC Session ID not specified in DT\n");
|
||||||
|
|
||||||
|
sess->pd_type = 0;
|
||||||
|
of_property_read_u32(dev->of_node, "pd-type", &sess->pd_type);
|
||||||
|
|
||||||
if (sessions > 0) {
|
if (sessions > 0) {
|
||||||
struct fastrpc_session_ctx *dup_sess;
|
struct fastrpc_session_ctx *dup_sess;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
|
||||||
return 0;
|
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)
|
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
|
||||||
{
|
{
|
||||||
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
|
||||||
int ret;
|
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)) {
|
if (s3fwrn5_firmware_init(info)) {
|
||||||
//skip bootloader mode
|
//skip bootloader mode
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = {
|
||||||
.open = s3fwrn5_nci_open,
|
.open = s3fwrn5_nci_open,
|
||||||
.close = s3fwrn5_nci_close,
|
.close = s3fwrn5_nci_close,
|
||||||
.send = s3fwrn5_nci_send,
|
.send = s3fwrn5_nci_send,
|
||||||
|
.setup = s3fwrn5_nci_setup,
|
||||||
.post_setup = s3fwrn5_nci_post_setup,
|
.post_setup = s3fwrn5_nci_post_setup,
|
||||||
.prop_ops = s3fwrn5_nci_prop_ops,
|
.prop_ops = s3fwrn5_nci_prop_ops,
|
||||||
.n_prop_ops = ARRAY_SIZE(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,
|
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;
|
struct s3fwrn5_info *info;
|
||||||
int ret;
|
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->phy_id = phy_id;
|
||||||
info->pdev = pdev;
|
info->pdev = pdev;
|
||||||
info->phy_ops = phy_ops;
|
info->phy_ops = phy_ops;
|
||||||
|
info->variant = variant;
|
||||||
mutex_init(&info->mutex);
|
mutex_init(&info->mutex);
|
||||||
|
|
||||||
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy {
|
||||||
struct i2c_client *i2c_dev;
|
struct i2c_client *i2c_dev;
|
||||||
struct clk *clk;
|
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;
|
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)
|
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
|
||||||
{
|
{
|
||||||
struct s3fwrn5_i2c_phy *phy = phy_id;
|
struct s3fwrn5_i2c_phy *phy = phy_id;
|
||||||
|
|
@ -146,6 +213,7 @@ out:
|
||||||
|
|
||||||
static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
static int s3fwrn5_i2c_probe(struct i2c_client *client)
|
||||||
{
|
{
|
||||||
|
enum s3fwrn5_variant variant;
|
||||||
struct s3fwrn5_i2c_phy *phy;
|
struct s3fwrn5_i2c_phy *phy;
|
||||||
int ret;
|
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.
|
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.
|
||||||
* Depending on the hardware configuration this could be an always-on
|
* Depending on the hardware configuration this could be an always-on
|
||||||
* oscillator or some external clock that must be explicitly enabled.
|
* 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);
|
mutex_init(&phy->clk_lock);
|
||||||
if (IS_ERR(phy->clk))
|
phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req",
|
||||||
return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
|
GPIOD_IN);
|
||||||
"failed to get clock\n");
|
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,
|
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
|
||||||
&i2c_phy_ops);
|
&i2c_phy_ops, variant);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
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[] = {
|
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);
|
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
|
||||||
|
|
||||||
static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
|
static const struct of_device_id of_s3fwrn5_i2c_match[] = {
|
||||||
{ .compatible = "samsung,s3fwrn5-i2c", },
|
{ .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);
|
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 = {
|
static struct i2c_driver s3fwrn5_i2c_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = S3FWRN5_I2C_DRIVER_NAME,
|
.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,
|
.probe = s3fwrn5_i2c_probe,
|
||||||
.remove = s3fwrn5_i2c_remove,
|
.remove = s3fwrn5_i2c_remove,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/minmax.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/unaligned.h>
|
||||||
|
|
||||||
#include "s3fwrn5.h"
|
#include "s3fwrn5.h"
|
||||||
#include "nci.h"
|
#include "nci.h"
|
||||||
|
|
@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
|
||||||
return 0;
|
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,
|
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
|
||||||
NCI_PROP_SET_RFREG),
|
NCI_PROP_SET_RFREG),
|
||||||
|
|
@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
|
||||||
NCI_PROP_FW_CFG),
|
NCI_PROP_FW_CFG),
|
||||||
.rsp = s3fwrn5_nci_prop_rsp,
|
.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
|
#define S3FWRN5_RFREG_SECTION_SIZE 252
|
||||||
|
|
@ -117,3 +125,112 @@ out:
|
||||||
release_firmware(fw);
|
release_firmware(fw);
|
||||||
return ret;
|
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
|
#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 {
|
struct nci_prop_fw_cfg_cmd {
|
||||||
__u8 clk_type;
|
__u8 clk_type;
|
||||||
__u8 clk_speed;
|
__u8 clk_speed;
|
||||||
|
|
@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp {
|
||||||
__u8 status;
|
__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(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_ */
|
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,17 @@ enum s3fwrn5_mode {
|
||||||
S3FWRN5_MODE_FW,
|
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 {
|
struct s3fwrn5_phy_ops {
|
||||||
void (*set_wake)(void *id, bool sleep);
|
void (*set_wake)(void *id, bool sleep);
|
||||||
void (*set_mode)(void *id, enum s3fwrn5_mode);
|
void (*set_mode)(void *id, enum s3fwrn5_mode);
|
||||||
|
|
@ -36,6 +47,7 @@ struct s3fwrn5_info {
|
||||||
const struct s3fwrn5_phy_ops *phy_ops;
|
const struct s3fwrn5_phy_ops *phy_ops;
|
||||||
|
|
||||||
struct s3fwrn5_fw_info fw_info;
|
struct s3fwrn5_fw_info fw_info;
|
||||||
|
enum s3fwrn5_variant variant;
|
||||||
|
|
||||||
struct mutex mutex;
|
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,
|
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);
|
void s3fwrn5_remove(struct nci_dev *ndev);
|
||||||
|
|
||||||
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
|
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,
|
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
|
||||||
&uart_phy_ops);
|
&uart_phy_ops, S3FWRN5_VARIANT_FWDL);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto err_serdev;
|
goto err_serdev;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -976,6 +976,20 @@ static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus,
|
||||||
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
|
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
|
||||||
int ret, i, len;
|
int ret, i, len;
|
||||||
|
|
||||||
|
if (msg->page) {
|
||||||
|
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page1,
|
||||||
|
msg->dev_num,
|
||||||
|
SDW_SCP_ADDRPAGE1);
|
||||||
|
if (ret)
|
||||||
|
return SDW_CMD_IGNORED;
|
||||||
|
|
||||||
|
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page2,
|
||||||
|
msg->dev_num,
|
||||||
|
SDW_SCP_ADDRPAGE2);
|
||||||
|
if (ret)
|
||||||
|
return SDW_CMD_IGNORED;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg->flags == SDW_MSG_FLAG_READ) {
|
if (msg->flags == SDW_MSG_FLAG_READ) {
|
||||||
for (i = 0; i < msg->len;) {
|
for (i = 0; i < msg->len;) {
|
||||||
len = min(msg->len - i, QCOM_SWRM_MAX_RD_LEN);
|
len = min(msg->len - i, QCOM_SWRM_MAX_RD_LEN);
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,54 @@ int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len)
|
||||||
qrtr_node_assign(node, le32_to_cpu(pkt->server.node));
|
qrtr_node_assign(node, le32_to_cpu(pkt->server.node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The DSPs are star-connected through this node: forward packets
|
||||||
|
* destined to another node onto that node's endpoint (e.g. the
|
||||||
|
* modem's voice stack talking to the ADSP's audio service). Only
|
||||||
|
* DATA and RESUME_TX transit; control packets keep going to the
|
||||||
|
* local ns, which does its own mesh-wide redistribution. RESUME_TX
|
||||||
|
* additionally releases this hop's flow-control token on the
|
||||||
|
* arrival link: per-hop counters advance in lockstep with the
|
||||||
|
* end-to-end ones since every packet of the flow transits here.
|
||||||
|
*/
|
||||||
|
if (cb->dst_node != qrtr_local_nid &&
|
||||||
|
cb->dst_node != QRTR_NODE_BCAST &&
|
||||||
|
(cb->type == QRTR_TYPE_DATA || cb->type == QRTR_TYPE_RESUME_TX)) {
|
||||||
|
struct sockaddr_qrtr from = {AF_QIPCRTR,
|
||||||
|
cb->src_node, cb->src_port};
|
||||||
|
struct sockaddr_qrtr to = {AF_QIPCRTR,
|
||||||
|
cb->dst_node, cb->dst_port};
|
||||||
|
struct qrtr_node *dst;
|
||||||
|
|
||||||
|
dst = qrtr_node_lookup(cb->dst_node);
|
||||||
|
if (!dst || dst == node) {
|
||||||
|
if (dst)
|
||||||
|
qrtr_node_release(dst);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb->type == QRTR_TYPE_RESUME_TX) {
|
||||||
|
struct sk_buff *clone;
|
||||||
|
|
||||||
|
clone = skb_clone(skb, GFP_ATOMIC);
|
||||||
|
if (clone)
|
||||||
|
qrtr_tx_resume(node, clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug("qrtr: fwd %u:%u -> %u:%u type %d len %zu\n",
|
||||||
|
cb->src_node, cb->src_port,
|
||||||
|
cb->dst_node, cb->dst_port, cb->type, size);
|
||||||
|
|
||||||
|
if (skb_cow_head(skb, sizeof(struct qrtr_hdr_v1))) {
|
||||||
|
qrtr_node_release(dst);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
qrtr_node_enqueue(dst, skb, cb->type, &from, &to);
|
||||||
|
qrtr_node_release(dst);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (cb->type == QRTR_TYPE_RESUME_TX) {
|
if (cb->type == QRTR_TYPE_RESUME_TX) {
|
||||||
qrtr_tx_resume(node, skb);
|
qrtr_tx_resume(node, skb);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ struct qrtr_node {
|
||||||
unsigned int id;
|
unsigned int id;
|
||||||
struct xarray servers;
|
struct xarray servers;
|
||||||
u32 server_count;
|
u32 server_count;
|
||||||
|
bool hello;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Max nodes, server, lookup limits are chosen based on the current platform
|
/* Max nodes, server, lookup limits are chosen based on the current platform
|
||||||
|
|
@ -212,6 +213,60 @@ static void lookup_notify(struct sockaddr_qrtr *to, struct qrtr_server *srv,
|
||||||
pr_err("failed to send lookup notification\n");
|
pr_err("failed to send lookup notification\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FP6 voice bring-up: the DSP firmwares expect the downstream IPC-router
|
||||||
|
* flooding model - they never send NEW_LOOKUP for services on their peers
|
||||||
|
* and instead rely on the application processor pushing announcements to
|
||||||
|
* every link. The modem's voice stack locates the ADSP's service-registry
|
||||||
|
* notifier this way; without it Voice Services never sees avs/audio come
|
||||||
|
* up and never creates a vocoder session.
|
||||||
|
*
|
||||||
|
* Flooding every announcement to every node wedges the phone within
|
||||||
|
* seconds of the DSPs booting (control packets are exempt from qrtr flow
|
||||||
|
* control, so any echo between routers runs unbounded). Push exactly the
|
||||||
|
* one edge voice needs: ADSP-hosted servers, to the modem, only. Any echo
|
||||||
|
* is structurally loop-free: a re-announcement of an ADSP server arriving
|
||||||
|
* from the modem is pushed to nobody (target == origin).
|
||||||
|
*/
|
||||||
|
#define QRTR_NS_FWD_SRC_NODE 5 /* adsp */
|
||||||
|
#define QRTR_NS_FWD_DST_NODE 0 /* modem */
|
||||||
|
|
||||||
|
/* Runtime kill-switch (qrtr.fp6_ns_push): off by default so a boot is
|
||||||
|
* always calm; flip it on, then restart the modem rproc so its HELLO
|
||||||
|
* triggers the replay.
|
||||||
|
*/
|
||||||
|
static bool fp6_ns_push;
|
||||||
|
module_param(fp6_ns_push, bool, 0644);
|
||||||
|
MODULE_PARM_DESC(fp6_ns_push, "FP6: push adsp service announcements to the modem");
|
||||||
|
|
||||||
|
static void service_announce_remotes(struct qrtr_server *srv,
|
||||||
|
unsigned int origin, bool new)
|
||||||
|
{
|
||||||
|
struct sockaddr_qrtr sq;
|
||||||
|
struct qrtr_node *node;
|
||||||
|
|
||||||
|
if (!fp6_ns_push)
|
||||||
|
return;
|
||||||
|
if (srv->node != QRTR_NS_FWD_SRC_NODE)
|
||||||
|
return;
|
||||||
|
if (origin == QRTR_NS_FWD_DST_NODE ||
|
||||||
|
srv->node == QRTR_NS_FWD_DST_NODE ||
|
||||||
|
qrtr_ns.local_node == QRTR_NS_FWD_DST_NODE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
node = xa_load(&nodes, QRTR_NS_FWD_DST_NODE);
|
||||||
|
if (!node || !node->hello)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sq.sq_family = AF_QIPCRTR;
|
||||||
|
sq.sq_node = QRTR_NS_FWD_DST_NODE;
|
||||||
|
sq.sq_port = QRTR_PORT_CTRL;
|
||||||
|
|
||||||
|
if (new)
|
||||||
|
service_announce_new(&sq, srv);
|
||||||
|
else
|
||||||
|
service_announce_del(&sq, srv);
|
||||||
|
}
|
||||||
|
|
||||||
static int announce_servers(struct sockaddr_qrtr *sq)
|
static int announce_servers(struct sockaddr_qrtr *sq)
|
||||||
{
|
{
|
||||||
struct qrtr_server *srv;
|
struct qrtr_server *srv;
|
||||||
|
|
@ -234,6 +289,24 @@ static int announce_servers(struct sockaddr_qrtr *sq)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FP6 voice: replay the ADSP's servers to a (re)booting modem (see
|
||||||
|
* service_announce_remotes())
|
||||||
|
*/
|
||||||
|
if (fp6_ns_push && sq->sq_node == QRTR_NS_FWD_DST_NODE) {
|
||||||
|
node = xa_load(&nodes, QRTR_NS_FWD_SRC_NODE);
|
||||||
|
if (!node)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
xa_for_each(&node->servers, index, srv) {
|
||||||
|
ret = service_announce_new(sq, srv);
|
||||||
|
if (ret < 0 && ret != -ENODEV) {
|
||||||
|
pr_err("failed to announce new service\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,6 +381,8 @@ static int server_del(struct qrtr_node *node, unsigned int port, bool bcast)
|
||||||
/* Broadcast the removal of local servers */
|
/* Broadcast the removal of local servers */
|
||||||
if (srv->node == qrtr_ns.local_node && bcast)
|
if (srv->node == qrtr_ns.local_node && bcast)
|
||||||
service_announce_del(&qrtr_ns.bcast_sq, srv);
|
service_announce_del(&qrtr_ns.bcast_sq, srv);
|
||||||
|
else if (bcast)
|
||||||
|
service_announce_remotes(srv, srv->node, false);
|
||||||
|
|
||||||
/* Announce the service's disappearance to observers */
|
/* Announce the service's disappearance to observers */
|
||||||
list_for_each(li, &qrtr_ns.lookups) {
|
list_for_each(li, &qrtr_ns.lookups) {
|
||||||
|
|
@ -352,8 +427,13 @@ static int say_hello(struct sockaddr_qrtr *dest)
|
||||||
/* Announce the list of servers registered on the local node */
|
/* Announce the list of servers registered on the local node */
|
||||||
static int ctrl_cmd_hello(struct sockaddr_qrtr *sq)
|
static int ctrl_cmd_hello(struct sockaddr_qrtr *sq)
|
||||||
{
|
{
|
||||||
|
struct qrtr_node *node;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
node = node_get(sq->sq_node);
|
||||||
|
if (node)
|
||||||
|
node->hello = true;
|
||||||
|
|
||||||
ret = say_hello(sq);
|
ret = say_hello(sq);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -521,6 +601,8 @@ static int ctrl_cmd_new_server(struct sockaddr_qrtr *from,
|
||||||
pr_err("failed to announce new service\n");
|
pr_err("failed to announce new service\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
service_announce_remotes(srv, from->sq_node, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Notify any potential lookups about the new server */
|
/* Notify any potential lookups about the new server */
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,7 @@ config SND_SOC_ALL_CODECS
|
||||||
imply SND_SOC_WCD9335
|
imply SND_SOC_WCD9335
|
||||||
imply SND_SOC_WCD934X
|
imply SND_SOC_WCD934X
|
||||||
imply SND_SOC_WCD937X_SDW
|
imply SND_SOC_WCD937X_SDW
|
||||||
|
imply SND_SOC_WCD9378_SDW
|
||||||
imply SND_SOC_WCD938X_SDW
|
imply SND_SOC_WCD938X_SDW
|
||||||
imply SND_SOC_WCD939X_SDW
|
imply SND_SOC_WCD939X_SDW
|
||||||
imply SND_SOC_LPASS_MACRO_COMMON
|
imply SND_SOC_LPASS_MACRO_COMMON
|
||||||
|
|
@ -2431,6 +2432,24 @@ config SND_SOC_WCD937X_SDW
|
||||||
via soundwire.
|
via soundwire.
|
||||||
To compile this codec driver say Y or m.
|
To compile this codec driver say Y or m.
|
||||||
|
|
||||||
|
config SND_SOC_WCD9378
|
||||||
|
depends on SND_SOC_WCD9378_SDW
|
||||||
|
tristate
|
||||||
|
depends on SOUNDWIRE || !SOUNDWIRE
|
||||||
|
select SND_SOC_WCD_COMMON
|
||||||
|
|
||||||
|
config SND_SOC_WCD9378_SDW
|
||||||
|
tristate "WCD9378 Codec - SDW"
|
||||||
|
select SND_SOC_WCD9378
|
||||||
|
depends on SOUNDWIRE
|
||||||
|
select REGMAP_SOUNDWIRE
|
||||||
|
help
|
||||||
|
The WCD9378 is an audio codec IC connected to the host over
|
||||||
|
SoundWire, found on Qualcomm SM7635 boards such as the
|
||||||
|
Fairphone 6. It has three ADCs for analog microphones, mic
|
||||||
|
bias supplies, headphone/earpiece outputs and MBHC.
|
||||||
|
To compile this codec driver say Y or m.
|
||||||
|
|
||||||
config SND_SOC_WCD938X
|
config SND_SOC_WCD938X
|
||||||
depends on SND_SOC_WCD938X_SDW
|
depends on SND_SOC_WCD938X_SDW
|
||||||
tristate
|
tristate
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,8 @@ snd-soc-wcd9335-y := wcd9335.o
|
||||||
snd-soc-wcd934x-y := wcd934x.o
|
snd-soc-wcd934x-y := wcd934x.o
|
||||||
snd-soc-wcd937x-y := wcd937x.o
|
snd-soc-wcd937x-y := wcd937x.o
|
||||||
snd-soc-wcd937x-sdw-y := wcd937x-sdw.o
|
snd-soc-wcd937x-sdw-y := wcd937x-sdw.o
|
||||||
|
snd-soc-wcd9378-y := wcd9378.o
|
||||||
|
snd-soc-wcd9378-sdw-y := wcd9378-sdw.o
|
||||||
snd-soc-wcd938x-y := wcd938x.o
|
snd-soc-wcd938x-y := wcd938x.o
|
||||||
snd-soc-wcd938x-sdw-y := wcd938x-sdw.o
|
snd-soc-wcd938x-sdw-y := wcd938x-sdw.o
|
||||||
snd-soc-wcd939x-y := wcd939x.o
|
snd-soc-wcd939x-y := wcd939x.o
|
||||||
|
|
@ -796,6 +798,11 @@ ifdef CONFIG_SND_SOC_WCD937X_SDW
|
||||||
# avoid link failure by forcing sdw code built-in when needed
|
# avoid link failure by forcing sdw code built-in when needed
|
||||||
obj-$(CONFIG_SND_SOC_WCD937X) += snd-soc-wcd937x-sdw.o
|
obj-$(CONFIG_SND_SOC_WCD937X) += snd-soc-wcd937x-sdw.o
|
||||||
endif
|
endif
|
||||||
|
obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378.o
|
||||||
|
ifdef CONFIG_SND_SOC_WCD9378_SDW
|
||||||
|
# avoid link failure by forcing sdw code built-in when needed
|
||||||
|
obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378-sdw.o
|
||||||
|
endif
|
||||||
obj-$(CONFIG_SND_SOC_WCD938X) += snd-soc-wcd938x.o
|
obj-$(CONFIG_SND_SOC_WCD938X) += snd-soc-wcd938x.o
|
||||||
ifdef CONFIG_SND_SOC_WCD938X_SDW
|
ifdef CONFIG_SND_SOC_WCD938X_SDW
|
||||||
# avoid link failure by forcing sdw code built-in when needed
|
# avoid link failure by forcing sdw code built-in when needed
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@
|
||||||
|
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <sound/soc.h>
|
#include <sound/soc.h>
|
||||||
|
#include <sound/pcm_params.h>
|
||||||
|
#include <sound/tlv.h>
|
||||||
#include "aw88261.h"
|
#include "aw88261.h"
|
||||||
#include "aw88395/aw88395_data_type.h"
|
#include "aw88395/aw88395_data_type.h"
|
||||||
#include "aw88395/aw88395_device.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)
|
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 volume = min(value, (unsigned int)AW88261_MUTE_VOL);
|
||||||
unsigned int real_value, volume;
|
|
||||||
unsigned int reg_value;
|
|
||||||
|
|
||||||
volume = min((value + vol_desc->init_volume), (unsigned int)AW88261_MUTE_VOL);
|
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL2_REG,
|
||||||
real_value = DB_TO_REG_VAL(volume);
|
~AW88261_VOL_MASK, 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void aw88261_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag)
|
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)
|
static void aw88261_dev_mute(struct aw_device *aw_dev, bool is_mute)
|
||||||
{
|
{
|
||||||
if (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,
|
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
||||||
~AW88261_HMUTE_MASK, AW88261_HMUTE_ENABLE_VALUE);
|
~AW88261_HMUTE_MASK, AW88261_HMUTE_ENABLE_VALUE);
|
||||||
} else {
|
} else {
|
||||||
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
regmap_update_bits(aw_dev->regmap, AW88261_SYSCTRL_REG,
|
||||||
~AW88261_HMUTE_MASK, AW88261_HMUTE_DISABLE_VALUE);
|
~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)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
if ((reg_val & AW88261_BIT_PLL_CHECK) != AW88261_BIT_PLL_CHECK) {
|
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 -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
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;
|
int ret, i;
|
||||||
|
|
||||||
for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) {
|
for (i = 0; i < AW88261_DEV_SYSST_CHECK_MAX; i++) {
|
||||||
ret = aw88261_dev_get_iis_status(aw_dev);
|
ret = aw88261_dev_get_iis_status(aw_dev);
|
||||||
if (ret) {
|
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);
|
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||||
} else {
|
} else {
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -175,71 +124,74 @@ static int aw88261_dev_check_mode1_pll(struct aw_device *aw_dev)
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int aw88261_dev_check_mode2_pll(struct aw_device *aw_dev)
|
static int aw88261_dev_configure_syspll(struct aw88261 *aw88261)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
struct aw_device *aw_dev = aw88261->aw_pa;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = aw88261_dev_check_mode1_pll(aw_dev);
|
/* Configure TDM slots (I2S is represented as no slots) */
|
||||||
if (ret) {
|
ret = regmap_update_bits(aw_dev->regmap, AW88261_I2SCTRL2_REG,
|
||||||
dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check");
|
~AW88261_SLOT_NUM_MASK, aw88261->slot_num_value);
|
||||||
ret = aw88261_dev_check_mode2_pll(aw_dev);
|
if (ret)
|
||||||
if (ret) {
|
return ret;
|
||||||
dev_err(aw_dev->dev, "mode2 check iis failed");
|
|
||||||
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)
|
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;
|
return ret;
|
||||||
|
|
||||||
check_val = reg_val & (~AW88261_BIT_SYSST_CHECK_MASK)
|
check_val = reg_val & (~AW88261_BIT_SYSST_CHECK_MASK)
|
||||||
& AW88261_BIT_SYSST_CHECK;
|
& AW88261_BIT_PLL_CHECK;
|
||||||
if (check_val != AW88261_BIT_SYSST_CHECK) {
|
if (check_val != AW88261_BIT_PLL_CHECK) {
|
||||||
dev_err(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x",
|
dev_dbg(aw_dev->dev, "check sysst fail, reg_val=0x%04x, check:0x%x",
|
||||||
reg_val, AW88261_BIT_SYSST_CHECK);
|
reg_val, AW88261_BIT_PLL_CHECK);
|
||||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -284,22 +236,22 @@ static void aw88261_reg_force_set(struct aw88261 *aw88261)
|
||||||
if (aw88261->frcset_en == AW88261_FRCSET_ENABLE) {
|
if (aw88261->frcset_en == AW88261_FRCSET_ENABLE) {
|
||||||
/* set FORCE_PWM */
|
/* set FORCE_PWM */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL3_REG,
|
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 */
|
/* set BOOST_OS_WIDTH */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL5_REG,
|
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 */
|
/* set BURST_LOOPR */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL6_REG,
|
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 */
|
/* set RSQN_DLY */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL7_REG,
|
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 */
|
/* set BURST_SSMODE */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL8_REG,
|
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 */
|
/* set BST_BURST */
|
||||||
regmap_update_bits(aw88261->regmap, AW88261_BSTCTRL9_REG,
|
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 {
|
} else {
|
||||||
dev_dbg(aw88261->aw_pa->dev, "needn't set reg value");
|
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;
|
int ret;
|
||||||
|
|
||||||
if (aw_dev->status == AW88261_DEV_PW_ON) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,9 +510,9 @@ static int aw88261_dev_start(struct aw88261 *aw88261)
|
||||||
aw88261_dev_pwd(aw_dev, false);
|
aw88261_dev_pwd(aw_dev, false);
|
||||||
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
usleep_range(AW88261_2000_US, AW88261_2000_US + 10);
|
||||||
|
|
||||||
ret = aw88261_dev_check_syspll(aw_dev);
|
ret = aw88261_dev_configure_syspll(aw88261);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(aw_dev->dev, "pll check failed cannot start");
|
dev_dbg(aw_dev->dev, "pll check failed");
|
||||||
goto pll_check_fail;
|
goto pll_check_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -571,7 +523,7 @@ static int aw88261_dev_start(struct aw88261 *aw88261)
|
||||||
/* check i2s status */
|
/* check i2s status */
|
||||||
ret = aw88261_dev_check_sysst(aw_dev);
|
ret = aw88261_dev_check_sysst(aw_dev);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(aw_dev->dev, "sysst check failed");
|
dev_dbg(aw_dev->dev, "sysst check failed");
|
||||||
goto sysst_check_fail;
|
goto sysst_check_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -672,31 +624,25 @@ static void aw88261_start_pa(struct aw88261 *aw88261)
|
||||||
for (i = 0; i < AW88261_START_RETRIES; i++) {
|
for (i = 0; i < AW88261_START_RETRIES; i++) {
|
||||||
ret = aw88261_reg_update(aw88261, aw88261->phase_sync);
|
ret = aw88261_reg_update(aw88261, aw88261->phase_sync);
|
||||||
if (ret) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
ret = aw88261_dev_start(aw88261);
|
ret = aw88261_dev_start(aw88261);
|
||||||
if (ret) {
|
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;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
dev_info(aw88261->aw_pa->dev, "start success\n");
|
dev_dbg(aw88261->aw_pa->dev, "start success\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ret != 0)
|
||||||
|
dev_err(aw88261->aw_pa->dev, "start failure (%d)\n", ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void aw88261_startup_work(struct work_struct *work)
|
static void aw88261_start(struct aw88261 *aw88261)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (aw88261->aw_pa->fw_status != AW88261_DEV_FW_OK)
|
if (aw88261->aw_pa->fw_status != AW88261_DEV_FW_OK)
|
||||||
return;
|
return;
|
||||||
|
|
@ -704,14 +650,226 @@ static void aw88261_start(struct aw88261 *aw88261, bool sync_start)
|
||||||
if (aw88261->aw_pa->status == AW88261_DEV_PW_ON)
|
if (aw88261->aw_pa->status == AW88261_DEV_PW_ON)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (sync_start == AW88261_SYNC_START)
|
aw88261_start_pa(aw88261);
|
||||||
aw88261_start_pa(aw88261);
|
|
||||||
else
|
|
||||||
queue_delayed_work(system_dfl_wq,
|
|
||||||
&aw88261->start_work,
|
|
||||||
AW88261_START_WORK_DELAY_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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[] = {
|
static struct snd_soc_dai_driver aw88261_dai[] = {
|
||||||
{
|
{
|
||||||
.name = "aw88261-aif",
|
.name = "aw88261-aif",
|
||||||
|
|
@ -730,78 +888,10 @@ static struct snd_soc_dai_driver aw88261_dai[] = {
|
||||||
.rates = AW88261_RATES,
|
.rates = AW88261_RATES,
|
||||||
.formats = AW88261_FORMATS,
|
.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)
|
static int aw88261_dev_set_profile_index(struct aw_device *aw_dev, int index)
|
||||||
{
|
{
|
||||||
/* check the index whether is valid */
|
/* check the index whether is valid */
|
||||||
|
|
@ -880,7 +970,7 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol,
|
||||||
|
|
||||||
if (aw88261->aw_pa->status) {
|
if (aw88261->aw_pa->status) {
|
||||||
aw88261_dev_stop(aw88261->aw_pa);
|
aw88261_dev_stop(aw88261->aw_pa);
|
||||||
aw88261_start(aw88261, AW88261_SYNC_START);
|
aw88261_start(aw88261);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&aw88261->lock);
|
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 aw88261 *aw88261 = snd_soc_component_get_drvdata(codec);
|
||||||
struct aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc;
|
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;
|
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 aw_volume_desc *vol_desc = &aw88261->aw_pa->volume_desc;
|
||||||
struct soc_mixer_control *mc =
|
struct soc_mixer_control *mc =
|
||||||
(struct soc_mixer_control *)kcontrol->private_value;
|
(struct soc_mixer_control *)kcontrol->private_value;
|
||||||
int value;
|
int value = ucontrol->value.integer.value[0];
|
||||||
|
|
||||||
value = ucontrol->value.integer.value[0];
|
|
||||||
|
|
||||||
if (value < mc->min || value > mc->max)
|
if (value < mc->min || value > mc->max)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
value = AW88261_MUTE_VOL - (value * 2);
|
||||||
|
|
||||||
if (vol_desc->ctl_volume != value) {
|
if (vol_desc->ctl_volume != value) {
|
||||||
vol_desc->ctl_volume = value;
|
vol_desc->ctl_volume = value;
|
||||||
aw88261_dev_set_volume(aw88261->aw_pa, vol_desc->ctl_volume);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int aw88261_get_fade_step(struct snd_kcontrol *kcontrol,
|
/*
|
||||||
struct snd_ctl_elem_value *ucontrol)
|
* 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.
|
||||||
struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol);
|
*
|
||||||
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec);
|
* The range is clamped at -90dB to prevent overflowing the 4-bit part.
|
||||||
|
*/
|
||||||
ucontrol->value.integer.value[0] = aw88261->aw_pa->fade_step;
|
static const DECLARE_TLV_DB_SCALE(volume_tlv, -9000, 25, 0);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct snd_kcontrol_new aw88261_controls[] = {
|
static const struct snd_kcontrol_new aw88261_controls[] = {
|
||||||
SOC_SINGLE_EXT("PCM Playback Volume", AW88261_SYSCTRL2_REG,
|
SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG,
|
||||||
6, AW88261_MUTE_VOL, 0, aw88261_volume_get,
|
6, AW88261_CTL_MAX_VOL, 1,
|
||||||
aw88261_volume_set),
|
aw88261_volume_get, aw88261_volume_set, volume_tlv),
|
||||||
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),
|
|
||||||
AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info,
|
AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info,
|
||||||
aw88261_profile_get, aw88261_profile_set),
|
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);
|
mutex_lock(&aw88261->lock);
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case SND_SOC_DAPM_PRE_PMU:
|
case SND_SOC_DAPM_PRE_PMU:
|
||||||
aw88261_start(aw88261, AW88261_ASYNC_START);
|
aw88261_start(aw88261);
|
||||||
break;
|
break;
|
||||||
case SND_SOC_DAPM_POST_PMD:
|
case SND_SOC_DAPM_POST_PMD:
|
||||||
aw88261_dev_stop(aw88261->aw_pa);
|
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)
|
if (ret)
|
||||||
return 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_cur = AW88261_INIT_PROFILE;
|
||||||
aw_dev->prof_index = 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)
|
static int aw88261_request_firmware_file(struct aw88261 *aw88261)
|
||||||
{
|
{
|
||||||
const struct firmware *cont = NULL;
|
const struct firmware *cont = NULL;
|
||||||
|
struct aw_container *aw_cfg;
|
||||||
const char *fw_name;
|
const char *fw_name;
|
||||||
int ret;
|
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",
|
dev_info(aw88261->aw_pa->dev, "loaded %s - size: %zu\n",
|
||||||
fw_name, cont ? cont->size : 0);
|
fw_name, cont ? cont->size : 0);
|
||||||
|
|
||||||
aw88261->aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, cont->size + sizeof(int), GFP_KERNEL);
|
aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL);
|
||||||
if (!aw88261->aw_cfg) {
|
if (!aw_cfg) {
|
||||||
release_firmware(cont);
|
release_firmware(cont);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
aw88261->aw_cfg->len = (int)cont->size;
|
aw_cfg->len = (int)cont->size;
|
||||||
memcpy(aw88261->aw_cfg->data, cont->data, cont->size);
|
memcpy(aw_cfg->data, cont->data, cont->size);
|
||||||
release_firmware(cont);
|
release_firmware(cont);
|
||||||
|
|
||||||
|
aw88261->aw_cfg = aw_cfg;
|
||||||
|
|
||||||
ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg);
|
ret = aw88395_dev_load_acf_check(aw88261->aw_pa, aw88261->aw_cfg);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(aw88261->aw_pa->dev, "load [%s] failed !", fw_name);
|
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);
|
struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
INIT_DELAYED_WORK(&aw88261->start_work, aw88261_startup_work);
|
|
||||||
|
|
||||||
ret = aw88261_request_firmware_file(aw88261);
|
ret = aw88261_request_firmware_file(aw88261);
|
||||||
if (ret)
|
if (ret)
|
||||||
return dev_err_probe(aw88261->aw_pa->dev, 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;
|
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 = {
|
static const struct snd_soc_component_driver soc_codec_dev_aw88261 = {
|
||||||
.probe = aw88261_codec_probe,
|
.probe = aw88261_codec_probe,
|
||||||
.remove = aw88261_codec_remove,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void aw88261_parse_channel_dt(struct aw88261 *aw88261)
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
if (chip_id != AW88261_CHIP_ID) {
|
if (chip_id != AW88261_CHIP_ID) {
|
||||||
dev_err(&i2c->dev, "unsupported device");
|
dev_err(&i2c->dev, "unsupported device id = %x", chip_id);
|
||||||
return -ENXIO;
|
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->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID;
|
||||||
aw_dev->channel = 0;
|
aw_dev->channel = 0;
|
||||||
aw_dev->fw_status = AW88261_DEV_FW_FAILED;
|
aw_dev->fw_status = AW88261_DEV_FW_FAILED;
|
||||||
aw_dev->fade_step = AW88261_VOLUME_STEP_DB;
|
aw_dev->volume_desc.ctl_volume = AW88261_CTL_DEFAULT_VOL;
|
||||||
aw_dev->volume_desc.ctl_volume = AW88261_VOL_DEFAULT_VALUE;
|
|
||||||
aw_dev->volume_desc.mute_volume = AW88261_MUTE_VOL;
|
aw_dev->volume_desc.mute_volume = AW88261_MUTE_VOL;
|
||||||
aw88261_parse_channel_dt(aw88261);
|
aw88261_parse_channel_dt(aw88261);
|
||||||
|
|
||||||
|
|
@ -1249,6 +1300,17 @@ static int aw88261_i2c_probe(struct i2c_client *i2c)
|
||||||
if (!aw88261)
|
if (!aw88261)
|
||||||
return -ENOMEM;
|
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);
|
mutex_init(&aw88261->lock);
|
||||||
|
|
||||||
i2c_set_clientdata(i2c, aw88261);
|
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[] = {
|
static const struct i2c_device_id aw88261_i2c_id[] = {
|
||||||
{ "aw88261" },
|
{ .name = "aw88261" },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, aw88261_i2c_id);
|
MODULE_DEVICE_TABLE(i2c, aw88261_i2c_id);
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,19 @@
|
||||||
#define AW88261_VCALK_SHIFT (0)
|
#define AW88261_VCALK_SHIFT (0)
|
||||||
#define AW88261_VCALKL_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_START_BIT (1)
|
||||||
#define AW88261_AMPPD_BITS_LEN (1)
|
#define AW88261_AMPPD_BITS_LEN (1)
|
||||||
#define AW88261_AMPPD_MASK \
|
#define AW88261_AMPPD_MASK \
|
||||||
|
|
@ -181,12 +194,6 @@
|
||||||
AW88261_OTHS_OT_VALUE | \
|
AW88261_OTHS_OT_VALUE | \
|
||||||
AW88261_PLLS_LOCKED_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_START_BIT (14)
|
||||||
#define AW88261_ULS_HMUTE_BITS_LEN (1)
|
#define AW88261_ULS_HMUTE_BITS_LEN (1)
|
||||||
#define AW88261_ULS_HMUTE_MASK \
|
#define AW88261_ULS_HMUTE_MASK \
|
||||||
|
|
@ -249,7 +256,8 @@
|
||||||
#define AW88261_VOL_MASK \
|
#define AW88261_VOL_MASK \
|
||||||
(~(((1<<AW88261_VOL_BITS_LEN)-1) << AW88261_VOL_START_BIT))
|
(~(((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_START_BIT (6)
|
||||||
#define AW88261_I2STXEN_BITS_LEN (1)
|
#define AW88261_I2STXEN_BITS_LEN (1)
|
||||||
|
|
@ -264,7 +272,148 @@
|
||||||
#define AW88261_I2STXEN_ENABLE_VALUE \
|
#define AW88261_I2STXEN_ENABLE_VALUE \
|
||||||
(AW88261_I2STXEN_ENABLE << AW88261_I2STXEN_START_BIT)
|
(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_BITS_LEN (1)
|
||||||
#define AW88261_CCO_MUX_MASK \
|
#define AW88261_CCO_MUX_MASK \
|
||||||
(~(((1<<AW88261_CCO_MUX_BITS_LEN)-1) << AW88261_CCO_MUX_START_BIT))
|
(~(((1<<AW88261_CCO_MUX_BITS_LEN)-1) << AW88261_CCO_MUX_START_BIT))
|
||||||
|
|
@ -370,21 +519,20 @@
|
||||||
#define AW88261_START_RETRIES (5)
|
#define AW88261_START_RETRIES (5)
|
||||||
#define AW88261_START_WORK_DELAY_MS (0)
|
#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 | \
|
#define AW88261_RATES (SNDRV_PCM_RATE_8000_48000 | \
|
||||||
|
SNDRV_PCM_RATE_12000 | \
|
||||||
|
SNDRV_PCM_RATE_24000 | \
|
||||||
SNDRV_PCM_RATE_96000)
|
SNDRV_PCM_RATE_96000)
|
||||||
#define AW88261_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
#define AW88261_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
|
||||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||||
SNDRV_PCM_FMTBIT_S32_LE)
|
SNDRV_PCM_FMTBIT_S32_LE)
|
||||||
|
|
||||||
#define FADE_TIME_MAX 100000
|
|
||||||
#define FADE_TIME_MIN 0
|
|
||||||
|
|
||||||
#define AW88261_DEV_DEFAULT_CH (0)
|
#define AW88261_DEV_DEFAULT_CH (0)
|
||||||
#define AW88261_ACF_FILE "aw88261_acf.bin"
|
#define AW88261_ACF_FILE "aw88261_acf.bin"
|
||||||
#define AW88261_DEV_SYSST_CHECK_MAX (10)
|
#define AW88261_DEV_SYSST_CHECK_MAX (10)
|
||||||
#define AW88261_SOFT_RESET_VALUE (0x55aa)
|
#define AW88261_SOFT_RESET_VALUE (0x55aa)
|
||||||
#define AW88261_REG_TO_DB (0x3f)
|
#define AW88261_REG_TO_DB (0x3f)
|
||||||
#define AW88261_VOL_START_MASK (0xfc00)
|
|
||||||
#define AW88261_INIT_PROFILE (0)
|
#define AW88261_INIT_PROFILE (0)
|
||||||
|
|
||||||
#define REG_VAL_TO_DB(value) ((((value) >> AW88261_VOL_6DB_START) * \
|
#define REG_VAL_TO_DB(value) ((((value) >> AW88261_VOL_6DB_START) * \
|
||||||
|
|
@ -403,11 +551,6 @@
|
||||||
.put = profile_set, \
|
.put = profile_set, \
|
||||||
}
|
}
|
||||||
|
|
||||||
enum {
|
|
||||||
AW88261_SYNC_START = 0,
|
|
||||||
AW88261_ASYNC_START,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum aw88261_id {
|
enum aw88261_id {
|
||||||
AW88261_CHIP_ID = 0x2113,
|
AW88261_CHIP_ID = 0x2113,
|
||||||
};
|
};
|
||||||
|
|
@ -442,7 +585,6 @@ struct aw88261 {
|
||||||
struct aw_device *aw_pa;
|
struct aw_device *aw_pa;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
struct gpio_desc *reset_gpio;
|
struct gpio_desc *reset_gpio;
|
||||||
struct delayed_work start_work;
|
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct aw_container *aw_cfg;
|
struct aw_container *aw_cfg;
|
||||||
|
|
||||||
|
|
@ -451,6 +593,19 @@ struct aw88261 {
|
||||||
unsigned int mute_st;
|
unsigned int mute_st;
|
||||||
unsigned int amppd_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;
|
bool phase_sync;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
495
sound/soc/codecs/wcd9378-sdw.c
Normal file
495
sound/soc/codecs/wcd9378-sdw.c
Normal file
|
|
@ -0,0 +1,495 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026, Jorijn van der Graaf
|
||||||
|
*
|
||||||
|
* SoundWire slave driver for the Qualcomm WCD9378 audio codec.
|
||||||
|
*
|
||||||
|
* The codec presents two SoundWire slaves (RX and TX, mfg 0x0217 part
|
||||||
|
* 0x0110); the SDCA control space is a 32-bit paged register map accessed
|
||||||
|
* through the TX slave.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/component.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/soundwire/sdw.h>
|
||||||
|
#include <linux/soundwire/sdw_registers.h>
|
||||||
|
#include <linux/soundwire/sdw_type.h>
|
||||||
|
#include <sound/pcm_params.h>
|
||||||
|
|
||||||
|
#include "wcd-common.h"
|
||||||
|
#include "wcd9378.h"
|
||||||
|
|
||||||
|
#define WCD9378_SDW_CH(id, pn, cmask, mmask) \
|
||||||
|
[id] = { \
|
||||||
|
.port_num = pn, \
|
||||||
|
.ch_mask = cmask, \
|
||||||
|
.master_ch_mask = mmask, \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each ADC sits alone on its own TX device port (channel 1); by default
|
||||||
|
* they land on channels 1/2/3 of the same master port (SWRM_TX1 on the
|
||||||
|
* FP6). DMIC/MBHC masks per the downstream qcom,tx_swr_ch_map.
|
||||||
|
*/
|
||||||
|
static struct wcd_sdw_ch_info wcd9378_sdw_tx_ch_info[] = {
|
||||||
|
WCD9378_SDW_CH(WCD9378_ADC1, WCD9378_ADC_1_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_ADC2, WCD9378_ADC_2_PORT, BIT(0), BIT(1)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_ADC3, WCD9378_ADC_3_PORT, BIT(0), BIT(2)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC0, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC1, WCD9378_DMIC_0_1_MBHC_PORT, BIT(3), BIT(1)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_MBHC, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(2)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC2, WCD9378_DMIC_2_5_PORT, BIT(0), BIT(2)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC3, WCD9378_DMIC_2_5_PORT, BIT(1), BIT(3)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC4, WCD9378_DMIC_2_5_PORT, BIT(2), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DMIC5, WCD9378_DMIC_2_5_PORT, BIT(3), BIT(1)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct wcd_sdw_ch_info wcd9378_sdw_rx_ch_info[] = {
|
||||||
|
WCD9378_SDW_CH(WCD9378_HPH_L, WCD9378_HPH_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_HPH_R, WCD9378_HPH_PORT, BIT(1), BIT(1)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_CLSH, WCD9378_CLSH_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_COMP_L, WCD9378_COMP_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_COMP_R, WCD9378_COMP_PORT, BIT(1), BIT(1)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_LO, WCD9378_LO_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DSD_L, WCD9378_DSD_PORT, BIT(0), BIT(0)),
|
||||||
|
WCD9378_SDW_CH(WCD9378_DSD_R, WCD9378_DSD_PORT, BIT(1), BIT(1)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sdw_dpn_prop wcd9378_dpn_prop[WCD9378_MAX_SWR_PORTS] = {
|
||||||
|
{
|
||||||
|
.num = 1,
|
||||||
|
.type = SDW_DPN_SIMPLE,
|
||||||
|
.min_ch = 1,
|
||||||
|
.max_ch = 8,
|
||||||
|
.simple_ch_prep_sm = true,
|
||||||
|
}, {
|
||||||
|
.num = 2,
|
||||||
|
.type = SDW_DPN_SIMPLE,
|
||||||
|
.min_ch = 1,
|
||||||
|
.max_ch = 4,
|
||||||
|
.simple_ch_prep_sm = true,
|
||||||
|
}, {
|
||||||
|
.num = 3,
|
||||||
|
.type = SDW_DPN_SIMPLE,
|
||||||
|
.min_ch = 1,
|
||||||
|
.max_ch = 4,
|
||||||
|
.simple_ch_prep_sm = true,
|
||||||
|
}, {
|
||||||
|
.num = 4,
|
||||||
|
.type = SDW_DPN_SIMPLE,
|
||||||
|
.min_ch = 1,
|
||||||
|
.max_ch = 4,
|
||||||
|
.simple_ch_prep_sm = true,
|
||||||
|
}, {
|
||||||
|
.num = 5,
|
||||||
|
.type = SDW_DPN_SIMPLE,
|
||||||
|
.min_ch = 1,
|
||||||
|
.max_ch = 4,
|
||||||
|
.simple_ch_prep_sm = true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
|
||||||
|
struct snd_pcm_substream *substream,
|
||||||
|
struct snd_pcm_hw_params *params,
|
||||||
|
struct snd_soc_dai *dai)
|
||||||
|
{
|
||||||
|
struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS];
|
||||||
|
unsigned long ch_mask;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
wcd->sconfig.ch_count = 1;
|
||||||
|
wcd->active_ports = 0;
|
||||||
|
for (i = 0; i < WCD9378_MAX_SWR_PORTS; i++) {
|
||||||
|
ch_mask = wcd->port_config[i].ch_mask;
|
||||||
|
if (!ch_mask)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for_each_set_bit(j, &ch_mask, 4)
|
||||||
|
wcd->sconfig.ch_count++;
|
||||||
|
|
||||||
|
port_config[wcd->active_ports] = wcd->port_config[i];
|
||||||
|
wcd->active_ports++;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcd->sconfig.bps = 1;
|
||||||
|
wcd->sconfig.frame_rate = params_rate(params);
|
||||||
|
wcd->sconfig.direction = wcd->is_tx ? SDW_DATA_DIR_TX : SDW_DATA_DIR_RX;
|
||||||
|
wcd->sconfig.type = SDW_STREAM_PCM;
|
||||||
|
|
||||||
|
return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig,
|
||||||
|
&port_config[0], wcd->active_ports,
|
||||||
|
wcd->sruntime);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(wcd9378_sdw_hw_params);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tell the codec the bus clock: base 19.2 MHz plus a scale (div) per bank.
|
||||||
|
* The downstream driver writes these raw SCP registers on every capture
|
||||||
|
* start; here the bus_config callback covers bank switches.
|
||||||
|
*/
|
||||||
|
static int wcd9378_bus_config(struct sdw_slave *slave,
|
||||||
|
struct sdw_bus_params *params)
|
||||||
|
{
|
||||||
|
u8 scale;
|
||||||
|
|
||||||
|
switch (params->curr_dr_freq) {
|
||||||
|
case 4800000:
|
||||||
|
scale = WCD9378_SWRS_CLK_SCALE_DIV4;
|
||||||
|
break;
|
||||||
|
case 9600000:
|
||||||
|
default:
|
||||||
|
scale = WCD9378_SWRS_CLK_SCALE_DIV2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdw_write(slave, WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(params->next_bank),
|
||||||
|
0x01);
|
||||||
|
sdw_write(slave, WCD9378_SWRS_SCP_BASE_CLK,
|
||||||
|
WCD9378_SWRS_BASE_CLK_19P2MHZ);
|
||||||
|
sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0, scale);
|
||||||
|
sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1, scale);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct sdw_slave_ops wcd9378_slave_ops = {
|
||||||
|
.update_status = wcd_update_status,
|
||||||
|
.bus_config = wcd9378_bus_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct reg_default wcd9378_defaults[] = {
|
||||||
|
{ WCD9378_ANA_BIAS, 0x00 },
|
||||||
|
{ WCD9378_ANA_TX_CH1, 0x20 },
|
||||||
|
{ WCD9378_ANA_TX_CH2, 0x00 },
|
||||||
|
{ WCD9378_ANA_TX_CH3, 0x20 },
|
||||||
|
{ WCD9378_ANA_TX_CH3_HPF, 0x00 },
|
||||||
|
{ WCD9378_ANA_MICB2_RAMP, 0x00 },
|
||||||
|
{ WCD9378_BIAS_VBG_FINE_ADJ, 0x55 },
|
||||||
|
{ WCD9378_MBHC_CTL_SPARE_1, 0x02 },
|
||||||
|
{ WCD9378_MICB1_TEST_CTL_2, 0x00 },
|
||||||
|
{ WCD9378_MICB2_TEST_CTL_2, 0x00 },
|
||||||
|
{ WCD9378_MICB3_TEST_CTL_2, 0x80 },
|
||||||
|
{ WCD9378_TX_COM_TXFE_DIV_CTL, 0x22 },
|
||||||
|
{ WCD9378_SLEEP_CTL, 0x16 },
|
||||||
|
{ WCD9378_TX_NEW_CH12_MUX, 0x11 },
|
||||||
|
{ WCD9378_TX_NEW_CH34_MUX, 0x23 },
|
||||||
|
{ WCD9378_TOP_CLK_CFG, 0x00 },
|
||||||
|
{ WCD9378_CDC_ANA_TX_CLK_CTL, 0x0e },
|
||||||
|
{ WCD9378_CDC_AMIC_CTL, 0x07 },
|
||||||
|
{ WCD9378_PDM_WD_CTL0, 0x0f },
|
||||||
|
{ WCD9378_PDM_WD_CTL1, 0x0f },
|
||||||
|
{ WCD9378_PLATFORM_CTL, 0x01 },
|
||||||
|
{ WCD9378_SYS_USAGE_CTRL, 0x00 },
|
||||||
|
{ WCD9378_HPH_UP_T0, 0x02 },
|
||||||
|
{ WCD9378_HPH_UP_T9, 0x02 },
|
||||||
|
{ WCD9378_HPH_DN_T0, 0x05 },
|
||||||
|
{ WCD9378_MICB_REMAP_TABLE_VAL_3, 0x00 },
|
||||||
|
{ WCD9378_MICB_REMAP_TABLE_VAL_4, 0x00 },
|
||||||
|
{ WCD9378_MICB_REMAP_TABLE_VAL_5, 0x00 },
|
||||||
|
{ WCD9378_SM0_MB_SEL, 0x00 },
|
||||||
|
{ WCD9378_SM1_MB_SEL, 0x00 },
|
||||||
|
{ WCD9378_SM2_MB_SEL, 0x00 },
|
||||||
|
{ WCD9378_MB_PULLUP_EN, 0x00 },
|
||||||
|
{ WCD9378_SMP_AMP_FUNC_ACT, 0x00 },
|
||||||
|
{ WCD9378_CMT_GRP_MASK, 0x00 },
|
||||||
|
{ WCD9378_SMP_JACK_IT31_MICB, 0x00 },
|
||||||
|
{ WCD9378_SMP_JACK_IT31_USAGE, 0x03 },
|
||||||
|
{ WCD9378_SMP_JACK_PDE34_REQ_PS, 0x03 },
|
||||||
|
{ WCD9378_SMP_JACK_FUNC_ACT, 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_MICB(0), 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_USAGE(0), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_PDE11_REQ_PS(0), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_FUNC_ACT(0), 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_MICB(1), 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_USAGE(1), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_PDE11_REQ_PS(1), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_FUNC_ACT(1), 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_MICB(2), 0x00 },
|
||||||
|
{ WCD9378_SMP_MIC_IT11_USAGE(2), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_PDE11_REQ_PS(2), 0x03 },
|
||||||
|
{ WCD9378_SMP_MIC_FUNC_ACT(2), 0x00 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool wcd9378_rdwr_register(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case WCD9378_ANA_BIAS:
|
||||||
|
case WCD9378_ANA_TX_CH1:
|
||||||
|
case WCD9378_ANA_TX_CH2:
|
||||||
|
case WCD9378_ANA_TX_CH3:
|
||||||
|
case WCD9378_ANA_TX_CH3_HPF:
|
||||||
|
case WCD9378_ANA_MICB2_RAMP:
|
||||||
|
case WCD9378_BIAS_VBG_FINE_ADJ:
|
||||||
|
case WCD9378_MBHC_CTL_SPARE_1:
|
||||||
|
case WCD9378_MICB1_TEST_CTL_2:
|
||||||
|
case WCD9378_MICB2_TEST_CTL_2:
|
||||||
|
case WCD9378_MICB3_TEST_CTL_2:
|
||||||
|
case WCD9378_TX_COM_TXFE_DIV_CTL:
|
||||||
|
case WCD9378_SLEEP_CTL:
|
||||||
|
case WCD9378_TX_NEW_CH12_MUX:
|
||||||
|
case WCD9378_TX_NEW_CH34_MUX:
|
||||||
|
case WCD9378_HPH_RDAC_GAIN_CTL:
|
||||||
|
case WCD9378_HPH_RDAC_HD2_CTL_L:
|
||||||
|
case WCD9378_HPH_RDAC_HD2_CTL_R:
|
||||||
|
case WCD9378_TOP_CLK_CFG:
|
||||||
|
case WCD9378_CDC_ANA_TX_CLK_CTL:
|
||||||
|
case WCD9378_CDC_AMIC_CTL:
|
||||||
|
case WCD9378_PDM_WD_CTL0:
|
||||||
|
case WCD9378_PDM_WD_CTL1:
|
||||||
|
case WCD9378_PLATFORM_CTL:
|
||||||
|
case WCD9378_SYS_USAGE_CTRL:
|
||||||
|
case WCD9378_HPH_UP_T0:
|
||||||
|
case WCD9378_HPH_UP_T9:
|
||||||
|
case WCD9378_HPH_DN_T0:
|
||||||
|
case WCD9378_MICB_REMAP_TABLE_VAL_3:
|
||||||
|
case WCD9378_MICB_REMAP_TABLE_VAL_4:
|
||||||
|
case WCD9378_MICB_REMAP_TABLE_VAL_5:
|
||||||
|
case WCD9378_SM0_MB_SEL:
|
||||||
|
case WCD9378_SM1_MB_SEL:
|
||||||
|
case WCD9378_SM2_MB_SEL:
|
||||||
|
case WCD9378_MB_PULLUP_EN:
|
||||||
|
case WCD9378_SMP_AMP_FUNC_STAT:
|
||||||
|
case WCD9378_SMP_AMP_FUNC_ACT:
|
||||||
|
case WCD9378_CMT_GRP_MASK:
|
||||||
|
case WCD9378_SMP_JACK_IT31_MICB:
|
||||||
|
case WCD9378_SMP_JACK_IT31_USAGE:
|
||||||
|
case WCD9378_SMP_JACK_PDE34_REQ_PS:
|
||||||
|
case WCD9378_SMP_JACK_FUNC_STAT:
|
||||||
|
case WCD9378_SMP_JACK_FUNC_ACT:
|
||||||
|
case WCD9378_SMP_MIC_IT11_MICB(0):
|
||||||
|
case WCD9378_SMP_MIC_IT11_USAGE(0):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_REQ_PS(0):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(0):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_ACT(0):
|
||||||
|
case WCD9378_SMP_MIC_IT11_MICB(1):
|
||||||
|
case WCD9378_SMP_MIC_IT11_USAGE(1):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_REQ_PS(1):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(1):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_ACT(1):
|
||||||
|
case WCD9378_SMP_MIC_IT11_MICB(2):
|
||||||
|
case WCD9378_SMP_MIC_IT11_USAGE(2):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_REQ_PS(2):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(2):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_ACT(2):
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool wcd9378_volatile_register(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case WCD9378_FUNC_EXT_ID_0:
|
||||||
|
case WCD9378_FUNC_EXT_ID_1:
|
||||||
|
case WCD9378_FUNC_EXT_VER:
|
||||||
|
case WCD9378_FUNC_STAT:
|
||||||
|
case WCD9378_DEV_MANU_ID_0:
|
||||||
|
case WCD9378_DEV_MANU_ID_1:
|
||||||
|
case WCD9378_DEV_PART_ID_0:
|
||||||
|
case WCD9378_DEV_PART_ID_1:
|
||||||
|
case WCD9378_DEV_VER:
|
||||||
|
case WCD9378_EFUSE_REG_16:
|
||||||
|
case WCD9378_EFUSE_REG_29:
|
||||||
|
case WCD9378_SEQ_TX0_STAT:
|
||||||
|
case WCD9378_SEQ_TX1_STAT:
|
||||||
|
case WCD9378_SEQ_TX2_STAT:
|
||||||
|
case WCD9378_SMP_AMP_FUNC_STAT:
|
||||||
|
case WCD9378_SMP_JACK_FUNC_STAT:
|
||||||
|
case WCD9378_SMP_JACK_PDE34_ACT_PS:
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(0):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(1):
|
||||||
|
case WCD9378_SMP_MIC_FUNC_STAT(2):
|
||||||
|
case WCD9378_SMP_MIC_OT10_USAGE(0):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_ACT_PS(0):
|
||||||
|
case WCD9378_SMP_MIC_OT10_USAGE(1):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_ACT_PS(1):
|
||||||
|
case WCD9378_SMP_MIC_OT10_USAGE(2):
|
||||||
|
case WCD9378_SMP_MIC_PDE11_ACT_PS(2):
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool wcd9378_readable_register(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
if (wcd9378_volatile_register(dev, reg))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return wcd9378_rdwr_register(dev, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct regmap_config wcd9378_regmap_config = {
|
||||||
|
.name = "wcd9378_csr",
|
||||||
|
.reg_bits = 32,
|
||||||
|
.val_bits = 8,
|
||||||
|
.cache_type = REGCACHE_MAPLE,
|
||||||
|
.reg_defaults = wcd9378_defaults,
|
||||||
|
.num_reg_defaults = ARRAY_SIZE(wcd9378_defaults),
|
||||||
|
.max_register = WCD9378_MAX_REGISTER,
|
||||||
|
.readable_reg = wcd9378_readable_register,
|
||||||
|
.writeable_reg = wcd9378_rdwr_register,
|
||||||
|
.volatile_reg = wcd9378_volatile_register,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int wcd9378_sdw_probe(struct sdw_slave *pdev,
|
||||||
|
const struct sdw_device_id *id)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct wcd9378_sdw_priv *wcd;
|
||||||
|
u8 master_ch_mask[WCD9378_MAX_SWR_CH_IDS];
|
||||||
|
int master_ch_mask_size = 0;
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL);
|
||||||
|
if (!wcd)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Port map index starts at 0, however the data ports start at index 1 */
|
||||||
|
if (of_property_present(dev->of_node, "qcom,tx-port-mapping")) {
|
||||||
|
wcd->is_tx = true;
|
||||||
|
ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping",
|
||||||
|
&pdev->m_port_map[1],
|
||||||
|
WCD9378_MAX_TX_SWR_PORTS);
|
||||||
|
} else {
|
||||||
|
ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping",
|
||||||
|
&pdev->m_port_map[1],
|
||||||
|
WCD9378_MAX_SWR_PORTS);
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
dev_info(dev, "Error getting static port mapping for %s (%d)\n",
|
||||||
|
wcd->is_tx ? "TX" : "RX", ret);
|
||||||
|
|
||||||
|
wcd->sdev = pdev;
|
||||||
|
dev_set_drvdata(dev, wcd);
|
||||||
|
|
||||||
|
pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH |
|
||||||
|
SDW_SCP_INT1_PARITY;
|
||||||
|
pdev->prop.lane_control_support = true;
|
||||||
|
pdev->prop.simple_clk_stop_capable = true;
|
||||||
|
/* The SDCA control space sits above the 16-bit address range */
|
||||||
|
pdev->prop.paging_support = true;
|
||||||
|
|
||||||
|
memset(master_ch_mask, 0, WCD9378_MAX_SWR_CH_IDS);
|
||||||
|
|
||||||
|
if (wcd->is_tx) {
|
||||||
|
master_ch_mask_size = of_property_count_u8_elems(dev->of_node,
|
||||||
|
"qcom,tx-channel-mapping");
|
||||||
|
master_ch_mask_size = min_t(int, master_ch_mask_size,
|
||||||
|
ARRAY_SIZE(wcd9378_sdw_tx_ch_info));
|
||||||
|
|
||||||
|
if (master_ch_mask_size > 0)
|
||||||
|
ret = of_property_read_u8_array(dev->of_node,
|
||||||
|
"qcom,tx-channel-mapping",
|
||||||
|
master_ch_mask,
|
||||||
|
master_ch_mask_size);
|
||||||
|
} else {
|
||||||
|
master_ch_mask_size = of_property_count_u8_elems(dev->of_node,
|
||||||
|
"qcom,rx-channel-mapping");
|
||||||
|
master_ch_mask_size = min_t(int, master_ch_mask_size,
|
||||||
|
ARRAY_SIZE(wcd9378_sdw_rx_ch_info));
|
||||||
|
|
||||||
|
if (master_ch_mask_size > 0)
|
||||||
|
ret = of_property_read_u8_array(dev->of_node,
|
||||||
|
"qcom,rx-channel-mapping",
|
||||||
|
master_ch_mask,
|
||||||
|
master_ch_mask_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcd->is_tx) {
|
||||||
|
pdev->prop.source_ports = GENMASK(WCD9378_MAX_TX_SWR_PORTS, 1);
|
||||||
|
pdev->prop.src_dpn_prop = wcd9378_dpn_prop;
|
||||||
|
wcd->ch_info = &wcd9378_sdw_tx_ch_info[0];
|
||||||
|
|
||||||
|
for (i = 0; i < master_ch_mask_size; i++)
|
||||||
|
wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]);
|
||||||
|
|
||||||
|
pdev->prop.wake_capable = true;
|
||||||
|
|
||||||
|
wcd->regmap = devm_regmap_init_sdw(pdev, &wcd9378_regmap_config);
|
||||||
|
if (IS_ERR(wcd->regmap))
|
||||||
|
return dev_err_probe(dev, PTR_ERR(wcd->regmap),
|
||||||
|
"Regmap init failed\n");
|
||||||
|
|
||||||
|
/* Start in cache-only until device is enumerated */
|
||||||
|
regcache_cache_only(wcd->regmap, true);
|
||||||
|
} else {
|
||||||
|
pdev->prop.sink_ports = GENMASK(WCD9378_MAX_SWR_PORTS, 1);
|
||||||
|
pdev->prop.sink_dpn_prop = wcd9378_dpn_prop;
|
||||||
|
wcd->ch_info = &wcd9378_sdw_rx_ch_info[0];
|
||||||
|
|
||||||
|
for (i = 0; i < master_ch_mask_size; i++)
|
||||||
|
wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = component_add(dev, &wcd_sdw_component_ops);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Set suspended until aggregate device is bind */
|
||||||
|
pm_runtime_set_suspended(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wcd9378_sdw_remove(struct sdw_slave *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
|
||||||
|
component_del(dev, &wcd_sdw_component_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct sdw_device_id wcd9378_sdw_id[] = {
|
||||||
|
SDW_SLAVE_ENTRY(0x0217, 0x0110, 0),
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id);
|
||||||
|
|
||||||
|
static int wcd9378_sdw_runtime_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (wcd->regmap) {
|
||||||
|
regcache_cache_only(wcd->regmap, true);
|
||||||
|
regcache_mark_dirty(wcd->regmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wcd9378_sdw_runtime_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (wcd->regmap) {
|
||||||
|
regcache_cache_only(wcd->regmap, false);
|
||||||
|
regcache_sync(wcd->regmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct dev_pm_ops wcd9378_sdw_pm_ops = {
|
||||||
|
RUNTIME_PM_OPS(wcd9378_sdw_runtime_suspend, wcd9378_sdw_runtime_resume, NULL)
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sdw_driver wcd9378_sdw_driver = {
|
||||||
|
.probe = wcd9378_sdw_probe,
|
||||||
|
.remove = wcd9378_sdw_remove,
|
||||||
|
.ops = &wcd9378_slave_ops,
|
||||||
|
.id_table = wcd9378_sdw_id,
|
||||||
|
.driver = {
|
||||||
|
.name = "wcd9378-sdw",
|
||||||
|
.pm = pm_ptr(&wcd9378_sdw_pm_ops),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module_sdw_driver(wcd9378_sdw_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("WCD9378 SDW codec driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
1390
sound/soc/codecs/wcd9378.c
Normal file
1390
sound/soc/codecs/wcd9378.c
Normal file
File diff suppressed because it is too large
Load diff
246
sound/soc/codecs/wcd9378.h
Normal file
246
sound/soc/codecs/wcd9378.h
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026, Jorijn van der Graaf
|
||||||
|
*
|
||||||
|
* Register map for the Qualcomm WCD9378 audio codec.
|
||||||
|
*
|
||||||
|
* The codec exposes its control registers in the SoundWire SDCA control
|
||||||
|
* address space (bit 30 set, SDCA function number in bits 25:22), accessed
|
||||||
|
* through the TX SoundWire slave. The analog core registers (function 0,
|
||||||
|
* implementation-defined region at +0x180000) are layout-compatible with
|
||||||
|
* the WCD937x family; on top of that the chip adds SDCA-style functions
|
||||||
|
* (SmartMIC0/1/2, SmartJACK, SmartAMP) whose sequencers drive the analog
|
||||||
|
* power-up autonomously.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __WCD9378_H__
|
||||||
|
#define __WCD9378_H__
|
||||||
|
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/soundwire/sdw.h>
|
||||||
|
#include <linux/soundwire/sdw_type.h>
|
||||||
|
#include <sound/soc.h>
|
||||||
|
|
||||||
|
/* SDCA function 0 (extension unit): device identity */
|
||||||
|
#define WCD9378_FUNC_EXT_ID_0 0x40000048
|
||||||
|
#define WCD9378_FUNC_EXT_ID_1 0x40000049
|
||||||
|
#define WCD9378_FUNC_EXT_VER 0x40000050
|
||||||
|
#define WCD9378_FUNC_STAT 0x40080000
|
||||||
|
#define WCD9378_DEV_MANU_ID_0 0x40100060
|
||||||
|
#define WCD9378_DEV_MANU_ID_1 0x40100061
|
||||||
|
#define WCD9378_DEV_PART_ID_0 0x40100068
|
||||||
|
#define WCD9378_DEV_PART_ID_1 0x40100069
|
||||||
|
#define WCD9378_DEV_VER 0x40100070
|
||||||
|
|
||||||
|
/* Analog core (WCD937x-compatible layout), function 0 + 0x180000 */
|
||||||
|
#define WCD9378_ANA_PAGE 0x40180000
|
||||||
|
#define WCD9378_ANA_BIAS 0x40180001
|
||||||
|
#define WCD9378_ANA_BIAS_ANALOG_BIAS_EN BIT(7)
|
||||||
|
#define WCD9378_ANA_BIAS_PRECHRG_EN BIT(6)
|
||||||
|
#define WCD9378_ANA_RX_SUPPLIES 0x40180008
|
||||||
|
#define WCD9378_ANA_TX_CH1 0x4018000e
|
||||||
|
#define WCD9378_ANA_TX_CH2 0x4018000f
|
||||||
|
#define WCD9378_ANA_TX_CH2_HPF1_INIT BIT(6)
|
||||||
|
#define WCD9378_ANA_TX_CH2_HPF2_INIT BIT(5)
|
||||||
|
#define WCD9378_ANA_TX_CH3 0x40180010
|
||||||
|
#define WCD9378_ANA_TX_CH3_HPF 0x40180011
|
||||||
|
#define WCD9378_ANA_TX_CH3_HPF3_INIT BIT(6)
|
||||||
|
#define WCD9378_ANA_TX_GAIN_MASK GENMASK(4, 0)
|
||||||
|
#define WCD9378_ANA_MICB1 0x40180022
|
||||||
|
#define WCD9378_ANA_MICB2 0x40180023
|
||||||
|
#define WCD9378_ANA_MICB2_RAMP 0x40180024
|
||||||
|
#define WCD9378_ANA_MICB2_RAMP_SHIFT_CTL_MASK GENMASK(4, 2)
|
||||||
|
#define WCD9378_ANA_MICB2_RAMP_EN BIT(7)
|
||||||
|
#define WCD9378_ANA_MICB3 0x40180025
|
||||||
|
#define WCD9378_BIAS_VBG_FINE_ADJ 0x40180029
|
||||||
|
#define WCD9378_MBHC_CTL_SPARE_1 0x40180058
|
||||||
|
#define WCD9378_MICB1_TEST_CTL_2 0x4018006c
|
||||||
|
#define WCD9378_MICB2_TEST_CTL_2 0x4018006f
|
||||||
|
#define WCD9378_MICB3_TEST_CTL_2 0x40180072
|
||||||
|
#define WCD9378_TX_COM_TXFE_DIV_CTL 0x4018007b
|
||||||
|
#define WCD9378_TX_COM_TXFE_DIV_SEQ_BYPASS BIT(7)
|
||||||
|
#define WCD9378_SLEEP_CTL 0x40180103
|
||||||
|
#define WCD9378_SLEEP_CTL_BG_CTL_MASK GENMASK(3, 1)
|
||||||
|
#define WCD9378_SLEEP_CTL_BG_EN BIT(7)
|
||||||
|
#define WCD9378_SLEEP_CTL_LDOL_BG_SEL BIT(6)
|
||||||
|
#define WCD9378_TX_NEW_CH12_MUX 0x4018012e
|
||||||
|
#define WCD9378_TX_NEW_CH12_MUX_CH1_SEL_MASK GENMASK(2, 0)
|
||||||
|
#define WCD9378_TX_NEW_CH12_MUX_CH2_SEL_MASK GENMASK(5, 3)
|
||||||
|
#define WCD9378_TX_NEW_CH34_MUX 0x4018012f
|
||||||
|
#define WCD9378_TX_NEW_CH34_MUX_CH3_SEL_MASK GENMASK(2, 0)
|
||||||
|
#define WCD9378_HPH_RDAC_GAIN_CTL 0x40180132
|
||||||
|
#define WCD9378_HPH_RDAC_HD2_CTL_L 0x40180133
|
||||||
|
#define WCD9378_HPH_RDAC_HD2_CTL_R 0x40180136
|
||||||
|
|
||||||
|
/* Digital page */
|
||||||
|
#define WCD9378_TOP_CLK_CFG 0x40180407
|
||||||
|
#define WCD9378_CDC_ANA_TX_CLK_CTL 0x40180417
|
||||||
|
#define WCD9378_CDC_ANA_TXSCBIAS_CLK_EN BIT(0)
|
||||||
|
#define WCD9378_CDC_AMIC_CTL 0x4018045a
|
||||||
|
#define WCD9378_PDM_WD_CTL0 0x40180465
|
||||||
|
#define WCD9378_PDM_WD_CTL1 0x40180466
|
||||||
|
#define WCD9378_EFUSE_REG_16 0x401804c0
|
||||||
|
#define WCD9378_EFUSE_REG_29 0x401804cd
|
||||||
|
#define WCD9378_PLATFORM_CTL 0x401804f0
|
||||||
|
|
||||||
|
/* Sequencer block (SEQR) */
|
||||||
|
#define WCD9378_SYS_USAGE_CTRL 0x40180501
|
||||||
|
#define WCD9378_SYS_USAGE_CTRL_MASK GENMASK(3, 0)
|
||||||
|
#define WCD9378_HPH_UP_T0 0x40180510
|
||||||
|
#define WCD9378_HPH_UP_T9 0x40180519
|
||||||
|
#define WCD9378_HPH_DN_T0 0x4018051b
|
||||||
|
#define WCD9378_SEQ_TX0_STAT 0x40180592
|
||||||
|
#define WCD9378_SEQ_TX1_STAT 0x40180593
|
||||||
|
#define WCD9378_SEQ_TX2_STAT 0x40180594
|
||||||
|
#define WCD9378_MICB_REMAP_TABLE_VAL_3 0x401805a3
|
||||||
|
#define WCD9378_MICB_REMAP_TABLE_VAL_4 0x401805a4
|
||||||
|
#define WCD9378_MICB_REMAP_TABLE_VAL_5 0x401805a5
|
||||||
|
#define WCD9378_SM0_MB_SEL 0x401805b0
|
||||||
|
#define WCD9378_SM1_MB_SEL 0x401805b1
|
||||||
|
#define WCD9378_SM2_MB_SEL 0x401805b2
|
||||||
|
#define WCD9378_SM_MB_SEL_MASK GENMASK(1, 0)
|
||||||
|
#define WCD9378_MB_PULLUP_EN 0x401805b3
|
||||||
|
|
||||||
|
/* SmartAMP SDCA function */
|
||||||
|
#define WCD9378_SMP_AMP_FUNC_STAT 0x40880000
|
||||||
|
#define WCD9378_SMP_AMP_FUNC_ACT 0x40880008
|
||||||
|
|
||||||
|
/* SmartJACK SDCA function (hosts ADC2 when fed from AMIC2) */
|
||||||
|
#define WCD9378_CMT_GRP_MASK 0x40c00008
|
||||||
|
#define WCD9378_SMP_JACK_IT31_MICB 0x40c00798
|
||||||
|
#define WCD9378_SMP_JACK_IT31_USAGE 0x40c007a0
|
||||||
|
#define WCD9378_SMP_JACK_PDE34_REQ_PS 0x40c00808
|
||||||
|
#define WCD9378_SMP_JACK_FUNC_STAT 0x40c80000
|
||||||
|
#define WCD9378_SMP_JACK_FUNC_ACT 0x40c80008
|
||||||
|
#define WCD9378_SMP_JACK_PDE34_ACT_PS 0x40c80800
|
||||||
|
|
||||||
|
/* SmartMIC0/1/2 SDCA functions (ADC1/ADC2/ADC3 sequencers) */
|
||||||
|
#define WCD9378_SMP_MIC_BASE(n) (0x41000000 + (n) * 0x400000)
|
||||||
|
#define WCD9378_SMP_MIC_IT11_MICB(n) (WCD9378_SMP_MIC_BASE(n) + 0x98)
|
||||||
|
#define WCD9378_SMP_MIC_IT11_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0xa0)
|
||||||
|
#define WCD9378_SMP_MIC_PDE11_REQ_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x108)
|
||||||
|
#define WCD9378_SMP_MIC_OT10_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0x3a0)
|
||||||
|
#define WCD9378_SMP_MIC_FUNC_STAT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80000)
|
||||||
|
#define WCD9378_SMP_MIC_FUNC_ACT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80008)
|
||||||
|
#define WCD9378_SMP_MIC_PDE11_ACT_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x80100)
|
||||||
|
|
||||||
|
#define WCD9378_MAX_REGISTER 0x41900070
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Raw (16-bit, non-paged) Qualcomm slave SCP registers, written with
|
||||||
|
* sdw_write() directly. Bus clock indication towards the codec and
|
||||||
|
* SDCA interrupt type configuration.
|
||||||
|
*/
|
||||||
|
#define WCD9378_SWRS_SCP_BASE_CLK 0x4d
|
||||||
|
#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0 0x62
|
||||||
|
#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1 0x72
|
||||||
|
#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_1 0xf4
|
||||||
|
#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_2 0xf8
|
||||||
|
#define WCD9378_SWRS_SCP_SDCA_INTRTYPE_3 0xfc
|
||||||
|
#define WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(m) (0xe0 + 0x10 * (m))
|
||||||
|
#define WCD9378_SWRS_BASE_CLK_19P2MHZ 0x01
|
||||||
|
#define WCD9378_SWRS_CLK_SCALE_DIV2 0x02 /* 9.6 MHz */
|
||||||
|
#define WCD9378_SWRS_CLK_SCALE_DIV4 0x03 /* 4.8 MHz */
|
||||||
|
|
||||||
|
/* ITxx_USAGE ADC mode values */
|
||||||
|
#define WCD9378_ADC_USAGE_HIFI 0x01
|
||||||
|
#define WCD9378_ADC_USAGE_LO_HIF 0x02
|
||||||
|
#define WCD9378_ADC_USAGE_NORMAL 0x03
|
||||||
|
#define WCD9378_ADC_USAGE_LP 0x05
|
||||||
|
#define WCD9378_ADC_USAGE_OFF 0x00
|
||||||
|
|
||||||
|
/* ITxx_MICB usage values */
|
||||||
|
#define WCD9378_MICB_USAGE_OFF 0x00
|
||||||
|
#define WCD9378_MICB_USAGE_PULL_DOWN 0x01
|
||||||
|
#define WCD9378_MICB_USAGE_1P2V 0x02
|
||||||
|
#define WCD9378_MICB_USAGE_1P8V_OR_PULLUP 0x03
|
||||||
|
#define WCD9378_MICB_USAGE_2P5V 0x04
|
||||||
|
#define WCD9378_MICB_USAGE_2P75V 0x05
|
||||||
|
#define WCD9378_MICB_USAGE_2P2V 0xf0
|
||||||
|
#define WCD9378_MICB_USAGE_2P7V 0xf1
|
||||||
|
#define WCD9378_MICB_USAGE_2P8V 0xf2
|
||||||
|
#define WCD9378_MICB_USAGE_REMAP_TABLE_3 0xf3
|
||||||
|
#define WCD9378_MICB_USAGE_REMAP_TABLE_4 0xf4
|
||||||
|
#define WCD9378_MICB_USAGE_REMAP_TABLE_5 0xf5
|
||||||
|
|
||||||
|
/* PDExx_REQ_PS power states */
|
||||||
|
#define WCD9378_PDE_PS0_ON 0x00
|
||||||
|
#define WCD9378_PDE_PS3_OFF 0x03
|
||||||
|
|
||||||
|
#define WCD9378_MAX_MICBIAS 3
|
||||||
|
#define WCD9378_MAX_SWR_CH_IDS 15
|
||||||
|
#define WCD9378_SWRM_CH_MASK(ch_idx) BIT((ch_idx) - 1)
|
||||||
|
|
||||||
|
enum wcd9378_tx_sdw_ports {
|
||||||
|
WCD9378_ADC_1_PORT = 1,
|
||||||
|
WCD9378_ADC_2_PORT,
|
||||||
|
WCD9378_ADC_3_PORT,
|
||||||
|
WCD9378_DMIC_0_1_MBHC_PORT,
|
||||||
|
WCD9378_DMIC_2_5_PORT,
|
||||||
|
WCD9378_MAX_TX_SWR_PORTS = WCD9378_DMIC_2_5_PORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wcd9378_rx_sdw_ports {
|
||||||
|
WCD9378_HPH_PORT = 1,
|
||||||
|
WCD9378_CLSH_PORT,
|
||||||
|
WCD9378_COMP_PORT,
|
||||||
|
WCD9378_LO_PORT,
|
||||||
|
WCD9378_DSD_PORT,
|
||||||
|
WCD9378_MAX_SWR_PORTS = WCD9378_DSD_PORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wcd9378_tx_sdw_channels {
|
||||||
|
WCD9378_ADC1,
|
||||||
|
WCD9378_ADC2,
|
||||||
|
WCD9378_ADC3,
|
||||||
|
WCD9378_DMIC0,
|
||||||
|
WCD9378_DMIC1,
|
||||||
|
WCD9378_MBHC,
|
||||||
|
WCD9378_DMIC2,
|
||||||
|
WCD9378_DMIC3,
|
||||||
|
WCD9378_DMIC4,
|
||||||
|
WCD9378_DMIC5,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wcd9378_rx_sdw_channels {
|
||||||
|
WCD9378_HPH_L,
|
||||||
|
WCD9378_HPH_R,
|
||||||
|
WCD9378_CLSH,
|
||||||
|
WCD9378_COMP_L,
|
||||||
|
WCD9378_COMP_R,
|
||||||
|
WCD9378_LO,
|
||||||
|
WCD9378_DSD_L,
|
||||||
|
WCD9378_DSD_R,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wcd9378_priv;
|
||||||
|
struct wcd9378_sdw_priv {
|
||||||
|
struct sdw_slave *sdev;
|
||||||
|
struct sdw_stream_config sconfig;
|
||||||
|
struct sdw_stream_runtime *sruntime;
|
||||||
|
struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS];
|
||||||
|
struct wcd_sdw_ch_info *ch_info;
|
||||||
|
bool port_enable[WCD9378_MAX_SWR_CH_IDS];
|
||||||
|
unsigned int master_channel_map[SDW_MAX_PORTS];
|
||||||
|
int active_ports;
|
||||||
|
bool is_tx;
|
||||||
|
struct wcd9378_priv *wcd9378;
|
||||||
|
struct regmap *regmap;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_SND_SOC_WCD9378_SDW)
|
||||||
|
int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
|
||||||
|
struct snd_pcm_substream *substream,
|
||||||
|
struct snd_pcm_hw_params *params,
|
||||||
|
struct snd_soc_dai *dai);
|
||||||
|
#else
|
||||||
|
static inline int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
|
||||||
|
struct snd_pcm_substream *substream,
|
||||||
|
struct snd_pcm_hw_params *params,
|
||||||
|
struct snd_soc_dai *dai)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __WCD9378_H__ */
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
snd-q6dsp-common-y := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o
|
snd-q6dsp-common-y := q6dsp-common.o q6dsp-lpass-ports.o q6dsp-lpass-clocks.o
|
||||||
snd-q6apm-y := q6apm.o audioreach.o topology.o
|
snd-q6apm-y := q6apm.o audioreach.o topology.o q6apm-voice-proto.o
|
||||||
|
|
||||||
obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o
|
obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += snd-q6dsp-common.o
|
||||||
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
|
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
|
||||||
|
|
|
||||||
|
|
@ -576,10 +576,11 @@ void *audioreach_alloc_graph_pkt(struct q6apm *apm,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt);
|
EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt);
|
||||||
|
|
||||||
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
|
int audioreach_send_cmd_sync_timeout(struct device *dev, gpr_device_t *gdev,
|
||||||
struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock,
|
struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock,
|
||||||
gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
||||||
const struct gpr_pkt *pkt, uint32_t rsp_opcode)
|
const struct gpr_pkt *pkt, uint32_t rsp_opcode,
|
||||||
|
unsigned long timeout_s)
|
||||||
{
|
{
|
||||||
|
|
||||||
const struct gpr_hdr *hdr = &pkt->hdr;
|
const struct gpr_hdr *hdr = &pkt->hdr;
|
||||||
|
|
@ -601,9 +602,9 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
|
||||||
|
|
||||||
if (rsp_opcode)
|
if (rsp_opcode)
|
||||||
rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode) ||
|
rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode) ||
|
||||||
(result->opcode == rsp_opcode), 5 * HZ);
|
(result->opcode == rsp_opcode), timeout_s * HZ);
|
||||||
else
|
else
|
||||||
rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), 5 * HZ);
|
rc = wait_event_timeout(*cmd_wait, (result->opcode == hdr->opcode), timeout_s * HZ);
|
||||||
|
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode);
|
dev_err(dev, "CMD timeout for [%x] opcode\n", hdr->opcode);
|
||||||
|
|
@ -620,6 +621,16 @@ err:
|
||||||
mutex_unlock(cmd_lock);
|
mutex_unlock(cmd_lock);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync_timeout);
|
||||||
|
|
||||||
|
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
|
||||||
|
struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock,
|
||||||
|
gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
||||||
|
const struct gpr_pkt *pkt, uint32_t rsp_opcode)
|
||||||
|
{
|
||||||
|
return audioreach_send_cmd_sync_timeout(dev, gdev, result, cmd_lock,
|
||||||
|
port, cmd_wait, pkt, rsp_opcode, 30);
|
||||||
|
}
|
||||||
EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync);
|
EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync);
|
||||||
|
|
||||||
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
|
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,15 @@ struct q6apm_graph;
|
||||||
#define APM_CMD_GRAPH_FLUSH 0x01001005
|
#define APM_CMD_GRAPH_FLUSH 0x01001005
|
||||||
#define APM_CMD_SET_CFG 0x01001006
|
#define APM_CMD_SET_CFG 0x01001006
|
||||||
#define APM_CMD_GET_CFG 0x01001007
|
#define APM_CMD_GET_CFG 0x01001007
|
||||||
|
#define APM_CMD_REGISTER_CFG 0x01001008
|
||||||
#define APM_CMD_SHARED_MEM_MAP_REGIONS 0x0100100C
|
#define APM_CMD_SHARED_MEM_MAP_REGIONS 0x0100100C
|
||||||
#define APM_CMD_SHARED_MEM_UNMAP_REGIONS 0x0100100D
|
#define APM_CMD_SHARED_MEM_UNMAP_REGIONS 0x0100100D
|
||||||
#define APM_CMD_RSP_SHARED_MEM_MAP_REGIONS 0x02001001
|
#define APM_CMD_RSP_SHARED_MEM_MAP_REGIONS 0x02001001
|
||||||
#define APM_CMD_RSP_GET_CFG 0x02001000
|
#define APM_CMD_RSP_GET_CFG 0x02001000
|
||||||
#define APM_CMD_CLOSE_ALL 0x01001013
|
#define APM_CMD_CLOSE_ALL 0x01001013
|
||||||
#define APM_CMD_REGISTER_SHARED_CFG 0x0100100A
|
#define APM_CMD_REGISTER_SHARED_CFG 0x0100100A
|
||||||
|
#define AMDB_CMD_LOAD_MODULES 0x01001020
|
||||||
|
#define AMDB_CMD_RSP_LOAD_MODULES 0x02001008
|
||||||
|
|
||||||
#define APM_MEMORY_MAP_SHMEM8_4K_POOL 3
|
#define APM_MEMORY_MAP_SHMEM8_4K_POOL 3
|
||||||
|
|
||||||
|
|
@ -842,6 +845,11 @@ void audioreach_graph_free_buf(struct q6apm_graph *graph);
|
||||||
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result,
|
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result,
|
||||||
struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
||||||
const struct gpr_pkt *pkt, uint32_t rsp_opcode);
|
const struct gpr_pkt *pkt, uint32_t rsp_opcode);
|
||||||
|
int audioreach_send_cmd_sync_timeout(struct device *dev, gpr_device_t *gdev,
|
||||||
|
struct gpr_ibasic_rsp_result_t *result,
|
||||||
|
struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait,
|
||||||
|
const struct gpr_pkt *pkt, uint32_t rsp_opcode,
|
||||||
|
unsigned long timeout_s);
|
||||||
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
|
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
|
||||||
uint32_t rsp_opcode);
|
uint32_t rsp_opcode);
|
||||||
int audioreach_set_media_format(struct q6apm_graph *graph,
|
int audioreach_set_media_format(struct q6apm_graph *graph,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
dev_err(dai->dev, "Failed to prepare Graph %d\n", rc);
|
||||||
goto err;
|
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;
|
return 0;
|
||||||
err:
|
err:
|
||||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||||
|
|
|
||||||
380
sound/soc/qcom/qdsp6/q6apm-voice-proto.c
Normal file
380
sound/soc/qcom/qdsp6/q6apm-voice-proto.c
Normal file
|
|
@ -0,0 +1,380 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Voice-call bring-up prototype for the Fairphone (Gen. 6): replay
|
||||||
|
* pre-generated APM command sequences through the q6apm GPR channel.
|
||||||
|
*
|
||||||
|
* The blobs reproduce, byte for byte, the GRAPH_OPEN/SET_CFG/PREPARE/START
|
||||||
|
* sequence the stock Android PAL/AGM/GSL stack sends to set up the
|
||||||
|
* handset voice-call graphs (mined from the device ACDB; generated by
|
||||||
|
* utilities/mkvoiceblobs.py in the bring-up repo). The modem's voice
|
||||||
|
* engine and the ADSP VCPM service wire up the vocoder themselves once
|
||||||
|
* these graphs are running.
|
||||||
|
*
|
||||||
|
* Local prototype only, NOT for upstream. Interface:
|
||||||
|
* echo <fw-file> > /sys/kernel/debug/q6apm-voice-proto/play
|
||||||
|
* where <fw-file> is relative to /lib/firmware. Playback aborts on the
|
||||||
|
* first command the DSP rejects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/dma-mapping.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/soc/qcom/apr.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <sound/soc.h>
|
||||||
|
#include "audioreach.h"
|
||||||
|
#include "q6apm.h"
|
||||||
|
|
||||||
|
#define Q6VP_MAGIC 0x50563651 /* "Q6VP" little-endian */
|
||||||
|
#define Q6VP_VERSION 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A record opcode with this bit set is sent out-of-band: the payload is
|
||||||
|
* placed in an ADSP-mapped shared-memory region and the GPR command carries
|
||||||
|
* only an apm_cmd_header referencing it. Stock GSL sends every voice
|
||||||
|
* GRAPH_OPEN this way; it is mandatory because a voice graph must be opened
|
||||||
|
* atomically as one command (all subgraphs + cross-subgraph module
|
||||||
|
* connections and control links), and the whole payload (~4.3-4.7 KB)
|
||||||
|
* exceeds the ~4 KiB in-band GPR/GLINK intent limit. Splitting the open
|
||||||
|
* per-subgraph to fit in-band makes the cross-referencing TX DevicePP
|
||||||
|
* subgraph invalid (rejected at open) — see journal/calls.md 2026-07-08.
|
||||||
|
*/
|
||||||
|
#define Q6VP_OOB_FLAG 0x80000000
|
||||||
|
/*
|
||||||
|
* A record opcode with this bit set carries the GPR destination port in the
|
||||||
|
* first payload word (stripped before sending). Lets a blob address static
|
||||||
|
* services other than APM — e.g. AMDB (port 3) module pre-loading, so the
|
||||||
|
* dynamic Fluence pp modules can be made resident BEFORE the voice
|
||||||
|
* GRAPH_OPENs. A first open that has to FastRPC-load them can fail or block
|
||||||
|
* so long that the client-side retry dance corrupts VCPM's per-VSID voice
|
||||||
|
* session (TX cal EBADPARAM / TX START EFAILED, journal/calls.md
|
||||||
|
* 2026-07-10). In-band records only.
|
||||||
|
*/
|
||||||
|
#define Q6VP_DSTPORT_FLAG 0x40000000
|
||||||
|
#define Q6VP_GRAPH_ID 0xf000 /* private mem-map handle owner */
|
||||||
|
#define Q6VP_BUF_SZ 0x800000 /* 8 MiB shared region */
|
||||||
|
#define Q6VP_SCRATCH_SZ 0x10000 /* [0, 64K): transient commands */
|
||||||
|
#define Q6VP_SID_MASK 0xf
|
||||||
|
|
||||||
|
struct q6vp_hdr {
|
||||||
|
__le32 magic;
|
||||||
|
__le32 version;
|
||||||
|
__le32 num_records;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct q6vp_rec {
|
||||||
|
__le32 opcode;
|
||||||
|
__le32 size;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(q6vp_play_lock);
|
||||||
|
static struct q6apm *q6vp_apm;
|
||||||
|
static struct dentry *q6vp_dir;
|
||||||
|
static struct device *q6vp_dais_dev;
|
||||||
|
static struct audioreach_graph_info *q6vp_info;
|
||||||
|
static void *q6vp_buf;
|
||||||
|
static dma_addr_t q6vp_buf_iova;
|
||||||
|
static phys_addr_t q6vp_buf_dsp_addr;
|
||||||
|
|
||||||
|
static int q6vp_send(uint32_t opcode, const void *payload, uint32_t size)
|
||||||
|
{
|
||||||
|
uint32_t dest_port = APM_MODULE_INSTANCE_ID;
|
||||||
|
uint32_t rsp_opcode = 0;
|
||||||
|
struct gpr_pkt *pkt;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (opcode & Q6VP_DSTPORT_FLAG) {
|
||||||
|
if (size < sizeof(__le32))
|
||||||
|
return -EINVAL;
|
||||||
|
dest_port = le32_to_cpup(payload);
|
||||||
|
payload += sizeof(__le32);
|
||||||
|
size -= sizeof(__le32);
|
||||||
|
opcode &= ~Q6VP_DSTPORT_FLAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AMDB load responds with its own opcode, not an ibasic result */
|
||||||
|
if (opcode == AMDB_CMD_LOAD_MODULES)
|
||||||
|
rsp_opcode = AMDB_CMD_RSP_LOAD_MODULES;
|
||||||
|
/* GET_CFG responds with the param data; payload dumped in callback */
|
||||||
|
if (opcode == APM_CMD_GET_CFG)
|
||||||
|
rsp_opcode = APM_CMD_RSP_GET_CFG;
|
||||||
|
|
||||||
|
pkt = audioreach_alloc_cmd_pkt(size, opcode, 0, GPR_APM_MODULE_IID,
|
||||||
|
dest_port);
|
||||||
|
if (IS_ERR(pkt))
|
||||||
|
return PTR_ERR(pkt);
|
||||||
|
|
||||||
|
memcpy((void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE, payload, size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Voice blobs need the long wait: a cold TX GRAPH_OPEN that has to
|
||||||
|
* FastRPC-load the Fluence pp modules takes >120 s. Regular audio
|
||||||
|
* commands (and especially the probe-time GET_SPF_STATE ready poll)
|
||||||
|
* must keep the short default or they hold the shared command mutex
|
||||||
|
* hostage at boot: a 240 s stall in apm_probe delays module loading
|
||||||
|
* past udev's worker timeout and the sound card never assembles.
|
||||||
|
*/
|
||||||
|
ret = q6apm_send_cmd_sync_timeout(q6vp_apm, pkt, rsp_opcode, 240);
|
||||||
|
kfree(pkt);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lazily set up the shared-memory region used for OOB commands: allocate a
|
||||||
|
* DMA buffer against the q6apm-dais child device (it carries the iommus
|
||||||
|
* mapping the ADSP expects, same as the PCM data path), register a private
|
||||||
|
* graph_info so the APM_CMD_RSP_SHARED_MEM_MAP_REGIONS callback has a slot
|
||||||
|
* to store the handle in, and map the region with the DSP. The mapping is
|
||||||
|
* kept until the module goes away.
|
||||||
|
*/
|
||||||
|
static int q6vp_oob_init(void)
|
||||||
|
{
|
||||||
|
struct device *dev = q6vp_apm->dev;
|
||||||
|
struct of_phandle_args args;
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct device_node *np;
|
||||||
|
u64 sid = 0;
|
||||||
|
u32 graph_id = Q6VP_GRAPH_ID;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (q6vp_info && q6vp_info->mem_map_handle)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!q6vp_dais_dev) {
|
||||||
|
np = of_get_compatible_child(dev->of_node, "qcom,q6apm-dais");
|
||||||
|
if (!np) {
|
||||||
|
dev_err(dev, "voice-proto: no q6apm-dais node\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
if (!of_parse_phandle_with_fixed_args(np, "iommus", 1, 0,
|
||||||
|
&args)) {
|
||||||
|
sid = args.args[0] & Q6VP_SID_MASK;
|
||||||
|
of_node_put(args.np);
|
||||||
|
}
|
||||||
|
pdev = of_find_device_by_node(np);
|
||||||
|
of_node_put(np);
|
||||||
|
if (!pdev) {
|
||||||
|
dev_err(dev, "voice-proto: no q6apm-dais device\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
q6vp_dais_dev = &pdev->dev;
|
||||||
|
|
||||||
|
q6vp_buf = dma_alloc_coherent(q6vp_dais_dev, Q6VP_BUF_SZ,
|
||||||
|
&q6vp_buf_iova, GFP_KERNEL);
|
||||||
|
if (!q6vp_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
q6vp_buf_dsp_addr = q6vp_buf_iova | (sid << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!q6vp_info) {
|
||||||
|
q6vp_info = kzalloc(sizeof(*q6vp_info), GFP_KERNEL);
|
||||||
|
if (!q6vp_info)
|
||||||
|
return -ENOMEM;
|
||||||
|
INIT_LIST_HEAD(&q6vp_info->sg_list);
|
||||||
|
q6vp_info->id = Q6VP_GRAPH_ID;
|
||||||
|
ret = idr_alloc_u32(&q6vp_apm->graph_info_idr, q6vp_info,
|
||||||
|
&graph_id, graph_id, GFP_KERNEL);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(q6vp_info);
|
||||||
|
q6vp_info = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = q6apm_map_memory_fixed_region(q6vp_dais_dev, Q6VP_GRAPH_ID,
|
||||||
|
q6vp_buf_dsp_addr, Q6VP_BUF_SZ);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "voice-proto: mem map failed (%d)\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(dev, "voice-proto: OOB region mapped, handle 0x%x\n",
|
||||||
|
q6vp_info->mem_map_handle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Persistent-slice bump offset: APM_CMD_REGISTER_CFG registers the payload
|
||||||
|
* with the DSP, which keeps reading that memory for as long as the config
|
||||||
|
* stays registered — those payloads must never be overwritten. They get
|
||||||
|
* bump-allocated slices above Q6VP_SCRATCH_SZ that live until reboot;
|
||||||
|
* transient commands (opens etc.) reuse the scratch slot at offset 0.
|
||||||
|
*/
|
||||||
|
static u32 q6vp_persist_off = Q6VP_SCRATCH_SZ;
|
||||||
|
|
||||||
|
static int q6vp_send_oob(uint32_t opcode, const void *payload, uint32_t size)
|
||||||
|
{
|
||||||
|
struct apm_cmd_header *cmd_header;
|
||||||
|
struct gpr_pkt *pkt;
|
||||||
|
u32 off = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = q6vp_oob_init();
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (opcode == APM_CMD_REGISTER_CFG) {
|
||||||
|
if (q6vp_persist_off + size > Q6VP_BUF_SZ)
|
||||||
|
return -EFBIG;
|
||||||
|
off = q6vp_persist_off;
|
||||||
|
q6vp_persist_off += ALIGN(size, SZ_4K);
|
||||||
|
} else if (size > Q6VP_SCRATCH_SZ) {
|
||||||
|
return -EFBIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(q6vp_buf + off, payload, size);
|
||||||
|
|
||||||
|
pkt = audioreach_alloc_apm_cmd_pkt(0, opcode, 0);
|
||||||
|
if (IS_ERR(pkt))
|
||||||
|
return PTR_ERR(pkt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The region is mapped in absolute-address mode (mainline's
|
||||||
|
* q6apm_map_memory_fixed_region passes property_flag 0, unlike GSL
|
||||||
|
* which maps with IS_OFFSET_MODE and then references offset 0), so
|
||||||
|
* the header carries the full DSP-visible address of the buffer.
|
||||||
|
*/
|
||||||
|
cmd_header = (void *)pkt + GPR_HDR_SIZE;
|
||||||
|
cmd_header->payload_address_lsw = lower_32_bits(q6vp_buf_dsp_addr + off);
|
||||||
|
cmd_header->payload_address_msw = upper_32_bits(q6vp_buf_dsp_addr + off);
|
||||||
|
cmd_header->mem_map_handle = q6vp_info->mem_map_handle;
|
||||||
|
cmd_header->payload_size = size;
|
||||||
|
|
||||||
|
ret = q6apm_send_cmd_sync_timeout(q6vp_apm, pkt, 0, 240);
|
||||||
|
kfree(pkt);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int q6vp_play(const char *name)
|
||||||
|
{
|
||||||
|
const struct firmware *fw;
|
||||||
|
const struct q6vp_hdr *hdr;
|
||||||
|
const struct q6vp_rec *rec;
|
||||||
|
struct device *dev = q6vp_apm->dev;
|
||||||
|
size_t off;
|
||||||
|
uint32_t i, num, opcode, size;
|
||||||
|
int ret, err = 0;
|
||||||
|
|
||||||
|
ret = request_firmware(&fw, name, dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "voice-proto: cannot load %s (%d)\n", name, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr = (const struct q6vp_hdr *)fw->data;
|
||||||
|
if (fw->size < sizeof(*hdr) ||
|
||||||
|
le32_to_cpu(hdr->magic) != Q6VP_MAGIC ||
|
||||||
|
le32_to_cpu(hdr->version) != Q6VP_VERSION) {
|
||||||
|
dev_err(dev, "voice-proto: %s: bad header\n", name);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
num = le32_to_cpu(hdr->num_records);
|
||||||
|
off = sizeof(*hdr);
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
if (off + sizeof(*rec) > fw->size) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
rec = (const struct q6vp_rec *)(fw->data + off);
|
||||||
|
opcode = le32_to_cpu(rec->opcode);
|
||||||
|
size = le32_to_cpu(rec->size);
|
||||||
|
off += sizeof(*rec);
|
||||||
|
if (off + size > fw->size) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opcode & Q6VP_OOB_FLAG)
|
||||||
|
ret = (opcode & Q6VP_DSTPORT_FLAG) ? -EINVAL :
|
||||||
|
q6vp_send_oob(opcode & ~Q6VP_OOB_FLAG,
|
||||||
|
fw->data + off, size);
|
||||||
|
else
|
||||||
|
ret = q6vp_send(opcode, fw->data + off, size);
|
||||||
|
dev_info(dev, "voice-proto: %s[%u] opcode 0x%08x size %u -> %d\n",
|
||||||
|
name, i,
|
||||||
|
opcode & ~(Q6VP_OOB_FLAG | Q6VP_DSTPORT_FLAG),
|
||||||
|
size, ret);
|
||||||
|
/* keep going on DSP rejections; each result is logged */
|
||||||
|
if (ret)
|
||||||
|
err = ret;
|
||||||
|
|
||||||
|
off += ALIGN(size, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
release_firmware(fw);
|
||||||
|
return ret ? ret : err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t q6vp_play_write(struct file *file, const char __user *ubuf,
|
||||||
|
size_t count, loff_t *ppos)
|
||||||
|
{
|
||||||
|
char name[128];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!q6vp_apm)
|
||||||
|
return -ENODEV;
|
||||||
|
if (count >= sizeof(name))
|
||||||
|
return -EINVAL;
|
||||||
|
if (copy_from_user(name, ubuf, count))
|
||||||
|
return -EFAULT;
|
||||||
|
name[count] = '\0';
|
||||||
|
strim(name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Serialize blob playback: concurrent debugfs writers (e.g. a
|
||||||
|
* dropped ssh session whose write is still stuck in a DSP wait)
|
||||||
|
* would interleave their records on the GPR channel and corrupt
|
||||||
|
* both sequences (seen 2026-07-10).
|
||||||
|
*/
|
||||||
|
ret = mutex_lock_interruptible(&q6vp_play_lock);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
ret = q6vp_play(name);
|
||||||
|
mutex_unlock(&q6vp_play_lock);
|
||||||
|
|
||||||
|
return ret ? ret : count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations q6vp_play_fops = {
|
||||||
|
.open = simple_open,
|
||||||
|
.write = q6vp_play_write,
|
||||||
|
.llseek = default_llseek,
|
||||||
|
};
|
||||||
|
|
||||||
|
void q6apm_voice_proto_init(struct q6apm *apm)
|
||||||
|
{
|
||||||
|
q6vp_apm = apm;
|
||||||
|
q6vp_dir = debugfs_create_dir("q6apm-voice-proto", NULL);
|
||||||
|
debugfs_create_file("play", 0200, q6vp_dir, NULL, &q6vp_play_fops);
|
||||||
|
}
|
||||||
|
|
||||||
|
void q6apm_voice_proto_exit(void)
|
||||||
|
{
|
||||||
|
debugfs_remove_recursive(q6vp_dir);
|
||||||
|
q6vp_dir = NULL;
|
||||||
|
|
||||||
|
if (q6vp_info) {
|
||||||
|
if (q6vp_info->mem_map_handle)
|
||||||
|
q6apm_unmap_memory_fixed_region(q6vp_dais_dev,
|
||||||
|
Q6VP_GRAPH_ID);
|
||||||
|
idr_remove(&q6vp_apm->graph_info_idr, Q6VP_GRAPH_ID);
|
||||||
|
kfree(q6vp_info);
|
||||||
|
q6vp_info = NULL;
|
||||||
|
}
|
||||||
|
if (q6vp_buf) {
|
||||||
|
dma_free_coherent(q6vp_dais_dev, Q6VP_BUF_SZ, q6vp_buf,
|
||||||
|
q6vp_buf_iova);
|
||||||
|
q6vp_buf = NULL;
|
||||||
|
}
|
||||||
|
q6vp_dais_dev = NULL;
|
||||||
|
q6vp_apm = NULL;
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,16 @@ int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
|
||||||
NULL, &apm->wait, pkt, rsp_opcode);
|
NULL, &apm->wait, pkt, rsp_opcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int q6apm_send_cmd_sync_timeout(struct q6apm *apm, const struct gpr_pkt *pkt,
|
||||||
|
uint32_t rsp_opcode, unsigned long timeout_s)
|
||||||
|
{
|
||||||
|
gpr_device_t *gdev = apm->gdev;
|
||||||
|
|
||||||
|
return audioreach_send_cmd_sync_timeout(&gdev->dev, gdev, &apm->result,
|
||||||
|
&apm->lock, NULL, &apm->wait,
|
||||||
|
pkt, rsp_opcode, timeout_s);
|
||||||
|
}
|
||||||
|
|
||||||
static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id)
|
static struct audioreach_graph *q6apm_get_audioreach_graph(struct q6apm *apm, uint32_t graph_id)
|
||||||
{
|
{
|
||||||
struct audioreach_graph_info *info;
|
struct audioreach_graph_info *info;
|
||||||
|
|
@ -764,6 +774,8 @@ static int apm_probe(gpr_device_t *gdev)
|
||||||
|
|
||||||
g_apm = apm;
|
g_apm = apm;
|
||||||
|
|
||||||
|
q6apm_voice_proto_init(apm);
|
||||||
|
|
||||||
q6apm_get_apm_state(apm);
|
q6apm_get_apm_state(apm);
|
||||||
|
|
||||||
ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0);
|
ret = snd_soc_register_component(dev, &q6apm_audio_component, NULL, 0);
|
||||||
|
|
@ -781,6 +793,7 @@ static int apm_probe(gpr_device_t *gdev)
|
||||||
|
|
||||||
static void apm_remove(gpr_device_t *gdev)
|
static void apm_remove(gpr_device_t *gdev)
|
||||||
{
|
{
|
||||||
|
q6apm_voice_proto_exit();
|
||||||
of_platform_depopulate(&gdev->dev);
|
of_platform_depopulate(&gdev->dev);
|
||||||
snd_soc_unregister_component(&gdev->dev);
|
snd_soc_unregister_component(&gdev->dev);
|
||||||
}
|
}
|
||||||
|
|
@ -817,6 +830,7 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
|
||||||
case GPR_BASIC_RSP_RESULT:
|
case GPR_BASIC_RSP_RESULT:
|
||||||
switch (result->opcode) {
|
switch (result->opcode) {
|
||||||
case APM_CMD_SHARED_MEM_MAP_REGIONS:
|
case APM_CMD_SHARED_MEM_MAP_REGIONS:
|
||||||
|
case APM_CMD_REGISTER_CFG:
|
||||||
case APM_CMD_GRAPH_START:
|
case APM_CMD_GRAPH_START:
|
||||||
case APM_CMD_GRAPH_OPEN:
|
case APM_CMD_GRAPH_OPEN:
|
||||||
case APM_CMD_GRAPH_PREPARE:
|
case APM_CMD_GRAPH_PREPARE:
|
||||||
|
|
@ -824,6 +838,7 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
|
||||||
case APM_CMD_GRAPH_FLUSH:
|
case APM_CMD_GRAPH_FLUSH:
|
||||||
case APM_CMD_GRAPH_STOP:
|
case APM_CMD_GRAPH_STOP:
|
||||||
case APM_CMD_SET_CFG:
|
case APM_CMD_SET_CFG:
|
||||||
|
case APM_CMD_GET_CFG:
|
||||||
apm->result.opcode = result->opcode;
|
apm->result.opcode = result->opcode;
|
||||||
apm->result.status = result->status;
|
apm->result.status = result->status;
|
||||||
if (result->status)
|
if (result->status)
|
||||||
|
|
@ -846,9 +861,62 @@ static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op)
|
||||||
wake_up(&apm->wait);
|
wake_up(&apm->wait);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
/*
|
||||||
|
* Unlisted command results (debug/probe opcodes sent
|
||||||
|
* via voice-proto) must still wake the sync waiter or
|
||||||
|
* every such send stalls for the full timeout.
|
||||||
|
*/
|
||||||
|
apm->result.opcode = result->opcode;
|
||||||
|
apm->result.status = result->status;
|
||||||
|
if (result->status)
|
||||||
|
dev_err(dev, "Error (%d) Processing 0x%08x cmd\n",
|
||||||
|
result->status, result->opcode);
|
||||||
|
wake_up(&apm->wait);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x03001000: { /* APM_EVENT_MODULE_TO_CLIENT */
|
||||||
|
/* apm_module_event_t: event_id, payload_size, payload */
|
||||||
|
const u32 *ev = data->payload;
|
||||||
|
u32 ev_sz = hdr->pkt_size - hdr->hdr_size * 4;
|
||||||
|
|
||||||
|
dev_info(dev, "MODULE EVENT from 0x%x: event_id 0x%08x size %u\n",
|
||||||
|
hdr->src_port, ev[0], ev[1]);
|
||||||
|
print_hex_dump(KERN_INFO, "EVENT: ", DUMP_PREFIX_OFFSET,
|
||||||
|
16, 4, ev, ev_sz, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APM_CMD_RSP_GET_CFG: {
|
||||||
|
/* status word, then apm_module_param_data_t sections */
|
||||||
|
const u32 *rsp_pl = data->payload;
|
||||||
|
u32 pl_sz = hdr->pkt_size - hdr->hdr_size * 4;
|
||||||
|
|
||||||
|
dev_info(dev, "GET_CFG rsp: status %d, %u bytes\n",
|
||||||
|
rsp_pl[0], pl_sz);
|
||||||
|
print_hex_dump(KERN_INFO, "GET_CFG: ", DUMP_PREFIX_OFFSET,
|
||||||
|
16, 4, rsp_pl, pl_sz, false);
|
||||||
|
apm->result.opcode = hdr->opcode;
|
||||||
|
apm->result.status = rsp_pl[0];
|
||||||
|
wake_up(&apm->wait);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AMDB_CMD_RSP_LOAD_MODULES: {
|
||||||
|
/* amdb_module_load_unload_t: proc_domain, error_code,
|
||||||
|
* num_modules, {module_id, handle_lsw, handle_msw}[].
|
||||||
|
* A zero handle = module not registered with the AMDB.
|
||||||
|
*/
|
||||||
|
const u32 *amdb = data->payload;
|
||||||
|
u32 i, n = amdb[2];
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
dev_info(dev, "AMDB load: module 0x%08x handle 0x%08x%08x\n",
|
||||||
|
amdb[3 + 3 * i], amdb[5 + 3 * i],
|
||||||
|
amdb[4 + 3 * i]);
|
||||||
|
apm->result.opcode = hdr->opcode;
|
||||||
|
apm->result.status = amdb[1];
|
||||||
|
wake_up(&apm->wait);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS:
|
case APM_CMD_RSP_SHARED_MEM_MAP_REGIONS:
|
||||||
apm->result.opcode = hdr->opcode;
|
apm->result.opcode = hdr->opcode;
|
||||||
apm->result.status = 0;
|
apm->result.status = 0;
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@ int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id);
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
|
int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
|
||||||
uint32_t rsp_opcode);
|
uint32_t rsp_opcode);
|
||||||
|
int q6apm_send_cmd_sync_timeout(struct q6apm *apm, const struct gpr_pkt *pkt,
|
||||||
|
uint32_t rsp_opcode, unsigned long timeout_s);
|
||||||
|
|
||||||
/* Callback for graph specific */
|
/* Callback for graph specific */
|
||||||
struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph,
|
struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph,
|
||||||
|
|
@ -157,4 +159,8 @@ int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph,
|
||||||
int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples);
|
int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples);
|
||||||
int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id);
|
int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id);
|
||||||
int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir);
|
int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir);
|
||||||
|
|
||||||
|
/* FP6 voice-call bring-up prototype (q6apm-voice-proto.c) */
|
||||||
|
void q6apm_voice_proto_init(struct q6apm *apm);
|
||||||
|
void q6apm_voice_proto_exit(void);
|
||||||
#endif /* __APM_GRAPH_ */
|
#endif /* __APM_GRAPH_ */
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
|
||||||
switch (cpu_dai->id) {
|
switch (cpu_dai->id) {
|
||||||
case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX:
|
case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX:
|
||||||
case QUINARY_MI2S_RX...QUINARY_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);
|
snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
|
||||||
break;
|
break;
|
||||||
case WSA_CODEC_DMA_RX_0:
|
case WSA_CODEC_DMA_RX_0:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue