blob: 2c975576467bd487d3d169c99c30135b5f192e91 [file] [log] [blame]
// 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");