root/sound/soc/renesas/siu_dai.c
// SPDX-License-Identifier: GPL-2.0+
//
// 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>

#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_obj(**port_info);
        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 void siu_remove(struct platform_device *pdev)
{
        pm_runtime_disable(&pdev->dev);
}

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 <carlos@kenati.com>");
MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver");
MODULE_LICENSE("GPL");

MODULE_FIRMWARE("siu_spb.bin");