blob: a91c0ae137c5873fcf383d349db094021ac97e45 [file] [log] [blame]
/* sound/soc/samsung/vts/vts-plat.c
*
* ALSA SoC - Samsung VTS platfrom driver
*
* Copyright (c) 2016 Samsung Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
//#define DEBUG
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/pm_runtime.h>
#include <linux/firmware.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/regmap.h>
#include <asm-generic/delay.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/samsung/mailbox.h>
#include <sound/samsung/vts.h>
#include <soc/samsung/exynos-pmu.h>
#include "vts.h"
static const struct snd_pcm_hardware vts_platform_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED
| SNDRV_PCM_INFO_BLOCK_TRANSFER
| SNDRV_PCM_INFO_MMAP
| SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16,
.rates = SNDRV_PCM_RATE_16000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = BUFFER_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = BUFFER_BYTES_MAX / PERIOD_BYTES_MAX,
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
};
static int vts_platform_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_runtime *runtime = substream->runtime;
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab);
} else {
snd_pcm_set_runtime_buffer(substream, &data->vts_data->dmab_rec);
}
dev_info(dev, "%s:%s:DmaAddr=%pad Total=%zu PrdSz=%u(%u) #Prds=%u dma_area=%p\n",
__func__, snd_pcm_stream_str(substream), &runtime->dma_addr,
runtime->dma_bytes, params_period_size(params),
params_period_bytes(params), params_periods(params),
runtime->dma_area);
data->pointer = 0;
return 0;
}
static int vts_platform_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int vts_platform_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
dev_info(dev, "%s\n", __func__);
return 0;
}
static int vts_platform_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
u32 values[3];
int result = 0;
dev_info(dev, "%s ++ CMD: %d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
dev_dbg(dev, "%s VTS_IRQ_AP_START_COPY\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_COPY, &values, 1, 1);
} else {
dev_dbg(dev, "%s VTS_IRQ_AP_START_REC\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_START_REC, &values, 1, 1);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (data->type == PLATFORM_VTS_TRIGGER_RECORD) {
dev_dbg(dev, "%s VTS_IRQ_AP_STOP_COPY\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_COPY, &values, 1, 1);
} else {
dev_dbg(dev, "%s VTS_IRQ_AP_STOP_REC\n", __func__);
result = vts_start_ipc_transaction(dev, data->vts_data, VTS_IRQ_AP_STOP_REC, &values, 1, 1);
}
break;
default:
result = -EINVAL;
break;
}
dev_info(dev, "%s -- CMD: %d\n", __func__, cmd);
return result;
}
static snd_pcm_uframes_t vts_platform_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_runtime *runtime = substream->runtime;
dev_dbg(dev, "%s: pointer=%08x\n", __func__, data->pointer);
return bytes_to_frames(runtime, data->pointer);
}
static int vts_platform_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
dev_info(dev, "%s\n", __func__);
snd_soc_set_runtime_hwparams(substream, &vts_platform_hardware);
if (data->type == PLATFORM_VTS_NORMAL_RECORD) {
vts_set_dmicctrl(data->vts_data->pdev,true);
}
return 0;
}
static int vts_platform_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
dev_info(dev, "%s\n", __func__);
if (data->type == PLATFORM_VTS_NORMAL_RECORD) {
vts_set_dmicctrl(data->vts_data->pdev,false);
}
return 0;
}
static int vts_platform_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct device *dev = platform->dev;
struct snd_pcm_runtime *runtime = substream->runtime;
dev_info(dev, "%s\n", __func__);
return dma_mmap_writecombine(dev, vma,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
}
static struct snd_pcm_ops vts_platform_ops = {
.open = vts_platform_open,
.close = vts_platform_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = vts_platform_hw_params,
.hw_free = vts_platform_hw_free,
.prepare = vts_platform_prepare,
.trigger = vts_platform_trigger,
.pointer = vts_platform_pointer,
.mmap = vts_platform_mmap,
};
static int vts_platform_new(struct snd_soc_pcm_runtime *runtime)
{
struct snd_soc_platform *platform = runtime->platform;
struct device *dev = platform->dev;
struct vts_platform_data *data = dev_get_drvdata(dev);
struct snd_pcm_substream *substream = runtime->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
dev_info(dev, "%s \n", __func__);
data->substream = substream;
dev_info(dev, "%s Update Soc Card from runtime!!\n", __func__);
data->vts_data->card = runtime->card;
return 0;
}
static void vts_platform_free(struct snd_pcm *pcm)
{
return;
}
static const struct snd_soc_platform_driver vts_dma = {
.ops = &vts_platform_ops,
.pcm_new = vts_platform_new,
.pcm_free = vts_platform_free,
};
static int samsung_vts_dma_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *np_vts;
struct vts_platform_data *data;
int result;
const char *type;
dev_info(dev, "%s \n", __func__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(dev, "Failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, data);
np_vts = of_parse_phandle(np, "vts", 0);
if (!np_vts) {
dev_err(dev, "Failed to get vts device node\n");
return -EPROBE_DEFER;
}
data->pdev_vts = of_find_device_by_node(np_vts);
if (!data->pdev_vts) {
dev_err(dev, "Failed to get vts platform device\n");
return -EPROBE_DEFER;
}
data->vts_data = platform_get_drvdata(data->pdev_vts);
result = of_property_read_u32_index(np, "id", 0, &data->id);
if (IS_ERR_VALUE(result)) {
dev_err(dev, "id property reading fail\n");
return result;
}
result = of_property_read_string(np, "type", &type);
if (IS_ERR_VALUE(result)) {
dev_err(dev, "type property reading fail\n");
return result;
}
if (!strncmp(type, "vts-record", sizeof("vts-record"))) {
data->type = PLATFORM_VTS_NORMAL_RECORD;
dev_info(dev, "%s - vts-record Probed \n", __func__);
} else {
data->type = PLATFORM_VTS_TRIGGER_RECORD;
dev_info(dev, "%s - vts-trigger-record Probed \n", __func__);
}
vts_register_dma(data->vts_data->pdev, pdev, data->id);
return snd_soc_register_platform(&pdev->dev, &vts_dma);
}
static int samsung_vts_dma_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static const struct of_device_id samsung_vts_dma_match[] = {
{
.compatible = "samsung,vts-dma",
},
{},
};
MODULE_DEVICE_TABLE(of, samsung_vts_dma_match);
static struct platform_driver samsung_vts_dma_driver = {
.probe = samsung_vts_dma_probe,
.remove = samsung_vts_dma_remove,
.driver = {
.name = "samsung-vts-dma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_vts_dma_match),
},
};
module_platform_driver(samsung_vts_dma_driver);
/* Module information */
MODULE_AUTHOR("Palli Satish Kumar Reddy, <palli.satish@samsung.com>");
MODULE_DESCRIPTION("Samsung VTS DMA");
MODULE_ALIAS("platform:samsung-vts-dma");
MODULE_LICENSE("GPL");