forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
13 KiB
513 lines
13 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* McBSP Sidetone support |
|
* |
|
* Copyright (C) 2004 Nokia Corporation |
|
* Author: Samuel Ortiz <[email protected]> |
|
* |
|
* Contact: Jarkko Nikula <[email protected]> |
|
* Peter Ujfalusi <[email protected]> |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/init.h> |
|
#include <linux/device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/err.h> |
|
#include <linux/clk.h> |
|
#include <linux/delay.h> |
|
#include <linux/io.h> |
|
#include <linux/slab.h> |
|
#include <linux/pm_runtime.h> |
|
|
|
#include "omap-mcbsp.h" |
|
#include "omap-mcbsp-priv.h" |
|
|
|
/* OMAP3 sidetone control registers */ |
|
#define OMAP_ST_REG_REV 0x00 |
|
#define OMAP_ST_REG_SYSCONFIG 0x10 |
|
#define OMAP_ST_REG_IRQSTATUS 0x18 |
|
#define OMAP_ST_REG_IRQENABLE 0x1C |
|
#define OMAP_ST_REG_SGAINCR 0x24 |
|
#define OMAP_ST_REG_SFIRCR 0x28 |
|
#define OMAP_ST_REG_SSELCR 0x2C |
|
|
|
/********************** McBSP SSELCR bit definitions ***********************/ |
|
#define SIDETONEEN BIT(10) |
|
|
|
/********************** McBSP Sidetone SYSCONFIG bit definitions ***********/ |
|
#define ST_AUTOIDLE BIT(0) |
|
|
|
/********************** McBSP Sidetone SGAINCR bit definitions *************/ |
|
#define ST_CH0GAIN(value) ((value) & 0xffff) /* Bits 0:15 */ |
|
#define ST_CH1GAIN(value) (((value) & 0xffff) << 16) /* Bits 16:31 */ |
|
|
|
/********************** McBSP Sidetone SFIRCR bit definitions **************/ |
|
#define ST_FIRCOEFF(value) ((value) & 0xffff) /* Bits 0:15 */ |
|
|
|
/********************** McBSP Sidetone SSELCR bit definitions **************/ |
|
#define ST_SIDETONEEN BIT(0) |
|
#define ST_COEFFWREN BIT(1) |
|
#define ST_COEFFWRDONE BIT(2) |
|
|
|
struct omap_mcbsp_st_data { |
|
void __iomem *io_base_st; |
|
struct clk *mcbsp_iclk; |
|
bool running; |
|
bool enabled; |
|
s16 taps[128]; /* Sidetone filter coefficients */ |
|
int nr_taps; /* Number of filter coefficients in use */ |
|
s16 ch0gain; |
|
s16 ch1gain; |
|
}; |
|
|
|
static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) |
|
{ |
|
writel_relaxed(val, mcbsp->st_data->io_base_st + reg); |
|
} |
|
|
|
static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg) |
|
{ |
|
return readl_relaxed(mcbsp->st_data->io_base_st + reg); |
|
} |
|
|
|
#define MCBSP_ST_READ(mcbsp, reg) omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg) |
|
#define MCBSP_ST_WRITE(mcbsp, reg, val) \ |
|
omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val) |
|
|
|
static void omap_mcbsp_st_on(struct omap_mcbsp *mcbsp) |
|
{ |
|
unsigned int w; |
|
|
|
if (mcbsp->pdata->force_ick_on) |
|
mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, true); |
|
|
|
/* Disable Sidetone clock auto-gating for normal operation */ |
|
w = MCBSP_ST_READ(mcbsp, SYSCONFIG); |
|
MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE)); |
|
|
|
/* Enable McBSP Sidetone */ |
|
w = MCBSP_READ(mcbsp, SSELCR); |
|
MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); |
|
|
|
/* Enable Sidetone from Sidetone Core */ |
|
w = MCBSP_ST_READ(mcbsp, SSELCR); |
|
MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN); |
|
} |
|
|
|
static void omap_mcbsp_st_off(struct omap_mcbsp *mcbsp) |
|
{ |
|
unsigned int w; |
|
|
|
w = MCBSP_ST_READ(mcbsp, SSELCR); |
|
MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN)); |
|
|
|
w = MCBSP_READ(mcbsp, SSELCR); |
|
MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); |
|
|
|
/* Enable Sidetone clock auto-gating to reduce power consumption */ |
|
w = MCBSP_ST_READ(mcbsp, SYSCONFIG); |
|
MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); |
|
|
|
if (mcbsp->pdata->force_ick_on) |
|
mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, false); |
|
} |
|
|
|
static void omap_mcbsp_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) |
|
{ |
|
u16 val, i; |
|
|
|
val = MCBSP_ST_READ(mcbsp, SSELCR); |
|
|
|
if (val & ST_COEFFWREN) |
|
MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
|
|
|
MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN); |
|
|
|
for (i = 0; i < 128; i++) |
|
MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]); |
|
|
|
i = 0; |
|
|
|
val = MCBSP_ST_READ(mcbsp, SSELCR); |
|
while (!(val & ST_COEFFWRDONE) && (++i < 1000)) |
|
val = MCBSP_ST_READ(mcbsp, SSELCR); |
|
|
|
MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
|
|
|
if (i == 1000) |
|
dev_err(mcbsp->dev, "McBSP FIR load error!\n"); |
|
} |
|
|
|
static void omap_mcbsp_st_chgain(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
|
|
MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) | |
|
ST_CH1GAIN(st_data->ch1gain)); |
|
} |
|
|
|
static int omap_mcbsp_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, |
|
s16 chgain) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
int ret = 0; |
|
|
|
if (!st_data) |
|
return -ENOENT; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
if (channel == 0) |
|
st_data->ch0gain = chgain; |
|
else if (channel == 1) |
|
st_data->ch1gain = chgain; |
|
else |
|
ret = -EINVAL; |
|
|
|
if (st_data->enabled) |
|
omap_mcbsp_st_chgain(mcbsp); |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int omap_mcbsp_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, |
|
s16 *chgain) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
int ret = 0; |
|
|
|
if (!st_data) |
|
return -ENOENT; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
if (channel == 0) |
|
*chgain = st_data->ch0gain; |
|
else if (channel == 1) |
|
*chgain = st_data->ch1gain; |
|
else |
|
ret = -EINVAL; |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int omap_mcbsp_st_enable(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
|
|
if (!st_data) |
|
return -ENODEV; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
st_data->enabled = 1; |
|
omap_mcbsp_st_start(mcbsp); |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return 0; |
|
} |
|
|
|
static int omap_mcbsp_st_disable(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
int ret = 0; |
|
|
|
if (!st_data) |
|
return -ENODEV; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
omap_mcbsp_st_stop(mcbsp); |
|
st_data->enabled = 0; |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return ret; |
|
} |
|
|
|
static int omap_mcbsp_st_is_enabled(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
|
|
if (!st_data) |
|
return -ENODEV; |
|
|
|
return st_data->enabled; |
|
} |
|
|
|
static ssize_t st_taps_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
ssize_t status = 0; |
|
int i; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
for (i = 0; i < st_data->nr_taps; i++) |
|
status += sprintf(&buf[status], (i ? ", %d" : "%d"), |
|
st_data->taps[i]); |
|
if (i) |
|
status += sprintf(&buf[status], "\n"); |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return status; |
|
} |
|
|
|
static ssize_t st_taps_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t size) |
|
{ |
|
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
int val, tmp, status, i = 0; |
|
|
|
spin_lock_irq(&mcbsp->lock); |
|
memset(st_data->taps, 0, sizeof(st_data->taps)); |
|
st_data->nr_taps = 0; |
|
|
|
do { |
|
status = sscanf(buf, "%d%n", &val, &tmp); |
|
if (status < 0 || status == 0) { |
|
size = -EINVAL; |
|
goto out; |
|
} |
|
if (val < -32768 || val > 32767) { |
|
size = -EINVAL; |
|
goto out; |
|
} |
|
st_data->taps[i++] = val; |
|
buf += tmp; |
|
if (*buf != ',') |
|
break; |
|
buf++; |
|
} while (1); |
|
|
|
st_data->nr_taps = i; |
|
|
|
out: |
|
spin_unlock_irq(&mcbsp->lock); |
|
|
|
return size; |
|
} |
|
|
|
static DEVICE_ATTR_RW(st_taps); |
|
|
|
static const struct attribute *sidetone_attrs[] = { |
|
&dev_attr_st_taps.attr, |
|
NULL, |
|
}; |
|
|
|
static const struct attribute_group sidetone_attr_group = { |
|
.attrs = (struct attribute **)sidetone_attrs, |
|
}; |
|
|
|
int omap_mcbsp_st_start(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
|
|
if (st_data->enabled && !st_data->running) { |
|
omap_mcbsp_st_fir_write(mcbsp, st_data->taps); |
|
omap_mcbsp_st_chgain(mcbsp); |
|
|
|
if (!mcbsp->free) { |
|
omap_mcbsp_st_on(mcbsp); |
|
st_data->running = 1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int omap_mcbsp_st_stop(struct omap_mcbsp *mcbsp) |
|
{ |
|
struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
|
|
|
if (st_data->running) { |
|
if (!mcbsp->free) { |
|
omap_mcbsp_st_off(mcbsp); |
|
st_data->running = 0; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int omap_mcbsp_st_init(struct platform_device *pdev) |
|
{ |
|
struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); |
|
struct omap_mcbsp_st_data *st_data; |
|
struct resource *res; |
|
int ret; |
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone"); |
|
if (!res) |
|
return 0; |
|
|
|
st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL); |
|
if (!st_data) |
|
return -ENOMEM; |
|
|
|
st_data->mcbsp_iclk = clk_get(mcbsp->dev, "ick"); |
|
if (IS_ERR(st_data->mcbsp_iclk)) { |
|
dev_warn(mcbsp->dev, |
|
"Failed to get ick, sidetone might be broken\n"); |
|
st_data->mcbsp_iclk = NULL; |
|
} |
|
|
|
st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start, |
|
resource_size(res)); |
|
if (!st_data->io_base_st) |
|
return -ENOMEM; |
|
|
|
ret = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group); |
|
if (ret) |
|
return ret; |
|
|
|
mcbsp->st_data = st_data; |
|
|
|
return 0; |
|
} |
|
|
|
void omap_mcbsp_st_cleanup(struct platform_device *pdev) |
|
{ |
|
struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); |
|
|
|
if (mcbsp->st_data) { |
|
sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); |
|
clk_put(mcbsp->st_data->mcbsp_iclk); |
|
} |
|
} |
|
|
|
static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_info *uinfo) |
|
{ |
|
struct soc_mixer_control *mc = |
|
(struct soc_mixer_control *)kcontrol->private_value; |
|
int max = mc->max; |
|
int min = mc->min; |
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
|
uinfo->count = 1; |
|
uinfo->value.integer.min = min; |
|
uinfo->value.integer.max = max; |
|
return 0; |
|
} |
|
|
|
#define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel) \ |
|
static int \ |
|
omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \ |
|
struct snd_ctl_elem_value *uc) \ |
|
{ \ |
|
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ |
|
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ |
|
struct soc_mixer_control *mc = \ |
|
(struct soc_mixer_control *)kc->private_value; \ |
|
int max = mc->max; \ |
|
int min = mc->min; \ |
|
int val = uc->value.integer.value[0]; \ |
|
\ |
|
if (val < min || val > max) \ |
|
return -EINVAL; \ |
|
\ |
|
/* OMAP McBSP implementation uses index values 0..4 */ \ |
|
return omap_mcbsp_st_set_chgain(mcbsp, channel, val); \ |
|
} \ |
|
\ |
|
static int \ |
|
omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \ |
|
struct snd_ctl_elem_value *uc) \ |
|
{ \ |
|
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ |
|
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ |
|
s16 chgain; \ |
|
\ |
|
if (omap_mcbsp_st_get_chgain(mcbsp, channel, &chgain)) \ |
|
return -EAGAIN; \ |
|
\ |
|
uc->value.integer.value[0] = chgain; \ |
|
return 0; \ |
|
} |
|
|
|
OMAP_MCBSP_ST_CHANNEL_VOLUME(0) |
|
OMAP_MCBSP_ST_CHANNEL_VOLUME(1) |
|
|
|
static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); |
|
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); |
|
u8 value = ucontrol->value.integer.value[0]; |
|
|
|
if (value == omap_mcbsp_st_is_enabled(mcbsp)) |
|
return 0; |
|
|
|
if (value) |
|
omap_mcbsp_st_enable(mcbsp); |
|
else |
|
omap_mcbsp_st_disable(mcbsp); |
|
|
|
return 1; |
|
} |
|
|
|
static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); |
|
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); |
|
|
|
ucontrol->value.integer.value[0] = omap_mcbsp_st_is_enabled(mcbsp); |
|
return 0; |
|
} |
|
|
|
#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \ |
|
xhandler_get, xhandler_put) \ |
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
|
.info = omap_mcbsp_st_info_volsw, \ |
|
.get = xhandler_get, .put = xhandler_put, \ |
|
.private_value = (unsigned long)&(struct soc_mixer_control) \ |
|
{.min = xmin, .max = xmax} } |
|
|
|
#define OMAP_MCBSP_ST_CONTROLS(port) \ |
|
static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \ |
|
SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0, \ |
|
omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode), \ |
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \ |
|
-32768, 32767, \ |
|
omap_mcbsp_get_st_ch0_volume, \ |
|
omap_mcbsp_set_st_ch0_volume), \ |
|
OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \ |
|
-32768, 32767, \ |
|
omap_mcbsp_get_st_ch1_volume, \ |
|
omap_mcbsp_set_st_ch1_volume), \ |
|
} |
|
|
|
OMAP_MCBSP_ST_CONTROLS(2); |
|
OMAP_MCBSP_ST_CONTROLS(3); |
|
|
|
int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id) |
|
{ |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); |
|
|
|
if (!mcbsp->st_data) { |
|
dev_warn(mcbsp->dev, "No sidetone data for port\n"); |
|
return 0; |
|
} |
|
|
|
switch (port_id) { |
|
case 2: /* McBSP 2 */ |
|
return snd_soc_add_dai_controls(cpu_dai, |
|
omap_mcbsp2_st_controls, |
|
ARRAY_SIZE(omap_mcbsp2_st_controls)); |
|
case 3: /* McBSP 3 */ |
|
return snd_soc_add_dai_controls(cpu_dai, |
|
omap_mcbsp3_st_controls, |
|
ARRAY_SIZE(omap_mcbsp3_st_controls)); |
|
default: |
|
dev_err(mcbsp->dev, "Port %d not supported\n", port_id); |
|
break; |
|
} |
|
|
|
return -EINVAL; |
|
} |
|
EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
|
|
|