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.
799 lines
19 KiB
799 lines
19 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
// |
|
// siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. |
|
// |
|
// Copyright (C) 2009-2010 Guennadi Liakhovetski <[email protected]> |
|
// Copyright (C) 2006 Carlos Munoz <[email protected]> |
|
|
|
#include <linux/delay.h> |
|
#include <linux/firmware.h> |
|
#include <linux/pm_runtime.h> |
|
#include <linux/slab.h> |
|
#include <linux/module.h> |
|
|
|
#include <asm/clock.h> |
|
#include <asm/siu.h> |
|
|
|
#include <sound/control.h> |
|
#include <sound/soc.h> |
|
|
|
#include "siu.h" |
|
|
|
/* Board specifics */ |
|
#if defined(CONFIG_CPU_SUBTYPE_SH7722) |
|
# define SIU_MAX_VOLUME 0x1000 |
|
#else |
|
# define SIU_MAX_VOLUME 0x7fff |
|
#endif |
|
|
|
#define PRAM_SIZE 0x2000 |
|
#define XRAM_SIZE 0x800 |
|
#define YRAM_SIZE 0x800 |
|
|
|
#define XRAM_OFFSET 0x4000 |
|
#define YRAM_OFFSET 0x6000 |
|
#define REG_OFFSET 0xc000 |
|
|
|
#define PLAYBACK_ENABLED 1 |
|
#define CAPTURE_ENABLED 2 |
|
|
|
#define VOLUME_CAPTURE 0 |
|
#define VOLUME_PLAYBACK 1 |
|
#define DFLT_VOLUME_LEVEL 0x08000800 |
|
|
|
/* |
|
* SPDIF is only available on port A and on some SIU implementations it is only |
|
* available for input. Due to the lack of hardware to test it, SPDIF is left |
|
* disabled in this driver version |
|
*/ |
|
struct format_flag { |
|
u32 i2s; |
|
u32 pcm; |
|
u32 spdif; |
|
u32 mask; |
|
}; |
|
|
|
struct port_flag { |
|
struct format_flag playback; |
|
struct format_flag capture; |
|
}; |
|
|
|
struct siu_info *siu_i2s_data; |
|
|
|
static struct port_flag siu_flags[SIU_PORT_NUM] = { |
|
[SIU_PORT_A] = { |
|
.playback = { |
|
.i2s = 0x50000000, |
|
.pcm = 0x40000000, |
|
.spdif = 0x80000000, /* not on all SIU versions */ |
|
.mask = 0xd0000000, |
|
}, |
|
.capture = { |
|
.i2s = 0x05000000, |
|
.pcm = 0x04000000, |
|
.spdif = 0x08000000, |
|
.mask = 0x0d000000, |
|
}, |
|
}, |
|
[SIU_PORT_B] = { |
|
.playback = { |
|
.i2s = 0x00500000, |
|
.pcm = 0x00400000, |
|
.spdif = 0, /* impossible - turn off */ |
|
.mask = 0x00500000, |
|
}, |
|
.capture = { |
|
.i2s = 0x00050000, |
|
.pcm = 0x00040000, |
|
.spdif = 0, /* impossible - turn off */ |
|
.mask = 0x00050000, |
|
}, |
|
}, |
|
}; |
|
|
|
static void siu_dai_start(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
|
|
dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); |
|
|
|
/* Issue software reset to siu */ |
|
siu_write32(base + SIU_SRCTL, 0); |
|
|
|
/* Wait for the reset to take effect */ |
|
udelay(1); |
|
|
|
port_info->stfifo = 0; |
|
port_info->trdat = 0; |
|
|
|
/* portA, portB, SIU operate */ |
|
siu_write32(base + SIU_SRCTL, 0x301); |
|
|
|
/* portA=256fs, portB=256fs */ |
|
siu_write32(base + SIU_CKCTL, 0x40400000); |
|
|
|
/* portA's BRG does not divide SIUCKA */ |
|
siu_write32(base + SIU_BRGASEL, 0); |
|
siu_write32(base + SIU_BRRA, 0); |
|
|
|
/* portB's BRG divides SIUCKB by half */ |
|
siu_write32(base + SIU_BRGBSEL, 1); |
|
siu_write32(base + SIU_BRRB, 0); |
|
|
|
siu_write32(base + SIU_IFCTL, 0x44440000); |
|
|
|
/* portA: 32 bit/fs, master; portB: 32 bit/fs, master */ |
|
siu_write32(base + SIU_SFORM, 0x0c0c0000); |
|
|
|
/* |
|
* Volume levels: looks like the DSP firmware implements volume controls |
|
* differently from what's described in the datasheet |
|
*/ |
|
siu_write32(base + SIU_SBDVCA, port_info->playback.volume); |
|
siu_write32(base + SIU_SBDVCB, port_info->capture.volume); |
|
} |
|
|
|
static void siu_dai_stop(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
|
|
/* SIU software reset */ |
|
siu_write32(base + SIU_SRCTL, 0); |
|
} |
|
|
|
static void siu_dai_spbAselect(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
struct siu_firmware *fw = &info->fw; |
|
u32 *ydef = fw->yram0; |
|
u32 idx; |
|
|
|
/* path A use */ |
|
if (!info->port_id) |
|
idx = 1; /* portA */ |
|
else |
|
idx = 2; /* portB */ |
|
|
|
ydef[0] = (fw->spbpar[idx].ab1a << 16) | |
|
(fw->spbpar[idx].ab0a << 8) | |
|
(fw->spbpar[idx].dir << 7) | 3; |
|
ydef[1] = fw->yram0[1]; /* 0x03000300 */ |
|
ydef[2] = (16 / 2) << 24; |
|
ydef[3] = fw->yram0[3]; /* 0 */ |
|
ydef[4] = fw->yram0[4]; /* 0 */ |
|
ydef[7] = fw->spbpar[idx].event; |
|
port_info->stfifo |= fw->spbpar[idx].stfifo; |
|
port_info->trdat |= fw->spbpar[idx].trdat; |
|
} |
|
|
|
static void siu_dai_spbBselect(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
struct siu_firmware *fw = &info->fw; |
|
u32 *ydef = fw->yram0; |
|
u32 idx; |
|
|
|
/* path B use */ |
|
if (!info->port_id) |
|
idx = 7; /* portA */ |
|
else |
|
idx = 8; /* portB */ |
|
|
|
ydef[5] = (fw->spbpar[idx].ab1a << 16) | |
|
(fw->spbpar[idx].ab0a << 8) | 1; |
|
ydef[6] = fw->spbpar[idx].event; |
|
port_info->stfifo |= fw->spbpar[idx].stfifo; |
|
port_info->trdat |= fw->spbpar[idx].trdat; |
|
} |
|
|
|
static void siu_dai_open(struct siu_stream *siu_stream) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
u32 srctl, ifctl; |
|
|
|
srctl = siu_read32(base + SIU_SRCTL); |
|
ifctl = siu_read32(base + SIU_IFCTL); |
|
|
|
switch (info->port_id) { |
|
case SIU_PORT_A: |
|
/* portA operates */ |
|
srctl |= 0x200; |
|
ifctl &= ~0xc2; |
|
break; |
|
case SIU_PORT_B: |
|
/* portB operates */ |
|
srctl |= 0x100; |
|
ifctl &= ~0x31; |
|
break; |
|
} |
|
|
|
siu_write32(base + SIU_SRCTL, srctl); |
|
/* Unmute and configure portA */ |
|
siu_write32(base + SIU_IFCTL, ifctl); |
|
} |
|
|
|
/* |
|
* At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower |
|
* packing is supported |
|
*/ |
|
static void siu_dai_pcmdatapack(struct siu_stream *siu_stream) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
u32 dpak; |
|
|
|
dpak = siu_read32(base + SIU_DPAK); |
|
|
|
switch (info->port_id) { |
|
case SIU_PORT_A: |
|
dpak &= ~0xc0000000; |
|
break; |
|
case SIU_PORT_B: |
|
dpak &= ~0x00c00000; |
|
break; |
|
} |
|
|
|
siu_write32(base + SIU_DPAK, dpak); |
|
} |
|
|
|
static int siu_dai_spbstart(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
struct siu_firmware *fw = &info->fw; |
|
u32 *ydef = fw->yram0; |
|
int cnt; |
|
u32 __iomem *add; |
|
u32 *ptr; |
|
|
|
/* Load SPB Program in PRAM */ |
|
ptr = fw->pram0; |
|
add = info->pram; |
|
for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++) |
|
siu_write32(add, *ptr); |
|
|
|
ptr = fw->pram1; |
|
add = info->pram + (0x0100 / sizeof(u32)); |
|
for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++) |
|
siu_write32(add, *ptr); |
|
|
|
/* XRAM initialization */ |
|
add = info->xram; |
|
for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++) |
|
siu_write32(add, 0); |
|
|
|
/* YRAM variable area initialization */ |
|
add = info->yram; |
|
for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++) |
|
siu_write32(add, ydef[cnt]); |
|
|
|
/* YRAM FIR coefficient area initialization */ |
|
add = info->yram + (0x0200 / sizeof(u32)); |
|
for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++) |
|
siu_write32(add, fw->yram_fir_coeff[cnt]); |
|
|
|
/* YRAM IIR coefficient area initialization */ |
|
add = info->yram + (0x0600 / sizeof(u32)); |
|
for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++) |
|
siu_write32(add, 0); |
|
|
|
siu_write32(base + SIU_TRDAT, port_info->trdat); |
|
port_info->trdat = 0x0; |
|
|
|
|
|
/* SPB start condition: software */ |
|
siu_write32(base + SIU_SBACTIV, 0); |
|
/* Start SPB */ |
|
siu_write32(base + SIU_SBCTL, 0xc0000000); |
|
/* Wait for program to halt */ |
|
cnt = 0x10000; |
|
while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000) |
|
cpu_relax(); |
|
|
|
if (!cnt) |
|
return -EBUSY; |
|
|
|
/* SPB program start address setting */ |
|
siu_write32(base + SIU_SBPSET, 0x00400000); |
|
/* SPB hardware start(FIFOCTL source) */ |
|
siu_write32(base + SIU_SBACTIV, 0xc0000000); |
|
|
|
return 0; |
|
} |
|
|
|
static void siu_dai_spbstop(struct siu_port *port_info) |
|
{ |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
|
|
siu_write32(base + SIU_SBACTIV, 0); |
|
/* SPB stop */ |
|
siu_write32(base + SIU_SBCTL, 0); |
|
|
|
port_info->stfifo = 0; |
|
} |
|
|
|
/* API functions */ |
|
|
|
/* Playback and capture hardware properties are identical */ |
|
static const struct snd_pcm_hardware siu_dai_pcm_hw = { |
|
.info = SNDRV_PCM_INFO_INTERLEAVED, |
|
.formats = SNDRV_PCM_FMTBIT_S16, |
|
.rates = SNDRV_PCM_RATE_8000_48000, |
|
.rate_min = 8000, |
|
.rate_max = 48000, |
|
.channels_min = 2, |
|
.channels_max = 2, |
|
.buffer_bytes_max = SIU_BUFFER_BYTES_MAX, |
|
.period_bytes_min = SIU_PERIOD_BYTES_MIN, |
|
.period_bytes_max = SIU_PERIOD_BYTES_MAX, |
|
.periods_min = SIU_PERIODS_MIN, |
|
.periods_max = SIU_PERIODS_MAX, |
|
}; |
|
|
|
static int siu_dai_info_volume(struct snd_kcontrol *kctrl, |
|
struct snd_ctl_elem_info *uinfo) |
|
{ |
|
struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
|
|
|
dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); |
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
|
uinfo->count = 2; |
|
uinfo->value.integer.min = 0; |
|
uinfo->value.integer.max = SIU_MAX_VOLUME; |
|
|
|
return 0; |
|
} |
|
|
|
static int siu_dai_get_volume(struct snd_kcontrol *kctrl, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
|
struct device *dev = port_info->pcm->card->dev; |
|
u32 vol; |
|
|
|
dev_dbg(dev, "%s\n", __func__); |
|
|
|
switch (kctrl->private_value) { |
|
case VOLUME_PLAYBACK: |
|
/* Playback is always on port 0 */ |
|
vol = port_info->playback.volume; |
|
ucontrol->value.integer.value[0] = vol & 0xffff; |
|
ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; |
|
break; |
|
case VOLUME_CAPTURE: |
|
/* Capture is always on port 1 */ |
|
vol = port_info->capture.volume; |
|
ucontrol->value.integer.value[0] = vol & 0xffff; |
|
ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; |
|
break; |
|
default: |
|
dev_err(dev, "%s() invalid private_value=%ld\n", |
|
__func__, kctrl->private_value); |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int siu_dai_put_volume(struct snd_kcontrol *kctrl, |
|
struct snd_ctl_elem_value *ucontrol) |
|
{ |
|
struct siu_port *port_info = snd_kcontrol_chip(kctrl); |
|
struct device *dev = port_info->pcm->card->dev; |
|
struct siu_info *info = siu_i2s_data; |
|
u32 __iomem *base = info->reg; |
|
u32 new_vol; |
|
u32 cur_vol; |
|
|
|
dev_dbg(dev, "%s\n", __func__); |
|
|
|
if (ucontrol->value.integer.value[0] < 0 || |
|
ucontrol->value.integer.value[0] > SIU_MAX_VOLUME || |
|
ucontrol->value.integer.value[1] < 0 || |
|
ucontrol->value.integer.value[1] > SIU_MAX_VOLUME) |
|
return -EINVAL; |
|
|
|
new_vol = ucontrol->value.integer.value[0] | |
|
ucontrol->value.integer.value[1] << 16; |
|
|
|
/* See comment above - DSP firmware implementation */ |
|
switch (kctrl->private_value) { |
|
case VOLUME_PLAYBACK: |
|
/* Playback is always on port 0 */ |
|
cur_vol = port_info->playback.volume; |
|
siu_write32(base + SIU_SBDVCA, new_vol); |
|
port_info->playback.volume = new_vol; |
|
break; |
|
case VOLUME_CAPTURE: |
|
/* Capture is always on port 1 */ |
|
cur_vol = port_info->capture.volume; |
|
siu_write32(base + SIU_SBDVCB, new_vol); |
|
port_info->capture.volume = new_vol; |
|
break; |
|
default: |
|
dev_err(dev, "%s() invalid private_value=%ld\n", |
|
__func__, kctrl->private_value); |
|
return -EINVAL; |
|
} |
|
|
|
if (cur_vol != new_vol) |
|
return 1; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct snd_kcontrol_new playback_controls = { |
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
|
.name = "PCM Playback Volume", |
|
.index = 0, |
|
.info = siu_dai_info_volume, |
|
.get = siu_dai_get_volume, |
|
.put = siu_dai_put_volume, |
|
.private_value = VOLUME_PLAYBACK, |
|
}; |
|
|
|
static const struct snd_kcontrol_new capture_controls = { |
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
|
.name = "PCM Capture Volume", |
|
.index = 0, |
|
.info = siu_dai_info_volume, |
|
.get = siu_dai_get_volume, |
|
.put = siu_dai_put_volume, |
|
.private_value = VOLUME_CAPTURE, |
|
}; |
|
|
|
int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card) |
|
{ |
|
struct device *dev = card->dev; |
|
struct snd_kcontrol *kctrl; |
|
int ret; |
|
|
|
*port_info = kzalloc(sizeof(**port_info), GFP_KERNEL); |
|
if (!*port_info) |
|
return -ENOMEM; |
|
|
|
dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info); |
|
|
|
(*port_info)->playback.volume = DFLT_VOLUME_LEVEL; |
|
(*port_info)->capture.volume = DFLT_VOLUME_LEVEL; |
|
|
|
/* |
|
* Add mixer support. The SPB is used to change the volume. Both |
|
* ports use the same SPB. Therefore, we only register one |
|
* control instance since it will be used by both channels. |
|
* In error case we continue without controls. |
|
*/ |
|
kctrl = snd_ctl_new1(&playback_controls, *port_info); |
|
ret = snd_ctl_add(card, kctrl); |
|
if (ret < 0) |
|
dev_err(dev, |
|
"failed to add playback controls %p port=%d err=%d\n", |
|
kctrl, port, ret); |
|
|
|
kctrl = snd_ctl_new1(&capture_controls, *port_info); |
|
ret = snd_ctl_add(card, kctrl); |
|
if (ret < 0) |
|
dev_err(dev, |
|
"failed to add capture controls %p port=%d err=%d\n", |
|
kctrl, port, ret); |
|
|
|
return 0; |
|
} |
|
|
|
void siu_free_port(struct siu_port *port_info) |
|
{ |
|
kfree(port_info); |
|
} |
|
|
|
static int siu_dai_startup(struct snd_pcm_substream *substream, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
|
struct snd_pcm_runtime *rt = substream->runtime; |
|
struct siu_port *port_info = siu_port_info(substream); |
|
int ret; |
|
|
|
dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, |
|
info->port_id, port_info); |
|
|
|
snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw); |
|
|
|
ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); |
|
if (unlikely(ret < 0)) |
|
return ret; |
|
|
|
siu_dai_start(port_info); |
|
|
|
return 0; |
|
} |
|
|
|
static void siu_dai_shutdown(struct snd_pcm_substream *substream, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
|
struct siu_port *port_info = siu_port_info(substream); |
|
|
|
dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, |
|
info->port_id, port_info); |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
port_info->play_cap &= ~PLAYBACK_ENABLED; |
|
else |
|
port_info->play_cap &= ~CAPTURE_ENABLED; |
|
|
|
/* Stop the siu if the other stream is not using it */ |
|
if (!port_info->play_cap) { |
|
/* during stmread or stmwrite ? */ |
|
if (WARN_ON(port_info->playback.rw_flg || port_info->capture.rw_flg)) |
|
return; |
|
siu_dai_spbstop(port_info); |
|
siu_dai_stop(port_info); |
|
} |
|
} |
|
|
|
/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */ |
|
static int siu_dai_prepare(struct snd_pcm_substream *substream, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
|
struct snd_pcm_runtime *rt = substream->runtime; |
|
struct siu_port *port_info = siu_port_info(substream); |
|
struct siu_stream *siu_stream; |
|
int self, ret; |
|
|
|
dev_dbg(substream->pcm->card->dev, |
|
"%s: port %d, active streams %lx, %d channels\n", |
|
__func__, info->port_id, port_info->play_cap, rt->channels); |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
|
self = PLAYBACK_ENABLED; |
|
siu_stream = &port_info->playback; |
|
} else { |
|
self = CAPTURE_ENABLED; |
|
siu_stream = &port_info->capture; |
|
} |
|
|
|
/* Set up the siu if not already done */ |
|
if (!port_info->play_cap) { |
|
siu_stream->rw_flg = 0; /* stream-data transfer flag */ |
|
|
|
siu_dai_spbAselect(port_info); |
|
siu_dai_spbBselect(port_info); |
|
|
|
siu_dai_open(siu_stream); |
|
|
|
siu_dai_pcmdatapack(siu_stream); |
|
|
|
ret = siu_dai_spbstart(port_info); |
|
if (ret < 0) |
|
goto fail; |
|
} else { |
|
ret = 0; |
|
} |
|
|
|
port_info->play_cap |= self; |
|
|
|
fail: |
|
return ret; |
|
} |
|
|
|
/* |
|
* SIU can set bus format to I2S / PCM / SPDIF independently for playback and |
|
* capture, however, the current API sets the bus format globally for a DAI. |
|
*/ |
|
static int siu_dai_set_fmt(struct snd_soc_dai *dai, |
|
unsigned int fmt) |
|
{ |
|
struct siu_info *info = snd_soc_dai_get_drvdata(dai); |
|
u32 __iomem *base = info->reg; |
|
u32 ifctl; |
|
|
|
dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n", |
|
__func__, fmt, info->port_id); |
|
|
|
if (info->port_id < 0) |
|
return -ENODEV; |
|
|
|
/* Here select between I2S / PCM / SPDIF */ |
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
|
case SND_SOC_DAIFMT_I2S: |
|
ifctl = siu_flags[info->port_id].playback.i2s | |
|
siu_flags[info->port_id].capture.i2s; |
|
break; |
|
case SND_SOC_DAIFMT_LEFT_J: |
|
ifctl = siu_flags[info->port_id].playback.pcm | |
|
siu_flags[info->port_id].capture.pcm; |
|
break; |
|
/* SPDIF disabled - see comment at the top */ |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
ifctl |= ~(siu_flags[info->port_id].playback.mask | |
|
siu_flags[info->port_id].capture.mask) & |
|
siu_read32(base + SIU_IFCTL); |
|
siu_write32(base + SIU_IFCTL, ifctl); |
|
|
|
return 0; |
|
} |
|
|
|
static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, |
|
unsigned int freq, int dir) |
|
{ |
|
struct clk *siu_clk, *parent_clk; |
|
char *siu_name, *parent_name; |
|
int ret; |
|
|
|
if (dir != SND_SOC_CLOCK_IN) |
|
return -EINVAL; |
|
|
|
dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id); |
|
|
|
switch (clk_id) { |
|
case SIU_CLKA_PLL: |
|
siu_name = "siua_clk"; |
|
parent_name = "pll_clk"; |
|
break; |
|
case SIU_CLKA_EXT: |
|
siu_name = "siua_clk"; |
|
parent_name = "siumcka_clk"; |
|
break; |
|
case SIU_CLKB_PLL: |
|
siu_name = "siub_clk"; |
|
parent_name = "pll_clk"; |
|
break; |
|
case SIU_CLKB_EXT: |
|
siu_name = "siub_clk"; |
|
parent_name = "siumckb_clk"; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
siu_clk = clk_get(dai->dev, siu_name); |
|
if (IS_ERR(siu_clk)) { |
|
dev_err(dai->dev, "%s: cannot get a SIU clock: %ld\n", __func__, |
|
PTR_ERR(siu_clk)); |
|
return PTR_ERR(siu_clk); |
|
} |
|
|
|
parent_clk = clk_get(dai->dev, parent_name); |
|
if (IS_ERR(parent_clk)) { |
|
ret = PTR_ERR(parent_clk); |
|
dev_err(dai->dev, "cannot get a SIU clock parent: %d\n", ret); |
|
goto epclkget; |
|
} |
|
|
|
ret = clk_set_parent(siu_clk, parent_clk); |
|
if (ret < 0) { |
|
dev_err(dai->dev, "cannot reparent the SIU clock: %d\n", ret); |
|
goto eclksetp; |
|
} |
|
|
|
ret = clk_set_rate(siu_clk, freq); |
|
if (ret < 0) |
|
dev_err(dai->dev, "cannot set SIU clock rate: %d\n", ret); |
|
|
|
/* TODO: when clkdev gets reference counting we'll move these to siu_dai_shutdown() */ |
|
eclksetp: |
|
clk_put(parent_clk); |
|
epclkget: |
|
clk_put(siu_clk); |
|
|
|
return ret; |
|
} |
|
|
|
static const struct snd_soc_dai_ops siu_dai_ops = { |
|
.startup = siu_dai_startup, |
|
.shutdown = siu_dai_shutdown, |
|
.prepare = siu_dai_prepare, |
|
.set_sysclk = siu_dai_set_sysclk, |
|
.set_fmt = siu_dai_set_fmt, |
|
}; |
|
|
|
static struct snd_soc_dai_driver siu_i2s_dai = { |
|
.name = "siu-i2s-dai", |
|
.playback = { |
|
.channels_min = 2, |
|
.channels_max = 2, |
|
.formats = SNDRV_PCM_FMTBIT_S16, |
|
.rates = SNDRV_PCM_RATE_8000_48000, |
|
}, |
|
.capture = { |
|
.channels_min = 2, |
|
.channels_max = 2, |
|
.formats = SNDRV_PCM_FMTBIT_S16, |
|
.rates = SNDRV_PCM_RATE_8000_48000, |
|
}, |
|
.ops = &siu_dai_ops, |
|
}; |
|
|
|
static int siu_probe(struct platform_device *pdev) |
|
{ |
|
const struct firmware *fw_entry; |
|
struct resource *res, *region; |
|
struct siu_info *info; |
|
int ret; |
|
|
|
info = devm_kmalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); |
|
if (!info) |
|
return -ENOMEM; |
|
siu_i2s_data = info; |
|
info->dev = &pdev->dev; |
|
|
|
ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); |
|
if (ret) |
|
return ret; |
|
|
|
/* |
|
* Loaded firmware is "const" - read only, but we have to modify it in |
|
* snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect() |
|
*/ |
|
memcpy(&info->fw, fw_entry->data, fw_entry->size); |
|
|
|
release_firmware(fw_entry); |
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|
if (!res) |
|
return -ENODEV; |
|
|
|
region = devm_request_mem_region(&pdev->dev, res->start, |
|
resource_size(res), pdev->name); |
|
if (!region) { |
|
dev_err(&pdev->dev, "SIU region already claimed\n"); |
|
return -EBUSY; |
|
} |
|
|
|
info->pram = devm_ioremap(&pdev->dev, res->start, PRAM_SIZE); |
|
if (!info->pram) |
|
return -ENOMEM; |
|
info->xram = devm_ioremap(&pdev->dev, res->start + XRAM_OFFSET, |
|
XRAM_SIZE); |
|
if (!info->xram) |
|
return -ENOMEM; |
|
info->yram = devm_ioremap(&pdev->dev, res->start + YRAM_OFFSET, |
|
YRAM_SIZE); |
|
if (!info->yram) |
|
return -ENOMEM; |
|
info->reg = devm_ioremap(&pdev->dev, res->start + REG_OFFSET, |
|
resource_size(res) - REG_OFFSET); |
|
if (!info->reg) |
|
return -ENOMEM; |
|
|
|
dev_set_drvdata(&pdev->dev, info); |
|
|
|
/* register using ARRAY version so we can keep dai name */ |
|
ret = devm_snd_soc_register_component(&pdev->dev, &siu_component, |
|
&siu_i2s_dai, 1); |
|
if (ret < 0) |
|
return ret; |
|
|
|
pm_runtime_enable(&pdev->dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int siu_remove(struct platform_device *pdev) |
|
{ |
|
pm_runtime_disable(&pdev->dev); |
|
return 0; |
|
} |
|
|
|
static struct platform_driver siu_driver = { |
|
.driver = { |
|
.name = "siu-pcm-audio", |
|
}, |
|
.probe = siu_probe, |
|
.remove = siu_remove, |
|
}; |
|
|
|
module_platform_driver(siu_driver); |
|
|
|
MODULE_AUTHOR("Carlos Munoz <[email protected]>"); |
|
MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver"); |
|
MODULE_LICENSE("GPL");
|
|
|