2020-08-14 18:53:05 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
|
|
|
*
|
|
|
|
* lpass-sc7180.c -- ALSA SoC platform-machine driver for QTi LPASS
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <dt-bindings/sound/sc7180-lpass.h>
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
|
|
|
|
#include "lpass-lpaif-reg.h"
|
|
|
|
#include "lpass.h"
|
|
|
|
|
|
|
|
static struct snd_soc_dai_driver sc7180_lpass_cpu_dai_driver[] = {
|
2021-01-20 01:15:27 +08:00
|
|
|
{
|
2020-08-14 18:53:05 +08:00
|
|
|
.id = MI2S_PRIMARY,
|
|
|
|
.name = "Primary MI2S",
|
|
|
|
.playback = {
|
|
|
|
.stream_name = "Primary Playback",
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16,
|
|
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
|
|
.rate_min = 48000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
},
|
|
|
|
.capture = {
|
|
|
|
.stream_name = "Primary Capture",
|
2020-11-16 15:49:15 +08:00
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16 |
|
|
|
|
SNDRV_PCM_FMTBIT_S32,
|
2020-08-14 18:53:05 +08:00
|
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
|
|
.rate_min = 48000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
},
|
|
|
|
.probe = &asoc_qcom_lpass_cpu_dai_probe,
|
|
|
|
.ops = &asoc_qcom_lpass_cpu_dai_ops,
|
2021-01-20 01:15:27 +08:00
|
|
|
}, {
|
2020-08-14 18:53:05 +08:00
|
|
|
.id = MI2S_SECONDARY,
|
|
|
|
.name = "Secondary MI2S",
|
|
|
|
.playback = {
|
|
|
|
.stream_name = "Secondary Playback",
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16,
|
|
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
|
|
.rate_min = 48000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
},
|
|
|
|
.probe = &asoc_qcom_lpass_cpu_dai_probe,
|
|
|
|
.ops = &asoc_qcom_lpass_cpu_dai_ops,
|
2021-01-20 01:15:27 +08:00
|
|
|
}, {
|
2020-10-08 13:17:03 +08:00
|
|
|
.id = LPASS_DP_RX,
|
|
|
|
.name = "Hdmi",
|
|
|
|
.playback = {
|
|
|
|
.stream_name = "Hdmi Playback",
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S24,
|
|
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
|
|
.rate_min = 48000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
},
|
|
|
|
.ops = &asoc_qcom_lpass_hdmi_dai_ops,
|
|
|
|
},
|
2020-08-14 18:53:05 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int sc7180_lpass_alloc_dma_channel(struct lpass_data *drvdata,
|
2020-10-08 13:17:03 +08:00
|
|
|
int direction, unsigned int dai_id)
|
2020-08-14 18:53:05 +08:00
|
|
|
{
|
|
|
|
struct lpass_variant *v = drvdata->variant;
|
|
|
|
int chan = 0;
|
|
|
|
|
2020-10-08 13:17:03 +08:00
|
|
|
if (dai_id == LPASS_DP_RX) {
|
|
|
|
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
chan = find_first_zero_bit(&drvdata->hdmi_dma_ch_bit_map,
|
|
|
|
v->hdmi_rdma_channels);
|
|
|
|
|
|
|
|
if (chan >= v->hdmi_rdma_channels)
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
set_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
|
|
|
|
} else {
|
|
|
|
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
chan = find_first_zero_bit(&drvdata->dma_ch_bit_map,
|
|
|
|
v->rdma_channels);
|
2020-08-14 18:53:05 +08:00
|
|
|
|
2020-11-03 18:18:53 +08:00
|
|
|
if (chan >= v->rdma_channels)
|
|
|
|
return -EBUSY;
|
2020-10-08 13:17:03 +08:00
|
|
|
} else {
|
|
|
|
chan = find_next_zero_bit(&drvdata->dma_ch_bit_map,
|
2020-08-14 18:53:05 +08:00
|
|
|
v->wrdma_channel_start +
|
|
|
|
v->wrdma_channels,
|
|
|
|
v->wrdma_channel_start);
|
|
|
|
|
2020-10-08 13:17:03 +08:00
|
|
|
if (chan >= v->wrdma_channel_start + v->wrdma_channels)
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
2020-08-14 18:53:05 +08:00
|
|
|
|
2020-10-08 13:17:03 +08:00
|
|
|
set_bit(chan, &drvdata->dma_ch_bit_map);
|
|
|
|
}
|
2020-08-14 18:53:05 +08:00
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
2020-10-08 13:17:03 +08:00
|
|
|
static int sc7180_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
|
2020-08-14 18:53:05 +08:00
|
|
|
{
|
2020-10-08 13:17:03 +08:00
|
|
|
if (dai_id == LPASS_DP_RX)
|
|
|
|
clear_bit(chan, &drvdata->hdmi_dma_ch_bit_map);
|
|
|
|
else
|
|
|
|
clear_bit(chan, &drvdata->dma_ch_bit_map);
|
2020-08-14 18:53:05 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc7180_lpass_init(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct lpass_data *drvdata = platform_get_drvdata(pdev);
|
|
|
|
struct lpass_variant *variant = drvdata->variant;
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
int ret, i;
|
|
|
|
|
|
|
|
drvdata->clks = devm_kcalloc(dev, variant->num_clks,
|
|
|
|
sizeof(*drvdata->clks), GFP_KERNEL);
|
|
|
|
drvdata->num_clks = variant->num_clks;
|
|
|
|
|
|
|
|
for (i = 0; i < drvdata->num_clks; i++)
|
|
|
|
drvdata->clks[i].id = variant->clk_name[i];
|
|
|
|
|
|
|
|
ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Failed to get clocks %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "sc7180 clk_enable failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc7180_lpass_exit(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct lpass_data *drvdata = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct lpass_variant sc7180_data = {
|
|
|
|
.i2sctrl_reg_base = 0x1000,
|
|
|
|
.i2sctrl_reg_stride = 0x1000,
|
|
|
|
.i2s_ports = 3,
|
|
|
|
.irq_reg_base = 0x9000,
|
|
|
|
.irq_reg_stride = 0x1000,
|
|
|
|
.irq_ports = 3,
|
|
|
|
.rdma_reg_base = 0xC000,
|
|
|
|
.rdma_reg_stride = 0x1000,
|
|
|
|
.rdma_channels = 5,
|
2020-10-08 13:17:03 +08:00
|
|
|
.hdmi_rdma_reg_base = 0x64000,
|
|
|
|
.hdmi_rdma_reg_stride = 0x1000,
|
ASoC: qcom: Fix number of HDMI RDMA channels on sc7180
Suspending/resuming with an HDMI dongle attached leads to crashes from
an audio regmap.
Unable to handle kernel paging request at virtual address ffffffc018068000
Mem abort info:
ESR = 0x96000047
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
Data abort info:
ISV = 0, ISS = 0x00000047
CM = 0, WnR = 1
swapper pgtable: 4k pages, 39-bit VAs, pgdp=0000000081b12000
[ffffffc018068000] pgd=0000000275d14003, pud=0000000275d14003, pmd=000000026365d003, pte=0000000000000000
Internal error: Oops: 96000047 [#1] PREEMPT SMP
Call trace:
regmap_mmio_write32le+0x2c/0x40
regmap_mmio_write+0x48/0x6c
_regmap_bus_reg_write+0x34/0x44
_regmap_write+0x100/0x150
regcache_default_sync+0xc0/0x138
regcache_sync+0x188/0x26c
lpass_platform_pcmops_resume+0x48/0x54 [snd_soc_lpass_platform]
snd_soc_component_resume+0x28/0x40
soc_resume_deferred+0x6c/0x178
process_one_work+0x208/0x3c8
worker_thread+0x23c/0x3e8
kthread+0x144/0x178
ret_from_fork+0x10/0x18
Code: d503201f d50332bf f94002a8 8b344108 (b9000113)
I can reliably reproduce this problem by running 'tail' on the registers
file in debugfs for the hdmi regmap.
# tail /sys/kernel/debug/regmap/62d87000.lpass-lpass_hdmi/registers
[ 84.658733] Unable to handle kernel paging request at virtual address ffffffd0128e800c
This crash happens because we're trying to read registers from the
regmap beyond the length of the mapping created by ioremap().
The number of hdmi_rdma_channels determines the size of the regmap via
this code in sound/soc/qcom/lpass-cpu.c:
lpass_hdmi_regmap_config.max_register = LPAIF_HDMI_RDMAPER_REG(variant, variant->hdmi_rdma_channels);
According to debugfs the size of the regmap is 0x68010 but according to
the DTS file posted in [1] the size is only 0x68000 (see the first reg
property of the lpass_cpu node). Let's change the number of channels to
be 3 instead of 4 so the math works out to have a max register of
0x67010, nicely fitting inside of the region size of 0x68000.
Note: I tried to bump up the size of the register region to the next
page to include the 0x68010 register but then the tail command caused
SErrors with an async abort, implying that the register region doesn't
exist or it isn't clocked because the bus is telling us that the
register read failed. I reduce the number of channels and played audio
through the HDMI channel and it kept working so I think this is correct.
Fixes: 2ad63dc8df6b ("ASoC: qcom: sc7180: Add support for audio over DP")
Link: https://lore.kernel.org/r/1601448168-18396-2-git-send-email-srivasam@codeaurora.org [1]
Cc: V Sujith Kumar Reddy <vsujithk@codeaurora.org>
Cc: Srinivasa Rao <srivasam@codeaurora.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Cheng-Yi Chiang <cychiang@chromium.org>
Signed-off-by: Stephen Boyd <swboyd@chromium.org>
Link: https://lore.kernel.org/r/20210115203329.846824-1-swboyd@chromium.org
Signed-off-by: Mark Brown <broonie@kernel.org>
2021-01-16 04:33:29 +08:00
|
|
|
.hdmi_rdma_channels = 3,
|
2020-08-14 18:53:05 +08:00
|
|
|
.dmactl_audif_start = 1,
|
|
|
|
.wrdma_reg_base = 0x18000,
|
|
|
|
.wrdma_reg_stride = 0x1000,
|
|
|
|
.wrdma_channel_start = 5,
|
|
|
|
.wrdma_channels = 4,
|
|
|
|
|
|
|
|
.loopback = REG_FIELD_ID(0x1000, 17, 17, 3, 0x1000),
|
|
|
|
.spken = REG_FIELD_ID(0x1000, 16, 16, 3, 0x1000),
|
|
|
|
.spkmode = REG_FIELD_ID(0x1000, 11, 15, 3, 0x1000),
|
|
|
|
.spkmono = REG_FIELD_ID(0x1000, 10, 10, 3, 0x1000),
|
|
|
|
.micen = REG_FIELD_ID(0x1000, 9, 9, 3, 0x1000),
|
|
|
|
.micmode = REG_FIELD_ID(0x1000, 4, 8, 3, 0x1000),
|
|
|
|
.micmono = REG_FIELD_ID(0x1000, 3, 3, 3, 0x1000),
|
|
|
|
.wssrc = REG_FIELD_ID(0x1000, 2, 2, 3, 0x1000),
|
2020-10-27 19:34:34 +08:00
|
|
|
.bitwidth = REG_FIELD_ID(0x1000, 0, 1, 3, 0x1000),
|
2020-08-14 18:53:05 +08:00
|
|
|
|
|
|
|
.rdma_dyncclk = REG_FIELD_ID(0xC000, 21, 21, 5, 0x1000),
|
|
|
|
.rdma_bursten = REG_FIELD_ID(0xC000, 20, 20, 5, 0x1000),
|
|
|
|
.rdma_wpscnt = REG_FIELD_ID(0xC000, 16, 19, 5, 0x1000),
|
2020-10-08 13:17:03 +08:00
|
|
|
.rdma_intf = REG_FIELD_ID(0xC000, 12, 15, 5, 0x1000),
|
2020-08-14 18:53:05 +08:00
|
|
|
.rdma_fifowm = REG_FIELD_ID(0xC000, 1, 5, 5, 0x1000),
|
|
|
|
.rdma_enable = REG_FIELD_ID(0xC000, 0, 0, 5, 0x1000),
|
|
|
|
|
|
|
|
.wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 4, 0x1000),
|
|
|
|
.wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 4, 0x1000),
|
|
|
|
.wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 4, 0x1000),
|
|
|
|
.wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 4, 0x1000),
|
|
|
|
.wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 5, 4, 0x1000),
|
|
|
|
.wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 4, 0x1000),
|
|
|
|
|
2020-10-08 13:17:03 +08:00
|
|
|
.hdmi_tx_ctl_addr = 0x1000,
|
|
|
|
.hdmi_legacy_addr = 0x1008,
|
|
|
|
.hdmi_vbit_addr = 0x610c0,
|
|
|
|
.hdmi_ch_lsb_addr = 0x61048,
|
|
|
|
.hdmi_ch_msb_addr = 0x6104c,
|
|
|
|
.ch_stride = 0x8,
|
|
|
|
.hdmi_parity_addr = 0x61034,
|
|
|
|
.hdmi_dmactl_addr = 0x61038,
|
|
|
|
.hdmi_dma_stride = 0x4,
|
|
|
|
.hdmi_DP_addr = 0x610c8,
|
|
|
|
.hdmi_sstream_addr = 0x6101c,
|
|
|
|
.hdmi_irq_reg_base = 0x63000,
|
|
|
|
.hdmi_irq_ports = 1,
|
|
|
|
|
|
|
|
.hdmi_rdma_dyncclk = REG_FIELD_ID(0x64000, 14, 14, 4, 0x1000),
|
|
|
|
.hdmi_rdma_bursten = REG_FIELD_ID(0x64000, 13, 13, 4, 0x1000),
|
|
|
|
.hdmi_rdma_burst8 = REG_FIELD_ID(0x64000, 15, 15, 4, 0x1000),
|
|
|
|
.hdmi_rdma_burst16 = REG_FIELD_ID(0x64000, 16, 16, 4, 0x1000),
|
|
|
|
.hdmi_rdma_dynburst = REG_FIELD_ID(0x64000, 18, 18, 4, 0x1000),
|
|
|
|
.hdmi_rdma_wpscnt = REG_FIELD_ID(0x64000, 10, 12, 4, 0x1000),
|
|
|
|
.hdmi_rdma_fifowm = REG_FIELD_ID(0x64000, 1, 5, 4, 0x1000),
|
|
|
|
.hdmi_rdma_enable = REG_FIELD_ID(0x64000, 0, 0, 4, 0x1000),
|
|
|
|
|
|
|
|
.sstream_en = REG_FIELD(0x6101c, 0, 0),
|
|
|
|
.dma_sel = REG_FIELD(0x6101c, 1, 2),
|
|
|
|
.auto_bbit_en = REG_FIELD(0x6101c, 3, 3),
|
|
|
|
.layout = REG_FIELD(0x6101c, 4, 4),
|
|
|
|
.layout_sp = REG_FIELD(0x6101c, 5, 8),
|
|
|
|
.set_sp_on_en = REG_FIELD(0x6101c, 10, 10),
|
|
|
|
.dp_audio = REG_FIELD(0x6101c, 11, 11),
|
|
|
|
.dp_staffing_en = REG_FIELD(0x6101c, 12, 12),
|
|
|
|
.dp_sp_b_hw_en = REG_FIELD(0x6101c, 13, 13),
|
|
|
|
|
|
|
|
.mute = REG_FIELD(0x610c8, 0, 0),
|
|
|
|
.as_sdp_cc = REG_FIELD(0x610c8, 1, 3),
|
|
|
|
.as_sdp_ct = REG_FIELD(0x610c8, 4, 7),
|
|
|
|
.aif_db4 = REG_FIELD(0x610c8, 8, 15),
|
|
|
|
.frequency = REG_FIELD(0x610c8, 16, 21),
|
|
|
|
.mst_index = REG_FIELD(0x610c8, 28, 29),
|
|
|
|
.dptx_index = REG_FIELD(0x610c8, 30, 31),
|
|
|
|
|
|
|
|
.soft_reset = REG_FIELD(0x1000, 31, 31),
|
|
|
|
.force_reset = REG_FIELD(0x1000, 30, 30),
|
|
|
|
|
|
|
|
.use_hw_chs = REG_FIELD(0x61038, 0, 0),
|
|
|
|
.use_hw_usr = REG_FIELD(0x61038, 1, 1),
|
|
|
|
.hw_chs_sel = REG_FIELD(0x61038, 2, 4),
|
|
|
|
.hw_usr_sel = REG_FIELD(0x61038, 5, 6),
|
|
|
|
|
|
|
|
.replace_vbit = REG_FIELD(0x610c0, 0, 0),
|
|
|
|
.vbit_stream = REG_FIELD(0x610c0, 1, 1),
|
|
|
|
|
|
|
|
.legacy_en = REG_FIELD(0x1008, 0, 0),
|
|
|
|
.calc_en = REG_FIELD(0x61034, 0, 0),
|
|
|
|
.lsb_bits = REG_FIELD(0x61048, 0, 31),
|
|
|
|
.msb_bits = REG_FIELD(0x6104c, 0, 31),
|
|
|
|
|
|
|
|
|
2020-08-14 18:53:05 +08:00
|
|
|
.clk_name = (const char*[]) {
|
|
|
|
"pcnoc-sway-clk",
|
|
|
|
"audio-core",
|
|
|
|
"pcnoc-mport-clk",
|
|
|
|
},
|
|
|
|
.num_clks = 3,
|
|
|
|
.dai_driver = sc7180_lpass_cpu_dai_driver,
|
|
|
|
.num_dai = ARRAY_SIZE(sc7180_lpass_cpu_dai_driver),
|
|
|
|
.dai_osr_clk_names = (const char *[]) {
|
|
|
|
"mclk0",
|
|
|
|
"null",
|
|
|
|
},
|
|
|
|
.dai_bit_clk_names = (const char *[]) {
|
|
|
|
"mi2s-bit-clk0",
|
|
|
|
"mi2s-bit-clk1",
|
|
|
|
},
|
|
|
|
.init = sc7180_lpass_init,
|
|
|
|
.exit = sc7180_lpass_exit,
|
|
|
|
.alloc_dma_channel = sc7180_lpass_alloc_dma_channel,
|
|
|
|
.free_dma_channel = sc7180_lpass_free_dma_channel,
|
|
|
|
};
|
|
|
|
|
2020-11-26 00:44:22 +08:00
|
|
|
static const struct of_device_id sc7180_lpass_cpu_device_id[] __maybe_unused = {
|
2020-08-14 18:53:05 +08:00
|
|
|
{.compatible = "qcom,sc7180-lpass-cpu", .data = &sc7180_data},
|
|
|
|
{}
|
|
|
|
};
|
2020-09-17 02:15:55 +08:00
|
|
|
MODULE_DEVICE_TABLE(of, sc7180_lpass_cpu_device_id);
|
2020-08-14 18:53:05 +08:00
|
|
|
|
|
|
|
static struct platform_driver sc7180_lpass_cpu_platform_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "sc7180-lpass-cpu",
|
|
|
|
.of_match_table = of_match_ptr(sc7180_lpass_cpu_device_id),
|
|
|
|
},
|
|
|
|
.probe = asoc_qcom_lpass_cpu_platform_probe,
|
|
|
|
.remove = asoc_qcom_lpass_cpu_platform_remove,
|
2020-11-14 02:38:22 +08:00
|
|
|
.shutdown = asoc_qcom_lpass_cpu_platform_shutdown,
|
2020-08-14 18:53:05 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
module_platform_driver(sc7180_lpass_cpu_platform_driver);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("SC7180 LPASS CPU DRIVER");
|
|
|
|
MODULE_LICENSE("GPL v2");
|