| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) STMicroelectronics SA 2015 |
| * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com> |
| * for STMicroelectronics. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/delay.h> |
| |
| #include "uniperif.h" |
| |
| /* |
| * User frame size shall be 2, 4, 6 or 8 32-bits words length |
| * (i.e. 8, 16, 24 or 32 bytes) |
| * This constraint comes from allowed values for |
| * UNIPERIF_I2S_FMT_NUM_CH register |
| */ |
| #define UNIPERIF_MAX_FRAME_SZ 0x20 |
| #define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ) |
| |
| struct sti_uniperiph_dev_data { |
| unsigned int id; /* Nb available player instances */ |
| unsigned int version; /* player IP version */ |
| unsigned int stream; |
| const char *dai_names; |
| enum uniperif_type type; |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_uniplayer_hdmi = { |
| .id = 0, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
| .stream = SNDRV_PCM_STREAM_PLAYBACK, |
| .dai_names = "Uni Player #0 (HDMI)", |
| .type = SND_ST_UNIPERIF_TYPE_HDMI |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_uniplayer_pcm_out = { |
| .id = 1, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
| .stream = SNDRV_PCM_STREAM_PLAYBACK, |
| .dai_names = "Uni Player #1 (PCM OUT)", |
| .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_uniplayer_dac = { |
| .id = 2, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
| .stream = SNDRV_PCM_STREAM_PLAYBACK, |
| .dai_names = "Uni Player #2 (DAC)", |
| .type = SND_ST_UNIPERIF_TYPE_PCM, |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_uniplayer_spdif = { |
| .id = 3, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, |
| .stream = SNDRV_PCM_STREAM_PLAYBACK, |
| .dai_names = "Uni Player #3 (SPDIF)", |
| .type = SND_ST_UNIPERIF_TYPE_SPDIF |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_unireader_pcm_in = { |
| .id = 0, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, |
| .stream = SNDRV_PCM_STREAM_CAPTURE, |
| .dai_names = "Uni Reader #0 (PCM IN)", |
| .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, |
| }; |
| |
| static const struct sti_uniperiph_dev_data sti_unireader_hdmi_in = { |
| .id = 1, |
| .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, |
| .stream = SNDRV_PCM_STREAM_CAPTURE, |
| .dai_names = "Uni Reader #1 (HDMI IN)", |
| .type = SND_ST_UNIPERIF_TYPE_PCM, |
| }; |
| |
| static const struct of_device_id snd_soc_sti_match[] = { |
| { .compatible = "st,stih407-uni-player-hdmi", |
| .data = &sti_uniplayer_hdmi |
| }, |
| { .compatible = "st,stih407-uni-player-pcm-out", |
| .data = &sti_uniplayer_pcm_out |
| }, |
| { .compatible = "st,stih407-uni-player-dac", |
| .data = &sti_uniplayer_dac |
| }, |
| { .compatible = "st,stih407-uni-player-spdif", |
| .data = &sti_uniplayer_spdif |
| }, |
| { .compatible = "st,stih407-uni-reader-pcm_in", |
| .data = &sti_unireader_pcm_in |
| }, |
| { .compatible = "st,stih407-uni-reader-hdmi", |
| .data = &sti_unireader_hdmi_in |
| }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, snd_soc_sti_match); |
| |
| int sti_uniperiph_reset(struct uniperif *uni) |
| { |
| int count = 10; |
| |
| /* Reset uniperipheral uni */ |
| SET_UNIPERIF_SOFT_RST_SOFT_RST(uni); |
| |
| if (uni->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { |
| while (GET_UNIPERIF_SOFT_RST_SOFT_RST(uni) && count) { |
| udelay(5); |
| count--; |
| } |
| } |
| |
| if (!count) { |
| dev_err(uni->dev, "Failed to reset uniperif\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, |
| unsigned int rx_mask, int slots, |
| int slot_width) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
| struct uniperif *uni = priv->dai_data.uni; |
| int i, frame_size, avail_slots; |
| |
| if (!UNIPERIF_TYPE_IS_TDM(uni)) { |
| dev_err(uni->dev, "cpu dai not in tdm mode\n"); |
| return -EINVAL; |
| } |
| |
| /* store info in unip context */ |
| uni->tdm_slot.slots = slots; |
| uni->tdm_slot.slot_width = slot_width; |
| /* unip is unidirectionnal */ |
| uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; |
| |
| /* number of available timeslots */ |
| for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { |
| if ((uni->tdm_slot.mask >> i) & 0x01) |
| avail_slots++; |
| } |
| uni->tdm_slot.avail_slots = avail_slots; |
| |
| /* frame size in bytes */ |
| frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; |
| |
| /* check frame size is allowed */ |
| if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || |
| (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { |
| dev_err(uni->dev, "frame size not allowed: %d bytes\n", |
| frame_size); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, |
| struct snd_pcm_hw_rule *rule) |
| { |
| struct uniperif *uni = rule->private; |
| struct snd_interval t; |
| |
| t.min = uni->tdm_slot.avail_slots; |
| t.max = uni->tdm_slot.avail_slots; |
| t.openmin = 0; |
| t.openmax = 0; |
| t.integer = 0; |
| |
| return snd_interval_refine(hw_param_interval(params, rule->var), &t); |
| } |
| |
| int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, |
| struct snd_pcm_hw_rule *rule) |
| { |
| struct uniperif *uni = rule->private; |
| struct snd_mask *maskp = hw_param_mask(params, rule->var); |
| u64 format; |
| |
| switch (uni->tdm_slot.slot_width) { |
| case 16: |
| format = SNDRV_PCM_FMTBIT_S16_LE; |
| break; |
| case 32: |
| format = SNDRV_PCM_FMTBIT_S32_LE; |
| break; |
| default: |
| dev_err(uni->dev, "format not supported: %d bits\n", |
| uni->tdm_slot.slot_width); |
| return -EINVAL; |
| } |
| |
| maskp->bits[0] &= (u_int32_t)format; |
| maskp->bits[1] &= (u_int32_t)(format >> 32); |
| /* clear remaining indexes */ |
| memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); |
| |
| if (!maskp->bits[0] && !maskp->bits[1]) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, |
| unsigned int *word_pos) |
| { |
| int slot_width = uni->tdm_slot.slot_width / 8; |
| int slots_num = uni->tdm_slot.slots; |
| unsigned int slots_mask = uni->tdm_slot.mask; |
| int i, j, k; |
| unsigned int word16_pos[4]; |
| |
| /* word16_pos: |
| * word16_pos[0] = WORDX_LSB |
| * word16_pos[1] = WORDX_MSB, |
| * word16_pos[2] = WORDX+1_LSB |
| * word16_pos[3] = WORDX+1_MSB |
| */ |
| |
| /* set unip word position */ |
| for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { |
| if ((slots_mask >> i) & 0x01) { |
| word16_pos[j] = i * slot_width; |
| |
| if (slot_width == 4) { |
| word16_pos[j + 1] = word16_pos[j] + 2; |
| j++; |
| } |
| j++; |
| |
| if (j > 3) { |
| word_pos[k] = word16_pos[1] | |
| (word16_pos[0] << 8) | |
| (word16_pos[3] << 16) | |
| (word16_pos[2] << 24); |
| j = 0; |
| k++; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * sti_uniperiph_dai_create_ctrl |
| * This function is used to create Ctrl associated to DAI but also pcm device. |
| * Request is done by front end to associate ctrl with pcm device id |
| */ |
| static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
| struct uniperif *uni = priv->dai_data.uni; |
| struct snd_kcontrol_new *ctrl; |
| int i; |
| |
| if (!uni->num_ctrls) |
| return 0; |
| |
| for (i = 0; i < uni->num_ctrls; i++) { |
| /* |
| * Several Control can have same name. Controls are indexed on |
| * Uniperipheral instance ID |
| */ |
| ctrl = &uni->snd_ctrls[i]; |
| ctrl->index = uni->id; |
| ctrl->device = uni->id; |
| } |
| |
| return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls); |
| } |
| |
| /* |
| * DAI |
| */ |
| int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
| struct uniperif *uni = priv->dai_data.uni; |
| struct snd_dmaengine_dai_dma_data *dma_data; |
| int transfer_size; |
| |
| if (uni->type == SND_ST_UNIPERIF_TYPE_TDM) |
| /* transfer size = user frame size (in 32-bits FIFO cell) */ |
| transfer_size = snd_soc_params_to_frame_size(params) / 32; |
| else |
| transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; |
| |
| dma_data = snd_soc_dai_get_dma_data(dai, substream); |
| dma_data->maxburst = transfer_size; |
| |
| return 0; |
| } |
| |
| int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
| |
| priv->dai_data.uni->daifmt = fmt; |
| |
| return 0; |
| } |
| |
| static int sti_uniperiph_suspend(struct snd_soc_component *component) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_component_get_drvdata(component); |
| struct uniperif *uni = priv->dai_data.uni; |
| int ret; |
| |
| /* The uniperipheral should be in stopped state */ |
| if (uni->state != UNIPERIF_STATE_STOPPED) { |
| dev_err(uni->dev, "%s: invalid uni state( %d)\n", |
| __func__, (int)uni->state); |
| return -EBUSY; |
| } |
| |
| /* Pinctrl: switch pinstate to sleep */ |
| ret = pinctrl_pm_select_sleep_state(uni->dev); |
| if (ret) |
| dev_err(uni->dev, "%s: failed to select pinctrl state\n", |
| __func__); |
| |
| return ret; |
| } |
| |
| static int sti_uniperiph_resume(struct snd_soc_component *component) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_component_get_drvdata(component); |
| struct uniperif *uni = priv->dai_data.uni; |
| int ret; |
| |
| if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| ret = uni_player_resume(uni); |
| if (ret) |
| return ret; |
| } |
| |
| /* pinctrl: switch pinstate to default */ |
| ret = pinctrl_pm_select_default_state(uni->dev); |
| if (ret) |
| dev_err(uni->dev, "%s: failed to select pinctrl state\n", |
| __func__); |
| |
| return ret; |
| } |
| |
| static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai) |
| { |
| struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); |
| struct sti_uniperiph_dai *dai_data = &priv->dai_data; |
| |
| /* DMA settings*/ |
| if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL); |
| else |
| snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data); |
| |
| dai_data->dma_data.addr = dai_data->uni->fifo_phys_address; |
| dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
| |
| return sti_uniperiph_dai_create_ctrl(dai); |
| } |
| |
| static const struct snd_soc_dai_driver sti_uniperiph_dai_template = { |
| .probe = sti_uniperiph_dai_probe, |
| }; |
| |
| static const struct snd_soc_component_driver sti_uniperiph_dai_component = { |
| .name = "sti_cpu_dai", |
| .suspend = sti_uniperiph_suspend, |
| .resume = sti_uniperiph_resume, |
| .legacy_dai_naming = 1, |
| }; |
| |
| static int sti_uniperiph_cpu_dai_of(struct device_node *node, |
| struct sti_uniperiph_data *priv) |
| { |
| struct device *dev = &priv->pdev->dev; |
| struct sti_uniperiph_dai *dai_data = &priv->dai_data; |
| struct snd_soc_dai_driver *dai = priv->dai; |
| struct snd_soc_pcm_stream *stream; |
| struct uniperif *uni; |
| const struct of_device_id *of_id; |
| const struct sti_uniperiph_dev_data *dev_data; |
| const char *mode; |
| int ret; |
| |
| /* Populate data structure depending on compatibility */ |
| of_id = of_match_node(snd_soc_sti_match, node); |
| if (!of_id->data) { |
| dev_err(dev, "data associated to device is missing\n"); |
| return -EINVAL; |
| } |
| dev_data = (struct sti_uniperiph_dev_data *)of_id->data; |
| |
| uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL); |
| if (!uni) |
| return -ENOMEM; |
| |
| uni->id = dev_data->id; |
| uni->ver = dev_data->version; |
| |
| *dai = sti_uniperiph_dai_template; |
| dai->name = dev_data->dai_names; |
| |
| /* Get resources and base address */ |
| uni->base = devm_platform_get_and_ioremap_resource(priv->pdev, 0, &uni->mem_region); |
| if (IS_ERR(uni->base)) |
| return PTR_ERR(uni->base); |
| |
| uni->fifo_phys_address = uni->mem_region->start + |
| UNIPERIF_FIFO_DATA_OFFSET(uni); |
| |
| uni->irq = platform_get_irq(priv->pdev, 0); |
| if (uni->irq < 0) |
| return -ENXIO; |
| |
| uni->type = dev_data->type; |
| |
| /* check if player should be configured for tdm */ |
| if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) { |
| if (!of_property_read_string(node, "st,tdm-mode", &mode)) |
| uni->type = SND_ST_UNIPERIF_TYPE_TDM; |
| else |
| uni->type = SND_ST_UNIPERIF_TYPE_PCM; |
| } |
| |
| dai_data->uni = uni; |
| dai_data->stream = dev_data->stream; |
| |
| if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| ret = uni_player_init(priv->pdev, uni); |
| stream = &dai->playback; |
| } else { |
| ret = uni_reader_init(priv->pdev, uni); |
| stream = &dai->capture; |
| } |
| if (ret < 0) |
| return ret; |
| |
| dai->ops = uni->dai_ops; |
| |
| stream->stream_name = dai->name; |
| stream->channels_min = uni->hw->channels_min; |
| stream->channels_max = uni->hw->channels_max; |
| stream->rates = uni->hw->rates; |
| stream->formats = uni->hw->formats; |
| |
| return 0; |
| } |
| |
| static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { |
| .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, |
| }; |
| |
| static int sti_uniperiph_probe(struct platform_device *pdev) |
| { |
| struct sti_uniperiph_data *priv; |
| struct device_node *node = pdev->dev.of_node; |
| int ret; |
| |
| /* Allocate the private data and the CPU_DAI array */ |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL); |
| if (!priv->dai) |
| return -ENOMEM; |
| |
| priv->pdev = pdev; |
| |
| ret = sti_uniperiph_cpu_dai_of(node, priv); |
| if (ret < 0) |
| return ret; |
| |
| dev_set_drvdata(&pdev->dev, priv); |
| |
| ret = devm_snd_soc_register_component(&pdev->dev, |
| &sti_uniperiph_dai_component, |
| priv->dai, 1); |
| if (ret < 0) |
| return ret; |
| |
| return devm_snd_dmaengine_pcm_register(&pdev->dev, |
| &dmaengine_pcm_config, 0); |
| } |
| |
| static struct platform_driver sti_uniperiph_driver = { |
| .driver = { |
| .name = "sti-uniperiph-dai", |
| .of_match_table = snd_soc_sti_match, |
| }, |
| .probe = sti_uniperiph_probe, |
| }; |
| module_platform_driver(sti_uniperiph_driver); |
| |
| MODULE_DESCRIPTION("uniperipheral DAI driver"); |
| MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>"); |
| MODULE_LICENSE("GPL v2"); |