brintos

brintos / linux-shallow public Read only

0
0
Text · 7.9 KiB · aa629ef Raw
308 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// CS40L50 Advanced Haptic Driver with waveform memory,4// integrated DSP, and closed-loop algorithms5//6// Copyright 2024 Cirrus Logic, Inc.7//8// Author: James Ogletree <james.ogletree@cirrus.com>9 10#include <linux/bitfield.h>11#include <linux/mfd/cs40l50.h>12#include <sound/pcm_params.h>13#include <sound/soc.h>14 15#define CS40L50_REFCLK_INPUT		0x2C0416#define CS40L50_ASP_CONTROL2		0x480817#define CS40L50_ASP_DATA_CONTROL5	0x484018 19/* PLL Config */20#define CS40L50_PLL_REFCLK_BCLK		0x021#define CS40L50_PLL_REFCLK_MCLK		0x522#define CS40L50_PLL_REEFCLK_MCLK_CFG	0x0023#define CS40L50_PLL_REFCLK_LOOP_MASK	BIT(11)24#define CS40L50_PLL_REFCLK_OPEN_LOOP	125#define CS40L50_PLL_REFCLK_CLOSED_LOOP	026#define CS40L50_PLL_REFCLK_LOOP_SHIFT	1127#define CS40L50_PLL_REFCLK_FREQ_MASK	GENMASK(10, 5)28#define CS40L50_PLL_REFCLK_FREQ_SHIFT	529#define CS40L50_PLL_REFCLK_SEL_MASK	GENMASK(2, 0)30#define CS40L50_BCLK_RATIO_DEFAULT	3231 32/* ASP Config */33#define CS40L50_ASP_RX_WIDTH_SHIFT	2434#define CS40L50_ASP_RX_WIDTH_MASK	GENMASK(31, 24)35#define CS40L50_ASP_RX_WL_MASK		GENMASK(5, 0)36#define CS40L50_ASP_FSYNC_INV_MASK	BIT(2)37#define CS40L50_ASP_BCLK_INV_MASK	BIT(6)38#define CS40L50_ASP_FMT_MASK		GENMASK(10, 8)39#define CS40L50_ASP_FMT_I2S		0x240 41struct cs40l50_pll_config {42	unsigned int freq;43	unsigned int cfg;44};45 46struct cs40l50_codec {47	struct device *dev;48	struct regmap *regmap;49	unsigned int daifmt;50	unsigned int bclk_ratio;51	unsigned int rate;52};53 54static const struct cs40l50_pll_config cs40l50_pll_cfg[] = {55	{ 32768, 0x00 },56	{ 1536000, 0x1B },57	{ 3072000, 0x21 },58	{ 6144000, 0x28 },59	{ 9600000, 0x30 },60	{ 12288000, 0x33 },61};62 63static int cs40l50_get_clk_config(const unsigned int freq, unsigned int *cfg)64{65	int i;66 67	for (i = 0; i < ARRAY_SIZE(cs40l50_pll_cfg); i++) {68		if (cs40l50_pll_cfg[i].freq == freq) {69			*cfg = cs40l50_pll_cfg[i].cfg;70			return 0;71		}72	}73 74	return -EINVAL;75}76 77static int cs40l50_swap_ext_clk(struct cs40l50_codec *codec, const unsigned int clk_src)78{79	unsigned int cfg;80	int ret;81 82	switch (clk_src) {83	case CS40L50_PLL_REFCLK_BCLK:84		ret = cs40l50_get_clk_config(codec->bclk_ratio * codec->rate, &cfg);85		if (ret)86			return ret;87		break;88	case CS40L50_PLL_REFCLK_MCLK:89		cfg = CS40L50_PLL_REEFCLK_MCLK_CFG;90		break;91	default:92		return -EINVAL;93	}94 95	ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,96				 CS40L50_PLL_REFCLK_LOOP_MASK,97				 CS40L50_PLL_REFCLK_OPEN_LOOP <<98				 CS40L50_PLL_REFCLK_LOOP_SHIFT);99	if (ret)100		return ret;101 102	ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,103				 CS40L50_PLL_REFCLK_FREQ_MASK |104				 CS40L50_PLL_REFCLK_SEL_MASK,105				 (cfg << CS40L50_PLL_REFCLK_FREQ_SHIFT) | clk_src);106	if (ret)107		return ret;108 109	return regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,110				  CS40L50_PLL_REFCLK_LOOP_MASK,111				  CS40L50_PLL_REFCLK_CLOSED_LOOP <<112				  CS40L50_PLL_REFCLK_LOOP_SHIFT);113}114 115static int cs40l50_clk_en(struct snd_soc_dapm_widget *w,116			  struct snd_kcontrol *kcontrol,117			  int event)118{119	struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm);120	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(comp);121	int ret;122 123	switch (event) {124	case SND_SOC_DAPM_POST_PMU:125		ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_STOP_PLAYBACK);126		if (ret)127			return ret;128 129		ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_START_I2S);130		if (ret)131			return ret;132 133		ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_BCLK);134		if (ret)135			return ret;136		break;137	case SND_SOC_DAPM_PRE_PMD:138		ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_MCLK);139		if (ret)140			return ret;141		break;142	default:143		return -EINVAL;144	}145 146	return 0;147}148 149static const struct snd_soc_dapm_widget cs40l50_dapm_widgets[] = {150	SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l50_clk_en,151			      SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),152	SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0),153	SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0),154	SND_SOC_DAPM_OUTPUT("OUT"),155};156 157static const struct snd_soc_dapm_route cs40l50_dapm_routes[] = {158	{ "ASP Playback", NULL, "ASP PLL" },159	{ "ASPRX1", NULL, "ASP Playback" },160	{ "ASPRX2", NULL, "ASP Playback" },161 162	{ "OUT", NULL, "ASPRX1" },163	{ "OUT", NULL, "ASPRX2" },164};165 166static int cs40l50_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)167{168	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(codec_dai->component);169 170	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC)171		return -EINVAL;172 173	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {174	case SND_SOC_DAIFMT_NB_NF:175		codec->daifmt = 0;176		break;177	case SND_SOC_DAIFMT_NB_IF:178		codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK;179		break;180	case SND_SOC_DAIFMT_IB_NF:181		codec->daifmt = CS40L50_ASP_BCLK_INV_MASK;182		break;183	case SND_SOC_DAIFMT_IB_IF:184		codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK | CS40L50_ASP_BCLK_INV_MASK;185		break;186	default:187		dev_err(codec->dev, "Invalid clock invert\n");188		return -EINVAL;189	}190 191	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {192	case SND_SOC_DAIFMT_I2S:193		codec->daifmt |= FIELD_PREP(CS40L50_ASP_FMT_MASK, CS40L50_ASP_FMT_I2S);194		break;195	default:196		dev_err(codec->dev, "Unsupported DAI format\n");197		return -EINVAL;198	}199 200	return 0;201}202 203static int cs40l50_hw_params(struct snd_pcm_substream *substream,204			     struct snd_pcm_hw_params *params,205			     struct snd_soc_dai *dai)206{207	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);208	unsigned int asp_rx_wl = params_width(params);209	int ret;210 211	codec->rate = params_rate(params);212 213	ret = regmap_update_bits(codec->regmap, CS40L50_ASP_DATA_CONTROL5,214				 CS40L50_ASP_RX_WL_MASK, asp_rx_wl);215	if (ret)216		return ret;217 218	codec->daifmt |= (asp_rx_wl << CS40L50_ASP_RX_WIDTH_SHIFT);219 220	return regmap_update_bits(codec->regmap, CS40L50_ASP_CONTROL2,221				  CS40L50_ASP_FSYNC_INV_MASK |222				  CS40L50_ASP_BCLK_INV_MASK |223				  CS40L50_ASP_FMT_MASK |224				  CS40L50_ASP_RX_WIDTH_MASK, codec->daifmt);225}226 227static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)228{229	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);230 231	codec->bclk_ratio = ratio;232 233	return 0;234}235 236static const struct snd_soc_dai_ops cs40l50_dai_ops = {237	.set_fmt = cs40l50_set_dai_fmt,238	.set_bclk_ratio = cs40l50_set_dai_bclk_ratio,239	.hw_params = cs40l50_hw_params,240};241 242static struct snd_soc_dai_driver cs40l50_dai[] = {243	{244		.name = "cs40l50-pcm",245		.id = 0,246		.playback = {247			.stream_name = "ASP Playback",248			.channels_min = 1,249			.channels_max = 2,250			.rates = SNDRV_PCM_RATE_48000,251			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,252		},253		.ops = &cs40l50_dai_ops,254	},255};256 257static int cs40l50_codec_probe(struct snd_soc_component *component)258{259	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(component);260 261	codec->bclk_ratio = CS40L50_BCLK_RATIO_DEFAULT;262 263	return 0;264}265 266static const struct snd_soc_component_driver soc_codec_dev_cs40l50 = {267	.probe = cs40l50_codec_probe,268	.dapm_widgets = cs40l50_dapm_widgets,269	.num_dapm_widgets = ARRAY_SIZE(cs40l50_dapm_widgets),270	.dapm_routes = cs40l50_dapm_routes,271	.num_dapm_routes = ARRAY_SIZE(cs40l50_dapm_routes),272};273 274static int cs40l50_codec_driver_probe(struct platform_device *pdev)275{276	struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);277	struct cs40l50_codec *codec;278 279	codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL);280	if (!codec)281		return -ENOMEM;282 283	codec->regmap = cs40l50->regmap;284	codec->dev = &pdev->dev;285 286	return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l50,287					       cs40l50_dai, ARRAY_SIZE(cs40l50_dai));288}289 290static const struct platform_device_id cs40l50_id[] = {291	{ "cs40l50-codec", },292	{}293};294MODULE_DEVICE_TABLE(platform, cs40l50_id);295 296static struct platform_driver cs40l50_codec_driver = {297	.probe = cs40l50_codec_driver_probe,298	.id_table = cs40l50_id,299	.driver = {300		.name = "cs40l50-codec",301	},302};303module_platform_driver(cs40l50_codec_driver);304 305MODULE_DESCRIPTION("ASoC CS40L50 driver");306MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>");307MODULE_LICENSE("GPL");308