diff options
author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2010-01-22 19:09:03 +0100 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2010-01-25 14:52:20 +0000 |
commit | 895d4509d069f0706427ca75fcf0929ed136d0d7 (patch) | |
tree | 9eadaf136a8903d9279c3967b62efbefe5248366 /sound/soc/sh/siu_dai.c | |
parent | 6c2fb6a8d8c43544e7665859f29373c98d17df75 (diff) | |
download | talos-op-linux-895d4509d069f0706427ca75fcf0929ed136d0d7.tar.gz talos-op-linux-895d4509d069f0706427ca75fcf0929ed136d0d7.zip |
ASoC: add DAI and platform / DMA drivers for SH SIU
Several SuperH platforms, including sh7722, sh7343, sh7354, sh7367 include
a Sound Interface Unit (SIU). This patch adds DAI and platform / DMA
drivers for this interface.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/sh/siu_dai.c')
-rw-r--r-- | sound/soc/sh/siu_dai.c | 847 |
1 files changed, 847 insertions, 0 deletions
diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c new file mode 100644 index 000000000000..5452d19607e1 --- /dev/null +++ b/sound/soc/sh/siu_dai.c @@ -0,0 +1,847 @@ +/* + * siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> + +#include <asm/clock.h> +#include <asm/siu.h> + +#include <sound/control.h> +#include <sound/soc-dai.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; +}; + +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_dai.private_data; + u32 __iomem *base = info->reg; + + dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); + + /* Turn on SIU clock */ + pm_runtime_get_sync(siu_i2s_dai.dev); + + /* 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(void) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + + /* SIU software reset */ + siu_write32(base + SIU_SRCTL, 0); + + /* Turn off SIU clock */ + pm_runtime_put_sync(siu_i2s_dai.dev); +} + +static void siu_dai_spbAselect(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_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_dai.private_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_dai.private_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_dai.private_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_dai.private_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_dai.private_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 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_dai.private_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 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 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 = siu_i2s_dai.private_data; + 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 = siu_i2s_dai.private_data; + 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 ? */ + BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg); + siu_dai_spbstop(port_info); + siu_dai_stop(); + } +} + +/* 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 = siu_i2s_dai.private_data; + 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; + } + + 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 = siu_i2s_dai.private_data; + 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(siu_i2s_dai.dev, siu_name); + if (IS_ERR(siu_clk)) + return PTR_ERR(siu_clk); + + parent_clk = clk_get(siu_i2s_dai.dev, parent_name); + if (!IS_ERR(parent_clk)) { + ret = clk_set_parent(siu_clk, parent_clk); + if (!ret) + clk_set_rate(siu_clk, freq); + clk_put(parent_clk); + } + + clk_put(siu_clk); + + return 0; +} + +static 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, +}; + +struct snd_soc_dai siu_i2s_dai = { + .name = "sh-siu", + .id = 0, + .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, +}; +EXPORT_SYMBOL_GPL(siu_i2s_dai); + +static int __devinit siu_probe(struct platform_device *pdev) +{ + const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + goto ereqfw; + + /* + * 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) { + ret = -ENODEV; + goto egetres; + } + + region = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!region) { + dev_err(&pdev->dev, "SIU region already claimed\n"); + ret = -EBUSY; + goto ereqmemreg; + } + + ret = -ENOMEM; + info->pram = ioremap(res->start, PRAM_SIZE); + if (!info->pram) + goto emappram; + info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE); + if (!info->xram) + goto emapxram; + info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE); + if (!info->yram) + goto emapyram; + info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) - + REG_OFFSET); + if (!info->reg) + goto emapreg; + + siu_i2s_dai.dev = &pdev->dev; + siu_i2s_dai.private_data = info; + + ret = snd_soc_register_dais(&siu_i2s_dai, 1); + if (ret < 0) + goto edaiinit; + + ret = snd_soc_register_platform(&siu_platform); + if (ret < 0) + goto esocregp; + + pm_runtime_enable(&pdev->dev); + + return ret; + +esocregp: + snd_soc_unregister_dais(&siu_i2s_dai, 1); +edaiinit: + iounmap(info->reg); +emapreg: + iounmap(info->yram); +emapyram: + iounmap(info->xram); +emapxram: + iounmap(info->pram); +emappram: + release_mem_region(res->start, resource_size(res)); +ereqmemreg: +egetres: +ereqfw: + kfree(info); + + return ret; +} + +static int __devexit siu_remove(struct platform_device *pdev) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct resource *res; + + pm_runtime_disable(&pdev->dev); + + snd_soc_unregister_platform(&siu_platform); + snd_soc_unregister_dais(&siu_i2s_dai, 1); + + iounmap(info->reg); + iounmap(info->yram); + iounmap(info->xram); + iounmap(info->pram); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + kfree(info); + + return 0; +} + +static struct platform_driver siu_driver = { + .driver = { + .name = "sh_siu", + }, + .probe = siu_probe, + .remove = __devexit_p(siu_remove), +}; + +static int __init siu_init(void) +{ + return platform_driver_register(&siu_driver); +} + +static void __exit siu_exit(void) +{ + platform_driver_unregister(&siu_driver); +} + +module_init(siu_init) +module_exit(siu_exit) + +MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>"); +MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver"); +MODULE_LICENSE("GPL"); |