cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

xlnx_i2s.c (7026B)


      1// SPDX-License-Identifier: GPL-2.0
      2//
      3// Xilinx ASoC I2S audio support
      4//
      5// Copyright (C) 2018 Xilinx, Inc.
      6//
      7// Author: Praveen Vuppala <praveenv@xilinx.com>
      8// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
      9
     10#include <linux/io.h>
     11#include <linux/module.h>
     12#include <linux/of.h>
     13#include <linux/of_platform.h>
     14#include <linux/platform_device.h>
     15#include <sound/pcm_params.h>
     16#include <sound/soc.h>
     17
     18#define DRV_NAME "xlnx_i2s"
     19
     20#define I2S_CORE_CTRL_OFFSET		0x08
     21#define I2S_CORE_CTRL_32BIT_LRCLK	BIT(3)
     22#define I2S_CORE_CTRL_ENABLE		BIT(0)
     23#define I2S_I2STIM_OFFSET		0x20
     24#define I2S_CH0_OFFSET			0x30
     25#define I2S_I2STIM_VALID_MASK		GENMASK(7, 0)
     26
     27struct xlnx_i2s_drv_data {
     28	struct snd_soc_dai_driver dai_drv;
     29	void __iomem *base;
     30	unsigned int sysclk;
     31	u32 data_width;
     32	u32 channels;
     33	bool is_32bit_lrclk;
     34	struct snd_ratnum ratnum;
     35	struct snd_pcm_hw_constraint_ratnums rate_constraints;
     36};
     37
     38static int xlnx_i2s_set_sclkout_div(struct snd_soc_dai *cpu_dai,
     39				    int div_id, int div)
     40{
     41	struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(cpu_dai);
     42
     43	if (!div || (div & ~I2S_I2STIM_VALID_MASK))
     44		return -EINVAL;
     45
     46	drv_data->sysclk = 0;
     47
     48	writel(div, drv_data->base + I2S_I2STIM_OFFSET);
     49
     50	return 0;
     51}
     52
     53static int xlnx_i2s_set_sysclk(struct snd_soc_dai *dai,
     54			       int clk_id, unsigned int freq, int dir)
     55{
     56	struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai);
     57
     58	drv_data->sysclk = freq;
     59	if (freq) {
     60		unsigned int bits_per_sample;
     61
     62		if (drv_data->is_32bit_lrclk)
     63			bits_per_sample = 32;
     64		else
     65			bits_per_sample = drv_data->data_width;
     66
     67		drv_data->ratnum.num = freq / (bits_per_sample * drv_data->channels) / 2;
     68		drv_data->ratnum.den_step = 1;
     69		drv_data->ratnum.den_min = 1;
     70		drv_data->ratnum.den_max = 255;
     71		drv_data->rate_constraints.rats = &drv_data->ratnum;
     72		drv_data->rate_constraints.nrats = 1;
     73	}
     74	return 0;
     75}
     76
     77static int xlnx_i2s_startup(struct snd_pcm_substream *substream,
     78			    struct snd_soc_dai *dai)
     79{
     80	struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai);
     81
     82	if (drv_data->sysclk)
     83		return snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
     84						     SNDRV_PCM_HW_PARAM_RATE,
     85						     &drv_data->rate_constraints);
     86
     87	return 0;
     88}
     89
     90static int xlnx_i2s_hw_params(struct snd_pcm_substream *substream,
     91			      struct snd_pcm_hw_params *params,
     92			      struct snd_soc_dai *i2s_dai)
     93{
     94	u32 reg_off, chan_id;
     95	struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai);
     96
     97	if (drv_data->sysclk) {
     98		unsigned int bits_per_sample, sclk, sclk_div;
     99
    100		if (drv_data->is_32bit_lrclk)
    101			bits_per_sample = 32;
    102		else
    103			bits_per_sample = drv_data->data_width;
    104
    105		sclk = params_rate(params) * bits_per_sample * params_channels(params);
    106		sclk_div = drv_data->sysclk / sclk / 2;
    107
    108		if ((drv_data->sysclk % sclk != 0) ||
    109		    !sclk_div || (sclk_div & ~I2S_I2STIM_VALID_MASK)) {
    110			dev_warn(i2s_dai->dev, "invalid SCLK divisor for sysclk %u and sclk %u\n",
    111				 drv_data->sysclk, sclk);
    112			return -EINVAL;
    113		}
    114		writel(sclk_div, drv_data->base + I2S_I2STIM_OFFSET);
    115	}
    116
    117	chan_id = params_channels(params) / 2;
    118
    119	while (chan_id > 0) {
    120		reg_off = I2S_CH0_OFFSET + ((chan_id - 1) * 4);
    121		writel(chan_id, drv_data->base + reg_off);
    122		chan_id--;
    123	}
    124
    125	return 0;
    126}
    127
    128static int xlnx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
    129			    struct snd_soc_dai *i2s_dai)
    130{
    131	struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai);
    132
    133	switch (cmd) {
    134	case SNDRV_PCM_TRIGGER_START:
    135	case SNDRV_PCM_TRIGGER_RESUME:
    136	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
    137		writel(I2S_CORE_CTRL_ENABLE, drv_data->base + I2S_CORE_CTRL_OFFSET);
    138		break;
    139	case SNDRV_PCM_TRIGGER_STOP:
    140	case SNDRV_PCM_TRIGGER_SUSPEND:
    141	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
    142		writel(0, drv_data->base + I2S_CORE_CTRL_OFFSET);
    143		break;
    144	default:
    145		return -EINVAL;
    146	}
    147
    148	return 0;
    149}
    150
    151static const struct snd_soc_dai_ops xlnx_i2s_dai_ops = {
    152	.trigger = xlnx_i2s_trigger,
    153	.set_sysclk = xlnx_i2s_set_sysclk,
    154	.set_clkdiv = xlnx_i2s_set_sclkout_div,
    155	.startup = xlnx_i2s_startup,
    156	.hw_params = xlnx_i2s_hw_params
    157};
    158
    159static const struct snd_soc_component_driver xlnx_i2s_component = {
    160	.name = DRV_NAME,
    161};
    162
    163static const struct of_device_id xlnx_i2s_of_match[] = {
    164	{ .compatible = "xlnx,i2s-transmitter-1.0", },
    165	{ .compatible = "xlnx,i2s-receiver-1.0", },
    166	{},
    167};
    168MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match);
    169
    170static int xlnx_i2s_probe(struct platform_device *pdev)
    171{
    172	struct xlnx_i2s_drv_data *drv_data;
    173	int ret;
    174	u32 format;
    175	struct device *dev = &pdev->dev;
    176	struct device_node *node = dev->of_node;
    177
    178	drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL);
    179	if (!drv_data)
    180		return -ENOMEM;
    181
    182	drv_data->base = devm_platform_ioremap_resource(pdev, 0);
    183	if (IS_ERR(drv_data->base))
    184		return PTR_ERR(drv_data->base);
    185
    186	ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels);
    187	if (ret < 0) {
    188		dev_err(dev, "cannot get supported channels\n");
    189		return ret;
    190	}
    191	drv_data->channels *= 2;
    192
    193	ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width);
    194	if (ret < 0) {
    195		dev_err(dev, "cannot get data width\n");
    196		return ret;
    197	}
    198	switch (drv_data->data_width) {
    199	case 16:
    200		format = SNDRV_PCM_FMTBIT_S16_LE;
    201		break;
    202	case 24:
    203		format = SNDRV_PCM_FMTBIT_S24_LE;
    204		break;
    205	default:
    206		return -EINVAL;
    207	}
    208
    209	if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) {
    210		drv_data->dai_drv.name = "xlnx_i2s_playback";
    211		drv_data->dai_drv.playback.stream_name = "Playback";
    212		drv_data->dai_drv.playback.formats = format;
    213		drv_data->dai_drv.playback.channels_min = drv_data->channels;
    214		drv_data->dai_drv.playback.channels_max = drv_data->channels;
    215		drv_data->dai_drv.playback.rates	= SNDRV_PCM_RATE_8000_192000;
    216		drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;
    217	} else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) {
    218		drv_data->dai_drv.name = "xlnx_i2s_capture";
    219		drv_data->dai_drv.capture.stream_name = "Capture";
    220		drv_data->dai_drv.capture.formats = format;
    221		drv_data->dai_drv.capture.channels_min = drv_data->channels;
    222		drv_data->dai_drv.capture.channels_max = drv_data->channels;
    223		drv_data->dai_drv.capture.rates = SNDRV_PCM_RATE_8000_192000;
    224		drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;
    225	} else {
    226		return -ENODEV;
    227	}
    228	drv_data->is_32bit_lrclk = readl(drv_data->base + I2S_CORE_CTRL_OFFSET) &
    229				   I2S_CORE_CTRL_32BIT_LRCLK;
    230
    231	dev_set_drvdata(&pdev->dev, drv_data);
    232
    233	ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component,
    234					      &drv_data->dai_drv, 1);
    235	if (ret) {
    236		dev_err(&pdev->dev, "i2s component registration failed\n");
    237		return ret;
    238	}
    239
    240	dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name);
    241
    242	return ret;
    243}
    244
    245static struct platform_driver xlnx_i2s_aud_driver = {
    246	.driver = {
    247		.name = DRV_NAME,
    248		.of_match_table = xlnx_i2s_of_match,
    249	},
    250	.probe = xlnx_i2s_probe,
    251};
    252
    253module_platform_driver(xlnx_i2s_aud_driver);
    254
    255MODULE_LICENSE("GPL v2");
    256MODULE_AUTHOR("Praveen Vuppala  <praveenv@xilinx.com>");
    257MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");