blob: 4714eecdb3d91944f56cbbc2c3510e55f8155278 [file] [log] [blame]
/*
* rt5514-spi.c -- RT5514 SPI driver
*
* Copyright 2015 Realtek Semiconductor Corp.
* Author: Oder Chiou <oder_chiou@realtek.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_qos.h>
#include <linux/sysfs.h>
#include <linux/clk.h>
#include <linux/firmware.h>
#include <linux/regmap.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include "rt5514.h"
#include "rt5514-spi.h"
static int rt5514_spi_burst_read(struct rt5514_dsp *dsp, unsigned int addr,
u8 *rxbuf, size_t len);
static int rt5514_spi_burst_write(struct rt5514_dsp *dsp, u32 addr,
const u8 *txbuf, size_t len);
static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = 0x20000 / 8,
.periods_min = 8,
.periods_max = 8,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = 0x20000,
};
static struct snd_soc_dai_driver rt5514_spi_dai = {
.name = "rt5514-dsp-cpu-dai",
.id = 0,
.capture = {
.stream_name = "DSP Capture",
.channels_min = 1,
.channels_max = 1,
.rates = SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
};
static void rt5514_spi_start(struct rt5514_dsp *dsp)
{
int ret;
const struct firmware *fw;
struct regmap *regmap = dsp->priv->i2c_regmap;
/* Reset */
regmap_write(regmap, 0x18002000, 0x000010ec);
/* LDO_I_limit */
regmap_write(regmap, 0x18002200, 0x00028604);
/* I2C bypass enable */
regmap_write(regmap, 0xfafafafa, 0x00000001);
/* mini-core reset */
regmap_write(regmap, 0x18002f00, 0x0005514b);
regmap_write(regmap, 0x18002f00, 0x00055149);
/* I2C bypass disable */
regmap_write(regmap, 0xfafafafa, 0x00000000);
/* PIN config */
regmap_write(regmap, 0x18002070, 0x00000040);
/* PLL3(QN)=RCOSC*(10+2) */
regmap_write(regmap, 0x18002240, 0x0000000a);
/* PLL3 source=RCOSC, fsi=rt_clk */
regmap_write(regmap, 0x18002100, 0x0000000b);
/* Power on RCOSC, pll3 */
regmap_write(regmap, 0x18002004, 0x00808b81);
/* DSP clk source = pll3, ENABLE DSP clk */
regmap_write(regmap, 0x18002f08, 0x00000005);
/* Enable DSP clk auto switch */
regmap_write(regmap, 0x18001114, 0x00000001);
/* Reduce DSP power */
regmap_write(regmap, 0x18001118, 0x00000001);
ret = request_firmware(&fw, RT5514_FIRMWARE1, dsp->dev);
if (ret) {
dev_err(dsp->dev, "request_firmware failed %d: "
RT5514_FIRMWARE1, ret);
return;
}
rt5514_spi_burst_write(dsp, 0x4ff60000, fw->data,
round_up(fw->size, 8));
release_firmware(fw);
ret = request_firmware(&fw, RT5514_FIRMWARE2, dsp->dev);
if (ret) {
dev_err(dsp->dev, "request_firmware failed %d: "
RT5514_FIRMWARE2, ret);
return;
}
rt5514_spi_burst_write(dsp, 0x4ffc0000, fw->data,
round_up(fw->size, 8));
release_firmware(fw);
/* DSP run */
regmap_write(regmap, 0x18002f00, 0x00055148);
dsp->priv->state = RT5514_DSP_ARMED;
}
static int rt5514_spi_prepare_to_stream(struct rt5514_dsp *dsp)
{
u8 buf[8];
/**
* The address area x1800XXXX is the register address, and it cannot
* support spi burst read perfectly. So we use the spi burst read
* individually to make sure the data correctly.
*/
rt5514_spi_burst_read(dsp, RT5514_BUFFER_VOICE_BASE, (u8 *)&buf,
sizeof(buf));
dsp->buf_base = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
rt5514_spi_burst_read(dsp, RT5514_BUFFER_VOICE_LIMIT, (u8 *)&buf,
sizeof(buf));
dsp->buf_limit = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
rt5514_spi_burst_read(dsp, RT5514_BUFFER_VOICE_RP, (u8 *)&buf,
sizeof(buf));
dsp->buf_rp = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
rt5514_spi_burst_read(dsp, RT5514_BUFFER_VOICE_SIZE, (u8 *)&buf,
sizeof(buf));
dsp->buf_size = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
if (dsp->buf_base && dsp->buf_limit && dsp->buf_rp && dsp->buf_size) {
dsp->dsp_offset = 0;
dsp->priv->state = RT5514_DSP_STREAM;
dev_info(dsp->dev, "buf_base=0x%x, buf_limit=0x%x, buf_rp=0x%x"
", buf_size=%zu. dma_bytes=%zu, "
"buffer_size=%luF, periods=%u, period_size=%luF",
dsp->buf_base, dsp->buf_limit, dsp->buf_rp,
dsp->buf_size,
dsp->substream->runtime->dma_bytes,
dsp->substream->runtime->buffer_size,
dsp->substream->runtime->periods,
dsp->substream->runtime->period_size);
if (dsp->buf_base != dsp->buf_rp)
dev_err(dsp->dev, "buf_base != buf_rp");
if (dsp->buf_size != dsp->buf_limit - dsp->buf_base)
dev_err(dsp->dev, "buf_size != buf_limit - buf_base");
return 0;
}
queue_delayed_work(system_freezable_wq, &dsp->work, 5);
return -EAGAIN;
}
static void rt5514_spi_stream_zero(struct rt5514_dsp *dsp, u32 size)
{
struct snd_pcm_runtime *runtime = dsp->substream->runtime;
size_t target = dsp->dma_offset + size;
memset(runtime->dma_area + dsp->dma_offset, 64,
min(target, runtime->dma_bytes) - dsp->dma_offset);
if (target > runtime->dma_bytes)
memset(runtime->dma_area, 64, target - runtime->dma_bytes);
if (target >= runtime->dma_bytes)
target -= runtime->dma_bytes;
dsp->dma_offset = target;
}
static void rt5514_spi_stream_data(struct rt5514_dsp *dsp, u32 addr, u32 size)
{
struct snd_pcm_runtime *runtime = dsp->substream->runtime;
size_t target = dsp->dma_offset + size;
size_t block_size = min(target, runtime->dma_bytes) - dsp->dma_offset;
rt5514_spi_burst_read(dsp, addr, runtime->dma_area + dsp->dma_offset,
block_size);
if (target > runtime->dma_bytes)
rt5514_spi_burst_read(dsp, addr + block_size, runtime->dma_area,
target - runtime->dma_bytes);
if (target >= runtime->dma_bytes)
target -= runtime->dma_bytes;
dsp->dma_offset = target;
}
static void rt5514_spi_stream(struct rt5514_dsp *dsp)
{
struct snd_pcm_runtime *runtime;
unsigned long delay_ms = RT5514_DSP_STREAM_DELAY_MS;
size_t period_bytes, truncated_bytes = 0;
size_t copy_bytes = 0;
if (!dsp->substream) {
dev_err(dsp->dev, "No pcm substream\n");
return;
}
runtime = dsp->substream->runtime;
period_bytes = snd_pcm_lib_period_bytes(dsp->substream);
if (frames_to_bytes(runtime, snd_pcm_capture_hw_avail(runtime)) <
period_bytes)
goto done;
if (dsp->dsp_offset < dsp->buf_size) {
copy_bytes = min(period_bytes, dsp->buf_size - dsp->dsp_offset);
if (dsp->buf_rp + copy_bytes <= dsp->buf_limit) {
rt5514_spi_stream_data(dsp, dsp->buf_rp, copy_bytes);
if (dsp->buf_rp + copy_bytes == dsp->buf_limit)
dsp->buf_rp = dsp->buf_base;
else
dsp->buf_rp += copy_bytes;
} else {
truncated_bytes = dsp->buf_limit - dsp->buf_rp;
rt5514_spi_stream_data(dsp, dsp->buf_rp,
truncated_bytes);
rt5514_spi_stream_data(dsp, dsp->buf_base,
copy_bytes - truncated_bytes);
dsp->buf_rp = dsp->buf_base +
copy_bytes - truncated_bytes;
}
dsp->dsp_offset += copy_bytes;
if (dsp->dsp_offset == dsp->buf_size) {
dev_info(dsp->dev, "dsp_offset -> buf_size(%zu), dma_offset=%zu",
dsp->buf_size, dsp->dma_offset);
}
}
if (period_bytes - copy_bytes) {
rt5514_spi_stream_zero(dsp, period_bytes - copy_bytes);
delay_ms = runtime->period_size * 1000 / runtime->rate;
}
snd_pcm_period_elapsed(dsp->substream);
done:
queue_delayed_work(system_freezable_wq, &dsp->work,
msecs_to_jiffies(delay_ms));
}
/*
* A delayed_work that configures the codec audio path for hotwording, loads
* DSP firmware, streams audio samples after a hotword is detected, and stops
* the DSP when requested.
*/
static void rt5514_spi_work(struct work_struct *work)
{
struct rt5514_dsp *dsp = container_of(work, struct rt5514_dsp,
work.work);
mutex_lock(&dsp->priv->dsp_lock);
switch (dsp->priv->state) {
case RT5514_DSP_START:
rt5514_spi_start(dsp);
break;
case RT5514_DSP_TRIGGERED:
dev_info(dsp->dev, "Hotword fired, prepare to stream\n");
if (rt5514_spi_prepare_to_stream(dsp))
break;
/* Fall through to start streaming */
case RT5514_DSP_STREAM:
rt5514_spi_stream(dsp);
break;
default:
break;
}
mutex_unlock(&dsp->priv->dsp_lock);
}
/* PCM for streaming audio from the DSP buffer */
static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream)
{
snd_soc_set_runtime_hwparams(substream, &rt5514_spi_pcm_hardware);
return 0;
}
static int rt5514_spi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct rt5514_dsp *dsp = snd_soc_platform_get_drvdata(rtd->platform);
int ret;
ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
dsp->substream = substream;
dsp->priv = snd_soc_codec_get_drvdata(rtd->codec);
if (ret < 0)
return ret;
mutex_lock(&dsp->priv->dsp_lock);
switch (dsp->priv->state) {
case RT5514_IDLE:
dsp->priv->state = RT5514_DSP_START;
break;
case RT5514_AIF1_ON:
dsp->priv->state = RT5514_AIF1_ON_DSP_PENDING;
break;
default:
break;
}
mutex_unlock(&dsp->priv->dsp_lock);
return ret;
}
static int rt5514_spi_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct rt5514_dsp *dsp = snd_soc_platform_get_drvdata(rtd->platform);
cancel_delayed_work_sync(&dsp->work);
mutex_lock(&dsp->priv->dsp_lock);
dsp->substream = NULL;
mutex_unlock(&dsp->priv->dsp_lock);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int rt5514_spi_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct rt5514_dsp *dsp = snd_soc_platform_get_drvdata(rtd->platform);
mutex_lock(&dsp->priv->dsp_lock);
dsp->dma_offset = 0;
dsp->dsp_offset = 0;
mutex_unlock(&dsp->priv->dsp_lock);
return 0;
}
static int rt5514_spi_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct rt5514_dsp *dsp = snd_soc_platform_get_drvdata(rtd->platform);
if (cmd == SNDRV_PCM_TRIGGER_START)
queue_delayed_work(system_freezable_wq, &dsp->work, 0);
return 0;
}
static snd_pcm_uframes_t rt5514_spi_pcm_pointer(
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct rt5514_dsp *rt5514_dsp =
snd_soc_platform_get_drvdata(rtd->platform);
return bytes_to_frames(runtime, rt5514_dsp->dma_offset);
}
static const struct snd_pcm_ops rt5514_spi_pcm_ops = {
.open = rt5514_spi_pcm_open,
.hw_params = rt5514_spi_hw_params,
.hw_free = rt5514_spi_hw_free,
.trigger = rt5514_spi_trigger,
.prepare = rt5514_spi_prepare,
.pointer = rt5514_spi_pcm_pointer,
.mmap = snd_pcm_lib_mmap_vmalloc,
.page = snd_pcm_lib_get_vmalloc_page,
};
static irqreturn_t rt5514_spi_irq(int irq, void *data)
{
struct rt5514_dsp *dsp = data;
if (!dsp->priv)
return IRQ_NONE;
mutex_lock(&dsp->priv->dsp_lock);
if (dsp->priv->state == RT5514_DSP_ARMED) {
dsp->priv->state = RT5514_DSP_TRIGGERED;
queue_delayed_work(system_freezable_wq, &dsp->work, 0);
}
mutex_unlock(&dsp->priv->dsp_lock);
return IRQ_HANDLED;
}
static int rt5514_spi_pcm_probe(struct snd_soc_platform *platform)
{
int irq, ret;
struct rt5514_dsp *dsp;
dsp = devm_kzalloc(platform->dev, sizeof(*dsp), GFP_KERNEL);
if (!dsp)
return -ENOMEM;
dsp->dev = platform->dev;
INIT_DELAYED_WORK(&dsp->work, rt5514_spi_work);
snd_soc_platform_set_drvdata(platform, dsp);
irq = to_spi_device(dsp->dev)->irq;
if (!irq) {
dev_err(dsp->dev, "No IRQ assigned.\n");
return -ENODEV;
}
ret = devm_request_threaded_irq(dsp->dev, irq, NULL, rt5514_spi_irq,
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514", dsp);
if (ret)
dev_err(dsp->dev, "Failed to request IRQ: %d\n", ret);
return ret;
}
static struct snd_soc_platform_driver rt5514_spi_platform = {
.probe = rt5514_spi_pcm_probe,
.ops = &rt5514_spi_pcm_ops,
};
static const struct snd_soc_component_driver rt5514_spi_dai_component = {
.name = "rt5514-spi-dai",
};
/**
* rt5514_spi_burst_read - Read data from SPI by rt5514 address.
* @addr: Start address.
* @rxbuf: Data Buffer for reading.
* @len: Data length, it must be a multiple of 8.
*
*
* Returns true for success.
*/
static int rt5514_spi_burst_read(struct rt5514_dsp *dsp, unsigned int addr,
u8 *rxbuf, size_t len)
{
u8 spi_cmd = RT5514_SPI_CMD_BURST_READ;
int status;
u8 write_buf[8];
unsigned int i, end, offset = 0;
struct spi_message message;
struct spi_transfer x[3];
while (offset < len) {
if (offset + RT5514_SPI_BUF_LEN <= len)
end = RT5514_SPI_BUF_LEN;
else
end = len % RT5514_SPI_BUF_LEN;
write_buf[0] = spi_cmd;
write_buf[1] = ((addr + offset) & 0xff000000) >> 24;
write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
write_buf[4] = ((addr + offset) & 0x000000ff) >> 0;
spi_message_init(&message);
memset(x, 0, sizeof(x));
x[0].len = 5;
x[0].tx_buf = write_buf;
spi_message_add_tail(&x[0], &message);
x[1].len = 4;
x[1].tx_buf = write_buf;
spi_message_add_tail(&x[1], &message);
x[2].len = end;
x[2].rx_buf = rxbuf + offset;
spi_message_add_tail(&x[2], &message);
status = spi_sync(to_spi_device(dsp->dev), &message);
if (status)
return false;
offset += RT5514_SPI_BUF_LEN;
}
for (i = 0; i < len; i += 8) {
write_buf[0] = rxbuf[i + 0];
write_buf[1] = rxbuf[i + 1];
write_buf[2] = rxbuf[i + 2];
write_buf[3] = rxbuf[i + 3];
write_buf[4] = rxbuf[i + 4];
write_buf[5] = rxbuf[i + 5];
write_buf[6] = rxbuf[i + 6];
write_buf[7] = rxbuf[i + 7];
rxbuf[i + 0] = write_buf[7];
rxbuf[i + 1] = write_buf[6];
rxbuf[i + 2] = write_buf[5];
rxbuf[i + 3] = write_buf[4];
rxbuf[i + 4] = write_buf[3];
rxbuf[i + 5] = write_buf[2];
rxbuf[i + 6] = write_buf[1];
rxbuf[i + 7] = write_buf[0];
}
return true;
}
/**
* rt5514_spi_burst_write - Write data to SPI by rt5514 address.
* @addr: Start address.
* @txbuf: Data Buffer for writng.
* @len: Data length, it must be a multiple of 8.
*
*
* Returns true for success.
*/
static int rt5514_spi_burst_write(struct rt5514_dsp *dsp, u32 addr,
const u8 *txbuf, size_t len)
{
u8 spi_cmd = RT5514_SPI_CMD_BURST_WRITE;
u8 *write_buf;
unsigned int i, end, offset = 0;
write_buf = kmalloc(RT5514_SPI_BUF_LEN + 6, GFP_KERNEL);
if (write_buf == NULL)
return -ENOMEM;
while (offset < len) {
if (offset + RT5514_SPI_BUF_LEN <= len)
end = RT5514_SPI_BUF_LEN;
else
end = len % RT5514_SPI_BUF_LEN;
write_buf[0] = spi_cmd;
write_buf[1] = ((addr + offset) & 0xff000000) >> 24;
write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
write_buf[4] = ((addr + offset) & 0x000000ff) >> 0;
for (i = 0; i < end; i += 8) {
write_buf[i + 12] = txbuf[offset + i + 0];
write_buf[i + 11] = txbuf[offset + i + 1];
write_buf[i + 10] = txbuf[offset + i + 2];
write_buf[i + 9] = txbuf[offset + i + 3];
write_buf[i + 8] = txbuf[offset + i + 4];
write_buf[i + 7] = txbuf[offset + i + 5];
write_buf[i + 6] = txbuf[offset + i + 6];
write_buf[i + 5] = txbuf[offset + i + 7];
}
write_buf[end + 5] = spi_cmd;
spi_write(to_spi_device(dsp->dev), write_buf, end + 6);
offset += RT5514_SPI_BUF_LEN;
}
kfree(write_buf);
return 0;
}
static int rt5514_spi_probe(struct spi_device *spi)
{
int ret;
ret = devm_snd_soc_register_platform(&spi->dev, &rt5514_spi_platform);
if (ret < 0) {
dev_err(&spi->dev, "Failed to register platform.\n");
return ret;
}
ret = devm_snd_soc_register_component(&spi->dev,
&rt5514_spi_dai_component,
&rt5514_spi_dai, 1);
if (ret < 0) {
dev_err(&spi->dev, "Failed to register component.\n");
return ret;
}
device_init_wakeup(&spi->dev, true);
return 0;
}
static int rt5514_suspend(struct device *dev)
{
int irq = to_spi_device(dev)->irq;
if (device_may_wakeup(dev))
enable_irq_wake(irq);
return 0;
}
static int rt5514_resume(struct device *dev)
{
int irq = to_spi_device(dev)->irq;
if (device_may_wakeup(dev))
disable_irq_wake(irq);
return 0;
}
static const struct dev_pm_ops rt5514_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rt5514_suspend, rt5514_resume)
};
static const struct of_device_id rt5514_of_match[] = {
{ .compatible = "realtek,rt5514", },
{},
};
MODULE_DEVICE_TABLE(of, rt5514_of_match);
static struct spi_driver rt5514_spi_driver = {
.driver = {
.name = "rt5514",
.pm = &rt5514_pm_ops,
.of_match_table = of_match_ptr(rt5514_of_match),
},
.probe = rt5514_spi_probe,
};
module_spi_driver(rt5514_spi_driver);
MODULE_DESCRIPTION("RT5514 SPI driver");
MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>");
MODULE_LICENSE("GPL v2");