| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) 2018 Synaptics Incorporated */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/irq.h> |
| #include <linux/of_device.h> |
| |
| #include "api_avio_dhub.h" |
| #include "api_dhub.h" |
| #include "berlin_pcm.h" |
| #include "berlin_capture.h" |
| #include "berlin_playback.h" |
| |
| static struct berlin_chip *chip = NULL; |
| static struct berlin_chip *dev_to_berlin_chip(struct device *dev) |
| { |
| struct snd_card *card = NULL; |
| |
| card = dev_get_drvdata(dev); |
| if (card == NULL) |
| return NULL; |
| return (struct berlin_chip *)card->private_data; |
| } |
| |
| void berlin_report_xrun(struct berlin_chip *chip, |
| enum berlin_xrun_t xrun_type) |
| { |
| atomic_long_inc(chip->xruns + xrun_type); |
| } |
| |
| static enum berlin_xrun_t berlin_xrun_string_to_type(const char *string) |
| { |
| if (strcmp("pcm_overrun", string) == 0) |
| return PCM_OVERRUN; |
| if (strcmp("fifo_overrun", string) == 0) |
| return FIFO_OVERRUN; |
| if (strcmp("pcm_underrun", string) == 0) |
| return PCM_UNDERRUN; |
| if (strcmp("fifo_underrun", string) == 0) |
| return FIFO_UNDERRUN; |
| if (strcmp("irq_disable_us", string) == 0) |
| return IRQ_DISABLE; |
| snd_printk("%s: unrecognized xrun type: %s\n", __func__, string); |
| return XRUN_T_MAX; |
| } |
| |
| static ssize_t |
| xrun_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| unsigned long overruns; |
| |
| enum berlin_xrun_t xrun_type = |
| berlin_xrun_string_to_type(attr->attr.name); |
| |
| if (xrun_type == XRUN_T_MAX) |
| return -EINVAL; |
| |
| if (chip == NULL) |
| return -ENODEV; |
| overruns = atomic_long_read(chip->xruns + xrun_type); |
| return snprintf(buf, PAGE_SIZE, "%lu\n", overruns); |
| } |
| |
| static ssize_t |
| xrun_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| unsigned long overruns; |
| int err; |
| |
| enum berlin_xrun_t xrun_type = |
| berlin_xrun_string_to_type(attr->attr.name); |
| |
| printk("%s %d: %d\n", __func__, __LINE__, xrun_type); |
| if (xrun_type == XRUN_T_MAX) |
| return -EINVAL; |
| |
| if (chip == NULL) |
| return -ENODEV; |
| |
| err = kstrtoul(buf, 10, &overruns); |
| if (err < 0) |
| return err; |
| |
| // TODO(yichunko): Remove the following after underrun issue is |
| // resolved |
| // in IRQ_DISABLE mode, overruns = # of us to block |
| if (xrun_type == IRQ_DISABLE) { |
| unsigned long flags; |
| |
| local_irq_save(flags); |
| snd_printk("block for %lu us.\n", overruns); |
| udelay(overruns); |
| local_irq_restore(flags); |
| } |
| // TODO(yichunko) |
| atomic_long_set(chip->xruns + xrun_type, overruns); |
| return count; |
| } |
| |
| static DEVICE_ATTR(pcm_overrun, 0644, xrun_show, xrun_store); |
| static DEVICE_ATTR(fifo_overrun, 0644, xrun_show, xrun_store); |
| static DEVICE_ATTR(pcm_underrun, 0644, xrun_show, xrun_store); |
| static DEVICE_ATTR(fifo_underrun, 0644, xrun_show, xrun_store); |
| static DEVICE_ATTR(irq_disable_us, 0644, xrun_show, xrun_store); |
| |
| static struct attribute *berlin_xrun_sysfs_entries[] = { |
| &dev_attr_pcm_overrun.attr, |
| &dev_attr_fifo_overrun.attr, |
| &dev_attr_pcm_underrun.attr, |
| &dev_attr_fifo_underrun.attr, |
| &dev_attr_irq_disable_us.attr, |
| NULL, |
| }; |
| |
| const struct attribute_group berlin_sysfs_group = { |
| .name = "xrun", |
| .attrs = berlin_xrun_sysfs_entries, |
| }; |
| EXPORT_SYMBOL(berlin_sysfs_group); |
| |
| static irqreturn_t berlin_alsa_io_isr(int irq, void *dev_id) |
| { |
| struct snd_pcm_substream *substream; |
| irq_hw_number_t hw_irq; |
| int chan_id; |
| |
| substream = dev_id; |
| hw_irq = irqd_to_hwirq(irq_get_irq_data(irq)); |
| chan_id = (int)hw_irq; |
| |
| if (substream->runtime) { |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| berlin_capture_isr(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| berlin_playback_isr(substream, chan_id); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| int berlin_pcm_request_dma_irq(struct snd_pcm_substream *substream, |
| u32 chid_num, |
| u32 irq_num, |
| unsigned int *irq, |
| const char *dev_name, |
| u32 mode) |
| { |
| u32 ch[MAX_CHID], i; |
| int err; |
| void *dev_id = substream; |
| |
| if (irq_num == 0 || chid_num == 0 || irq_num > chid_num) { |
| snd_printk("%s: invalid num irq %d chid %d\n", |
| __func__, irq_num, chid_num); |
| return 0; |
| } |
| |
| for (i = 0; i < irq_num; i++) { |
| err = request_irq(irq[i], berlin_alsa_io_isr, 0, |
| dev_name, dev_id); |
| if (unlikely(err < 0)) { |
| snd_printk("irq %d request error: %d\n", |
| irq[i], err); |
| return err; |
| } |
| } |
| |
| for (i = 0; i < chid_num; i++) |
| ch[i] = irqd_to_hwirq(irq_get_irq_data(irq[i])); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| berlin_capture_set_ch_mode(substream, chid_num, ch, mode); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| berlin_playback_set_ch_mode(substream, chid_num, ch, mode); |
| |
| return err; |
| } |
| EXPORT_SYMBOL(berlin_pcm_request_dma_irq); |
| |
| void berlin_pcm_free_dma_irq(struct snd_pcm_substream *substream, |
| u32 chid_num, |
| u32 irq_num, |
| unsigned int *irq) |
| { |
| u32 i = 0; |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| berlin_capture_set_ch_mode(substream, 0, 0, 0); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| berlin_playback_set_ch_mode(substream, 0, 0, 0); |
| |
| for (i = 0; i < irq_num; i++) |
| free_irq(irq[i], (void *)substream); |
| } |
| EXPORT_SYMBOL(berlin_pcm_free_dma_irq); |
| |
| void berlin_pcm_max_ch_inuse(struct snd_pcm_substream *substream, |
| u32 ch_num) |
| { |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| berlin_capture_set_ch_inuse(substream, ch_num); |
| } |
| EXPORT_SYMBOL(berlin_pcm_max_ch_inuse); |
| |
| static int berlin_pcm_open(struct snd_pcm_substream *substream) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_open(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_open(substream); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_close(struct snd_pcm_substream *substream) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_close(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_close(substream); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_hw_params(substream, params); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_hw_params(substream, params); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_hw_free(struct snd_pcm_substream *substream) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_hw_free(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_hw_free(substream); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_prepare(struct snd_pcm_substream *substream) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_prepare(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_prepare(substream); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| snd_printd(" %s stream %p\n", __func__, substream); |
| |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_trigger(substream, cmd); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_trigger(substream, cmd); |
| |
| return 0; |
| } |
| |
| static snd_pcm_uframes_t |
| berlin_pcm_pointer(struct snd_pcm_substream *substream) |
| { |
| if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| return berlin_capture_pointer(substream); |
| else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_pointer(substream); |
| |
| return 0; |
| } |
| |
| static int berlin_pcm_ack(struct snd_pcm_substream *substream) |
| { |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| return berlin_playback_ack(substream); |
| |
| return 0; |
| } |
| |
| static const struct snd_pcm_ops berlin_pcm_ops = { |
| .open = berlin_pcm_open, |
| .close = berlin_pcm_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = berlin_pcm_hw_params, |
| .hw_free = berlin_pcm_hw_free, |
| .prepare = berlin_pcm_prepare, |
| .trigger = berlin_pcm_trigger, |
| .pointer = berlin_pcm_pointer, |
| .ack = berlin_pcm_ack, |
| }; |
| |
| #define PREALLOC_BUFFER (0x10000) |
| #define PREALLOC_BUFFER_MAX (0x20000) |
| static int berlin_pcm_new(struct snd_soc_pcm_runtime *rtd) |
| { |
| int ret; |
| |
| ret = snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, |
| SNDRV_DMA_TYPE_CONTINUOUS, |
| snd_dma_continuous_data |
| (GFP_KERNEL), |
| PREALLOC_BUFFER, |
| PREALLOC_BUFFER_MAX); |
| |
| return ret; |
| } |
| |
| static void berlin_pcm_free(struct snd_pcm *pcm) |
| { |
| snd_pcm_lib_preallocate_free_for_all(pcm); |
| } |
| |
| static struct snd_soc_platform_driver berlin_pcm_platform = { |
| .ops = &berlin_pcm_ops, |
| .pcm_new = berlin_pcm_new, |
| .pcm_free = berlin_pcm_free, |
| }; |
| |
| static int berlin_pcm_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| int ret; |
| |
| snd_printk("berlin-pcm probe %p\n", pdev); |
| |
| chip = devm_kzalloc(dev, sizeof(struct berlin_chip), GFP_KERNEL); |
| if (chip == NULL) |
| return -ENOMEM; |
| |
| chip->pdev = pdev; |
| of_dma_configure(dev, dev->of_node); |
| |
| /* Dhub configuration */ |
| DhubInitialization(0, AG_DHUB_BASE, AG_HBO_SRAM_BASE, |
| &AG_dhubHandle, AG_config, AG_NUM_OF_CHANNELS, DHUB_TYPE_64BIT); |
| snd_printd("berlin dhub inited chip 0x%p\n", chip); |
| dev_set_drvdata(&pdev->dev, chip); |
| |
| ret = devm_snd_soc_register_platform(&pdev->dev, |
| &berlin_pcm_platform); |
| if (ret < 0) { |
| snd_printk("can not do snd soc register\n"); |
| } |
| |
| return ret; |
| } |
| |
| static int berlin_pcm_dev_remove(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| |
| static const struct of_device_id berlin_pcm_of_match[] = { |
| {.compatible = "syna,berlin-pcm"}, |
| {.compatible = "syna,as370-pcm"}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, berlin_pcm_of_match); |
| |
| static struct platform_driver berlin_pcm_driver = { |
| .driver = { |
| .name = "syna-berlin-pcm", |
| .of_match_table = berlin_pcm_of_match, |
| }, |
| |
| .probe = berlin_pcm_probe, |
| .remove = berlin_pcm_dev_remove, |
| }; |
| module_platform_driver(berlin_pcm_driver); |
| |
| MODULE_AUTHOR("Synaptics"); |
| MODULE_DESCRIPTION("Berlin PCM Driver"); |
| MODULE_LICENSE("GPL v2"); |