brintos

brintos / linux-shallow public Read only

0
0
Text · 15.6 KiB · d38090e Raw
670 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * jh7110_tdm.c -- StarFive JH7110 TDM driver4 *5 * Copyright (C) 2023 StarFive Technology Co., Ltd.6 *7 * Author: Walker Chen <walker.chen@starfivetech.com>8 */9 10#include <linux/clk.h>11#include <linux/device.h>12#include <linux/dmaengine.h>13#include <linux/module.h>14#include <linux/of_irq.h>15#include <linux/of_platform.h>16#include <linux/pm_runtime.h>17#include <linux/regmap.h>18#include <linux/reset.h>19#include <linux/types.h>20#include <sound/dmaengine_pcm.h>21#include <sound/initval.h>22#include <sound/pcm.h>23#include <sound/pcm_params.h>24#include <sound/soc.h>25#include <sound/soc-dai.h>26 27#define TDM_PCMGBCR			0x0028	#define PCMGBCR_ENABLE		BIT(0)29	#define CLKPOL_BIT		530	#define ELM_BIT			331	#define SYNCM_BIT		232	#define MS_BIT			133#define TDM_PCMTXCR			0x0434	#define PCMTXCR_TXEN		BIT(0)35	#define IFL_BIT			1136	#define WL_BIT			837	#define SSCALE_BIT		438	#define SL_BIT			239	#define LRJ_BIT			140#define TDM_PCMRXCR			0x0841	#define PCMRXCR_RXEN		BIT(0)42#define TDM_PCMDIV			0x0c43 44#define JH7110_TDM_FIFO			0x170c000045#define JH7110_TDM_FIFO_DEPTH		3246 47enum TDM_MASTER_SLAVE_MODE {48	TDM_AS_MASTER = 0,49	TDM_AS_SLAVE,50};51 52enum TDM_CLKPOL {53	/* tx raising and rx falling */54	TDM_TX_RASING_RX_FALLING = 0,55	/* tx falling and rx raising */56	TDM_TX_FALLING_RX_RASING,57};58 59enum TDM_ELM {60	/* only work while SYNCM=0 */61	TDM_ELM_LATE = 0,62	TDM_ELM_EARLY,63};64 65enum TDM_SYNCM {66	/* short frame sync */67	TDM_SYNCM_SHORT = 0,68	/* long frame sync */69	TDM_SYNCM_LONG,70};71 72enum TDM_IFL {73	/* FIFO to send or received : half-1/2, Quarter-1/4 */74	TDM_FIFO_HALF = 0,75	TDM_FIFO_QUARTER,76};77 78enum TDM_WL {79	/* send or received word length */80	TDM_8BIT_WORD_LEN = 0,81	TDM_16BIT_WORD_LEN,82	TDM_20BIT_WORD_LEN,83	TDM_24BIT_WORD_LEN,84	TDM_32BIT_WORD_LEN,85};86 87enum TDM_SL {88	/* send or received slot length */89	TDM_8BIT_SLOT_LEN = 0,90	TDM_16BIT_SLOT_LEN,91	TDM_32BIT_SLOT_LEN,92};93 94enum TDM_LRJ {95	/* left-justify or right-justify */96	TDM_RIGHT_JUSTIFY = 0,97	TDM_LEFT_JUSTIFT,98};99 100struct tdm_chan_cfg {101	enum TDM_IFL ifl;102	enum TDM_WL  wl;103	unsigned char sscale;104	enum TDM_SL  sl;105	enum TDM_LRJ lrj;106	unsigned char enable;107};108 109struct jh7110_tdm_dev {110	void __iomem *tdm_base;111	struct device *dev;112	struct clk_bulk_data clks[6];113	struct reset_control *resets;114 115	enum TDM_CLKPOL clkpolity;116	enum TDM_ELM	elm;117	enum TDM_SYNCM	syncm;118	enum TDM_MASTER_SLAVE_MODE ms_mode;119 120	struct tdm_chan_cfg tx;121	struct tdm_chan_cfg rx;122 123	u16 syncdiv;124	u32 samplerate;125	u32 pcmclk;126 127	/* data related to DMA transfers b/w tdm and DMAC */128	struct snd_dmaengine_dai_dma_data play_dma_data;129	struct snd_dmaengine_dai_dma_data capture_dma_data;130	u32 saved_pcmgbcr;131	u32 saved_pcmtxcr;132	u32 saved_pcmrxcr;133	u32 saved_pcmdiv;134};135 136static inline u32 jh7110_tdm_readl(struct jh7110_tdm_dev *tdm, u16 reg)137{138	return readl_relaxed(tdm->tdm_base + reg);139}140 141static inline void jh7110_tdm_writel(struct jh7110_tdm_dev *tdm, u16 reg, u32 val)142{143	writel_relaxed(val, tdm->tdm_base + reg);144}145 146static void jh7110_tdm_save_context(struct jh7110_tdm_dev *tdm,147				    struct snd_pcm_substream *substream)148{149	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)150		tdm->saved_pcmtxcr = jh7110_tdm_readl(tdm, TDM_PCMTXCR);151	else152		tdm->saved_pcmrxcr = jh7110_tdm_readl(tdm, TDM_PCMRXCR);153}154 155static void jh7110_tdm_start(struct jh7110_tdm_dev *tdm,156			     struct snd_pcm_substream *substream)157{158	u32 data;159 160	data = jh7110_tdm_readl(tdm, TDM_PCMGBCR);161	jh7110_tdm_writel(tdm, TDM_PCMGBCR, data | PCMGBCR_ENABLE);162 163	/* restore context */164	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)165		jh7110_tdm_writel(tdm, TDM_PCMTXCR, tdm->saved_pcmtxcr | PCMTXCR_TXEN);166	else167		jh7110_tdm_writel(tdm, TDM_PCMRXCR, tdm->saved_pcmrxcr | PCMRXCR_RXEN);168}169 170static void jh7110_tdm_stop(struct jh7110_tdm_dev *tdm,171			    struct snd_pcm_substream *substream)172{173	unsigned int val;174 175	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {176		val = jh7110_tdm_readl(tdm, TDM_PCMTXCR);177		val &= ~PCMTXCR_TXEN;178		jh7110_tdm_writel(tdm, TDM_PCMTXCR, val);179	} else {180		val = jh7110_tdm_readl(tdm, TDM_PCMRXCR);181		val &= ~PCMRXCR_RXEN;182		jh7110_tdm_writel(tdm, TDM_PCMRXCR, val);183	}184}185 186static int jh7110_tdm_syncdiv(struct jh7110_tdm_dev *tdm)187{188	u32 sl, sscale, syncdiv;189 190	if (tdm->rx.sl >= tdm->tx.sl)191		sl = tdm->rx.sl;192	else193		sl = tdm->tx.sl;194 195	if (tdm->rx.sscale >= tdm->tx.sscale)196		sscale = tdm->rx.sscale;197	else198		sscale = tdm->tx.sscale;199 200	syncdiv = tdm->pcmclk / tdm->samplerate - 1;201 202	if ((syncdiv + 1) < (sl * sscale)) {203		dev_err(tdm->dev, "Failed to set syncdiv!\n");204		return -EINVAL;205	}206 207	if (tdm->syncm == TDM_SYNCM_LONG &&208	    (tdm->rx.sscale <= 1 || tdm->tx.sscale <= 1) &&209	    ((syncdiv + 1) <= sl)) {210		dev_err(tdm->dev, "Wrong syncdiv! It must be (syncdiv+1) > max[tx.sl, rx.sl]\n");211		return -EINVAL;212	}213 214	jh7110_tdm_writel(tdm, TDM_PCMDIV, syncdiv);215	return 0;216}217 218static int jh7110_tdm_config(struct jh7110_tdm_dev *tdm,219			     struct snd_pcm_substream *substream)220{221	u32 datarx, datatx;222	int ret;223 224	ret = jh7110_tdm_syncdiv(tdm);225	if (ret)226		return ret;227 228	datarx = (tdm->rx.ifl << IFL_BIT) |229		  (tdm->rx.wl << WL_BIT) |230		  (tdm->rx.sscale << SSCALE_BIT) |231		  (tdm->rx.sl << SL_BIT) |232		  (tdm->rx.lrj << LRJ_BIT);233 234	datatx = (tdm->tx.ifl << IFL_BIT) |235		  (tdm->tx.wl << WL_BIT) |236		  (tdm->tx.sscale << SSCALE_BIT) |237		  (tdm->tx.sl << SL_BIT) |238		  (tdm->tx.lrj << LRJ_BIT);239 240	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)241		jh7110_tdm_writel(tdm, TDM_PCMTXCR, datatx);242	else243		jh7110_tdm_writel(tdm, TDM_PCMRXCR, datarx);244 245	return 0;246}247 248static void jh7110_tdm_clk_disable(struct jh7110_tdm_dev *tdm)249{250	clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), tdm->clks);251}252 253static int jh7110_tdm_clk_enable(struct jh7110_tdm_dev *tdm)254{255	int ret;256 257	ret = clk_bulk_prepare_enable(ARRAY_SIZE(tdm->clks), tdm->clks);258	if (ret) {259		dev_err(tdm->dev, "Failed to enable tdm clocks\n");260		return ret;261	}262 263	ret = reset_control_deassert(tdm->resets);264	if (ret) {265		dev_err(tdm->dev, "Failed to deassert tdm resets\n");266		goto dis_tdm_clk;267	}268 269	/* select tdm_ext clock as the clock source for tdm */270	ret = clk_set_parent(tdm->clks[5].clk, tdm->clks[4].clk);271	if (ret) {272		dev_err(tdm->dev, "Can't set extern clock source for clk_tdm\n");273		goto dis_tdm_clk;274	}275 276	return 0;277 278dis_tdm_clk:279	clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), tdm->clks);280 281	return ret;282}283 284static int jh7110_tdm_runtime_suspend(struct device *dev)285{286	struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev);287 288	jh7110_tdm_clk_disable(tdm);289	return 0;290}291 292static int jh7110_tdm_runtime_resume(struct device *dev)293{294	struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev);295 296	return jh7110_tdm_clk_enable(tdm);297}298 299static int jh7110_tdm_system_suspend(struct device *dev)300{301	struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev);302 303	/* save context */304	tdm->saved_pcmgbcr = jh7110_tdm_readl(tdm, TDM_PCMGBCR);305	tdm->saved_pcmdiv = jh7110_tdm_readl(tdm, TDM_PCMDIV);306 307	return pm_runtime_force_suspend(dev);308}309 310static int jh7110_tdm_system_resume(struct device *dev)311{312	struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev);313 314	/* restore context */315	jh7110_tdm_writel(tdm, TDM_PCMGBCR, tdm->saved_pcmgbcr);316	jh7110_tdm_writel(tdm, TDM_PCMDIV, tdm->saved_pcmdiv);317 318	return pm_runtime_force_resume(dev);319}320 321static const struct snd_soc_component_driver jh7110_tdm_component = {322	.name = "jh7110-tdm",323};324 325static int jh7110_tdm_startup(struct snd_pcm_substream *substream,326			      struct snd_soc_dai *cpu_dai)327{328	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);329	struct snd_soc_dai_link *dai_link = rtd->dai_link;330 331	dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;332 333	return 0;334}335 336static int jh7110_tdm_hw_params(struct snd_pcm_substream *substream,337				struct snd_pcm_hw_params *params,338				struct snd_soc_dai *dai)339{340	struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai);341	int chan_wl, chan_sl, chan_nr;342	unsigned int data_width;343	unsigned int dma_bus_width;344	struct snd_dmaengine_dai_dma_data *dma_data = NULL;345	int ret;346 347	data_width = params_width(params);348 349	tdm->samplerate = params_rate(params);350	tdm->pcmclk = params_channels(params) * tdm->samplerate * data_width;351 352	switch (params_format(params)) {353	case SNDRV_PCM_FORMAT_S16_LE:354		chan_wl = TDM_16BIT_WORD_LEN;355		chan_sl = TDM_16BIT_SLOT_LEN;356		dma_bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES;357		break;358 359	case SNDRV_PCM_FORMAT_S32_LE:360		chan_wl = TDM_32BIT_WORD_LEN;361		chan_sl = TDM_32BIT_SLOT_LEN;362		dma_bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES;363		break;364 365	default:366		dev_err(tdm->dev, "tdm: unsupported PCM fmt");367		return -EINVAL;368	}369 370	chan_nr = params_channels(params);371	switch (chan_nr) {372	case 1:373	case 2:374	case 4:375	case 6:376	case 8:377		break;378	default:379		dev_err(tdm->dev, "channel not supported\n");380		return -EINVAL;381	}382 383	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {384		tdm->tx.wl = chan_wl;385		tdm->tx.sl = chan_sl;386		tdm->tx.sscale = chan_nr;387		tdm->play_dma_data.addr_width = dma_bus_width;388		dma_data = &tdm->play_dma_data;389	} else {390		tdm->rx.wl = chan_wl;391		tdm->rx.sl = chan_sl;392		tdm->rx.sscale = chan_nr;393		tdm->capture_dma_data.addr_width = dma_bus_width;394		dma_data = &tdm->capture_dma_data;395	}396 397	snd_soc_dai_set_dma_data(dai, substream, dma_data);398 399	ret = jh7110_tdm_config(tdm, substream);400	if (ret)401		return ret;402 403	jh7110_tdm_save_context(tdm, substream);404	return 0;405}406 407static int jh7110_tdm_trigger(struct snd_pcm_substream *substream,408			      int cmd, struct snd_soc_dai *dai)409{410	struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai);411	int ret = 0;412 413	switch (cmd) {414	case SNDRV_PCM_TRIGGER_START:415	case SNDRV_PCM_TRIGGER_RESUME:416	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:417		jh7110_tdm_start(tdm, substream);418		break;419 420	case SNDRV_PCM_TRIGGER_STOP:421	case SNDRV_PCM_TRIGGER_SUSPEND:422	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:423		jh7110_tdm_stop(tdm, substream);424		break;425	default:426		ret = -EINVAL;427		break;428	}429 430	return ret;431}432 433static int jh7110_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,434				  unsigned int fmt)435{436	struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(cpu_dai);437	unsigned int gbcr;438 439	/* set master/slave audio interface */440	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {441	case SND_SOC_DAIFMT_BP_FP:442		/* cpu is master */443		tdm->ms_mode = TDM_AS_MASTER;444		break;445	case SND_SOC_DAIFMT_BC_FC:446		/* codec is master */447		tdm->ms_mode = TDM_AS_SLAVE;448		break;449	case SND_SOC_DAIFMT_BC_FP:450	case SND_SOC_DAIFMT_BP_FC:451		return -EINVAL;452	default:453		dev_dbg(tdm->dev, "dwc : Invalid clock provider format\n");454		return -EINVAL;455	}456 457	gbcr = (tdm->clkpolity << CLKPOL_BIT) |458		(tdm->elm << ELM_BIT) |459		(tdm->syncm << SYNCM_BIT) |460		(tdm->ms_mode << MS_BIT);461	jh7110_tdm_writel(tdm, TDM_PCMGBCR, gbcr);462 463	return 0;464}465 466static int jh7110_tdm_dai_probe(struct snd_soc_dai *dai)467{468	struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai);469 470	snd_soc_dai_init_dma_data(dai, &tdm->play_dma_data, &tdm->capture_dma_data);471	snd_soc_dai_set_drvdata(dai, tdm);472	return 0;473}474 475static const struct snd_soc_dai_ops jh7110_tdm_dai_ops = {476	.probe		= jh7110_tdm_dai_probe,477	.startup	= jh7110_tdm_startup,478	.hw_params	= jh7110_tdm_hw_params,479	.trigger	= jh7110_tdm_trigger,480	.set_fmt	= jh7110_tdm_set_dai_fmt,481};482 483#define JH7110_TDM_RATES	SNDRV_PCM_RATE_8000_48000484 485#define JH7110_TDM_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | \486				 SNDRV_PCM_FMTBIT_S32_LE)487 488static struct snd_soc_dai_driver jh7110_tdm_dai = {489	.name = "sf_tdm",490	.id = 0,491	.playback = {492		.stream_name    = "Playback",493		.channels_min   = 1,494		.channels_max   = 8,495		.rates          = JH7110_TDM_RATES,496		.formats        = JH7110_TDM_FORMATS,497	},498	.capture = {499		.stream_name    = "Capture",500		.channels_min   = 1,501		.channels_max   = 8,502		.rates          = JH7110_TDM_RATES,503		.formats        = JH7110_TDM_FORMATS,504	},505	.ops = &jh7110_tdm_dai_ops,506	.symmetric_rate = 1,507};508 509static const struct snd_pcm_hardware jh7110_pcm_hardware = {510	.info			= (SNDRV_PCM_INFO_MMAP		|511				   SNDRV_PCM_INFO_MMAP_VALID	|512				   SNDRV_PCM_INFO_PAUSE		|513				   SNDRV_PCM_INFO_RESUME	|514				   SNDRV_PCM_INFO_INTERLEAVED	|515				   SNDRV_PCM_INFO_BLOCK_TRANSFER),516	.buffer_bytes_max	= 192512,517	.period_bytes_min	= 4096,518	.period_bytes_max	= 32768,519	.periods_min		= 1,520	.periods_max		= 48,521	.fifo_size		= 16,522};523 524static const struct snd_dmaengine_pcm_config jh7110_dmaengine_pcm_config = {525	.pcm_hardware = &jh7110_pcm_hardware,526	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,527	.prealloc_buffer_size = 192512,528};529 530static void jh7110_tdm_init_params(struct jh7110_tdm_dev *tdm)531{532	tdm->clkpolity = TDM_TX_RASING_RX_FALLING;533	tdm->elm = TDM_ELM_LATE;534	tdm->syncm = TDM_SYNCM_SHORT;535 536	tdm->rx.ifl = TDM_FIFO_HALF;537	tdm->tx.ifl = TDM_FIFO_HALF;538	tdm->rx.wl = TDM_16BIT_WORD_LEN;539	tdm->tx.wl = TDM_16BIT_WORD_LEN;540	tdm->rx.sscale = 2;541	tdm->tx.sscale = 2;542	tdm->rx.lrj = TDM_LEFT_JUSTIFT;543	tdm->tx.lrj = TDM_LEFT_JUSTIFT;544 545	tdm->play_dma_data.addr = JH7110_TDM_FIFO;546	tdm->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;547	tdm->play_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2;548	tdm->play_dma_data.maxburst = 16;549 550	tdm->capture_dma_data.addr = JH7110_TDM_FIFO;551	tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;552	tdm->capture_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2;553	tdm->capture_dma_data.maxburst = 8;554}555 556static int jh7110_tdm_clk_reset_get(struct platform_device *pdev,557				    struct jh7110_tdm_dev *tdm)558{559	int ret;560 561	tdm->clks[0].id = "mclk_inner";562	tdm->clks[1].id = "tdm_ahb";563	tdm->clks[2].id = "tdm_apb";564	tdm->clks[3].id = "tdm_internal";565	tdm->clks[4].id = "tdm_ext";566	tdm->clks[5].id = "tdm";567 568	ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks);569	if (ret) {570		dev_err(&pdev->dev, "Failed to get tdm clocks\n");571		return ret;572	}573 574	tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev);575	if (IS_ERR(tdm->resets)) {576		dev_err(&pdev->dev, "Failed to get tdm resets\n");577		return PTR_ERR(tdm->resets);578	}579 580	return 0;581}582 583static int jh7110_tdm_probe(struct platform_device *pdev)584{585	struct jh7110_tdm_dev *tdm;586	int ret;587 588	tdm = devm_kzalloc(&pdev->dev, sizeof(*tdm), GFP_KERNEL);589	if (!tdm)590		return -ENOMEM;591 592	tdm->tdm_base = devm_platform_ioremap_resource(pdev, 0);593	if (IS_ERR(tdm->tdm_base))594		return PTR_ERR(tdm->tdm_base);595 596	tdm->dev = &pdev->dev;597 598	ret = jh7110_tdm_clk_reset_get(pdev, tdm);599	if (ret) {600		dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n");601		return ret;602	}603 604	jh7110_tdm_init_params(tdm);605 606	dev_set_drvdata(&pdev->dev, tdm);607	ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component,608					      &jh7110_tdm_dai, 1);609	if (ret) {610		dev_err(&pdev->dev, "Failed to register dai\n");611		return ret;612	}613 614	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,615					      &jh7110_dmaengine_pcm_config,616					      SND_DMAENGINE_PCM_FLAG_COMPAT);617	if (ret) {618		dev_err(&pdev->dev, "Could not register pcm: %d\n", ret);619		return ret;620	}621 622	pm_runtime_enable(&pdev->dev);623	if (!pm_runtime_enabled(&pdev->dev)) {624		ret = jh7110_tdm_runtime_resume(&pdev->dev);625		if (ret)626			goto err_pm_disable;627	}628 629	return 0;630 631err_pm_disable:632	pm_runtime_disable(&pdev->dev);633 634	return ret;635}636 637static void jh7110_tdm_dev_remove(struct platform_device *pdev)638{639	pm_runtime_disable(&pdev->dev);640}641 642static const struct of_device_id jh7110_tdm_of_match[] = {643	{ .compatible = "starfive,jh7110-tdm", },644	{}645};646 647MODULE_DEVICE_TABLE(of, jh7110_tdm_of_match);648 649static const struct dev_pm_ops jh7110_tdm_pm_ops = {650	RUNTIME_PM_OPS(jh7110_tdm_runtime_suspend,651		       jh7110_tdm_runtime_resume, NULL)652	SYSTEM_SLEEP_PM_OPS(jh7110_tdm_system_suspend,653			    jh7110_tdm_system_resume)654};655 656static struct platform_driver jh7110_tdm_driver = {657	.driver = {658		.name = "jh7110-tdm",659		.of_match_table = jh7110_tdm_of_match,660		.pm = pm_ptr(&jh7110_tdm_pm_ops),661	},662	.probe = jh7110_tdm_probe,663	.remove = jh7110_tdm_dev_remove,664};665module_platform_driver(jh7110_tdm_driver);666 667MODULE_DESCRIPTION("StarFive JH7110 TDM ASoC Driver");668MODULE_AUTHOR("Walker Chen <walker.chen@starfivetech.com>");669MODULE_LICENSE("GPL");670