linux/sound/soc/img/img-parallel-out.c
<<
>>
Prefs
   1/*
   2 * IMG parallel output controller driver
   3 *
   4 * Copyright (C) 2015 Imagination Technologies Ltd.
   5 *
   6 * Author: Damien Horsley <Damien.Horsley@imgtec.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify it
   9 * under the terms and conditions of the GNU General Public License,
  10 * version 2, as published by the Free Software Foundation.
  11 */
  12
  13#include <linux/clk.h>
  14#include <linux/init.h>
  15#include <linux/kernel.h>
  16#include <linux/module.h>
  17#include <linux/of.h>
  18#include <linux/platform_device.h>
  19#include <linux/pm_runtime.h>
  20#include <linux/reset.h>
  21
  22#include <sound/core.h>
  23#include <sound/dmaengine_pcm.h>
  24#include <sound/initval.h>
  25#include <sound/pcm.h>
  26#include <sound/pcm_params.h>
  27#include <sound/soc.h>
  28
  29#define IMG_PRL_OUT_TX_FIFO             0
  30
  31#define IMG_PRL_OUT_CTL                 0x4
  32#define IMG_PRL_OUT_CTL_CH_MASK         BIT(4)
  33#define IMG_PRL_OUT_CTL_PACKH_MASK      BIT(3)
  34#define IMG_PRL_OUT_CTL_EDGE_MASK       BIT(2)
  35#define IMG_PRL_OUT_CTL_ME_MASK         BIT(1)
  36#define IMG_PRL_OUT_CTL_SRST_MASK       BIT(0)
  37
  38struct img_prl_out {
  39        void __iomem *base;
  40        struct clk *clk_sys;
  41        struct clk *clk_ref;
  42        struct snd_dmaengine_dai_dma_data dma_data;
  43        struct device *dev;
  44        struct reset_control *rst;
  45};
  46
  47static int img_prl_out_suspend(struct device *dev)
  48{
  49        struct img_prl_out *prl = dev_get_drvdata(dev);
  50
  51        clk_disable_unprepare(prl->clk_ref);
  52
  53        return 0;
  54}
  55
  56static int img_prl_out_resume(struct device *dev)
  57{
  58        struct img_prl_out *prl = dev_get_drvdata(dev);
  59        int ret;
  60
  61        ret = clk_prepare_enable(prl->clk_ref);
  62        if (ret) {
  63                dev_err(dev, "clk_enable failed: %d\n", ret);
  64                return ret;
  65        }
  66
  67        return 0;
  68}
  69
  70static inline void img_prl_out_writel(struct img_prl_out *prl,
  71                                u32 val, u32 reg)
  72{
  73        writel(val, prl->base + reg);
  74}
  75
  76static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg)
  77{
  78        return readl(prl->base + reg);
  79}
  80
  81static void img_prl_out_reset(struct img_prl_out *prl)
  82{
  83        u32 ctl;
  84
  85        ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) &
  86                        ~IMG_PRL_OUT_CTL_ME_MASK;
  87
  88        reset_control_assert(prl->rst);
  89        reset_control_deassert(prl->rst);
  90
  91        img_prl_out_writel(prl, ctl, IMG_PRL_OUT_CTL);
  92}
  93
  94static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd,
  95                        struct snd_soc_dai *dai)
  96{
  97        struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
  98        u32 reg;
  99
 100        switch (cmd) {
 101        case SNDRV_PCM_TRIGGER_START:
 102        case SNDRV_PCM_TRIGGER_RESUME:
 103        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 104                reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
 105                reg |= IMG_PRL_OUT_CTL_ME_MASK;
 106                img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
 107                break;
 108        case SNDRV_PCM_TRIGGER_STOP:
 109        case SNDRV_PCM_TRIGGER_SUSPEND:
 110        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 111                img_prl_out_reset(prl);
 112                break;
 113        default:
 114                return -EINVAL;
 115        }
 116
 117        return 0;
 118}
 119
 120static int img_prl_out_hw_params(struct snd_pcm_substream *substream,
 121        struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
 122{
 123        struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
 124        unsigned int rate, channels;
 125        u32 reg, control_set = 0;
 126
 127        rate = params_rate(params);
 128        channels = params_channels(params);
 129
 130        switch (params_format(params)) {
 131        case SNDRV_PCM_FORMAT_S32_LE:
 132                control_set |= IMG_PRL_OUT_CTL_PACKH_MASK;
 133                break;
 134        case SNDRV_PCM_FORMAT_S24_LE:
 135                break;
 136        default:
 137                return -EINVAL;
 138        }
 139
 140        if (channels != 2)
 141                return -EINVAL;
 142
 143        clk_set_rate(prl->clk_ref, rate * 256);
 144
 145        reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
 146        reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set;
 147        img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
 148
 149        return 0;
 150}
 151
 152static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 153{
 154        struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
 155        u32 reg, control_set = 0;
 156        int ret;
 157
 158        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 159        case SND_SOC_DAIFMT_NB_NF:
 160                break;
 161        case SND_SOC_DAIFMT_NB_IF:
 162                control_set |= IMG_PRL_OUT_CTL_EDGE_MASK;
 163                break;
 164        default:
 165                return -EINVAL;
 166        }
 167
 168        ret = pm_runtime_get_sync(prl->dev);
 169        if (ret < 0)
 170                return ret;
 171
 172        reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL);
 173        reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set;
 174        img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL);
 175        pm_runtime_put(prl->dev);
 176
 177        return 0;
 178}
 179
 180static const struct snd_soc_dai_ops img_prl_out_dai_ops = {
 181        .trigger = img_prl_out_trigger,
 182        .hw_params = img_prl_out_hw_params,
 183        .set_fmt = img_prl_out_set_fmt
 184};
 185
 186static int img_prl_out_dai_probe(struct snd_soc_dai *dai)
 187{
 188        struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai);
 189
 190        snd_soc_dai_init_dma_data(dai, &prl->dma_data, NULL);
 191
 192        return 0;
 193}
 194
 195static struct snd_soc_dai_driver img_prl_out_dai = {
 196        .probe = img_prl_out_dai_probe,
 197        .playback = {
 198                .channels_min = 2,
 199                .channels_max = 2,
 200                .rates = SNDRV_PCM_RATE_8000_192000,
 201                .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE
 202        },
 203        .ops = &img_prl_out_dai_ops
 204};
 205
 206static const struct snd_soc_component_driver img_prl_out_component = {
 207        .name = "img-prl-out"
 208};
 209
 210static int img_prl_out_probe(struct platform_device *pdev)
 211{
 212        struct img_prl_out *prl;
 213        struct resource *res;
 214        void __iomem *base;
 215        int ret;
 216        struct device *dev = &pdev->dev;
 217
 218        prl = devm_kzalloc(&pdev->dev, sizeof(*prl), GFP_KERNEL);
 219        if (!prl)
 220                return -ENOMEM;
 221
 222        platform_set_drvdata(pdev, prl);
 223
 224        prl->dev = &pdev->dev;
 225
 226        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 227        base = devm_ioremap_resource(&pdev->dev, res);
 228        if (IS_ERR(base))
 229                return PTR_ERR(base);
 230
 231        prl->base = base;
 232
 233        prl->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst");
 234        if (IS_ERR(prl->rst)) {
 235                if (PTR_ERR(prl->rst) != -EPROBE_DEFER)
 236                        dev_err(&pdev->dev, "No top level reset found\n");
 237                return PTR_ERR(prl->rst);
 238        }
 239
 240        prl->clk_sys = devm_clk_get(&pdev->dev, "sys");
 241        if (IS_ERR(prl->clk_sys)) {
 242                if (PTR_ERR(prl->clk_sys) != -EPROBE_DEFER)
 243                        dev_err(dev, "Failed to acquire clock 'sys'\n");
 244                return PTR_ERR(prl->clk_sys);
 245        }
 246
 247        prl->clk_ref = devm_clk_get(&pdev->dev, "ref");
 248        if (IS_ERR(prl->clk_ref)) {
 249                if (PTR_ERR(prl->clk_ref) != -EPROBE_DEFER)
 250                        dev_err(dev, "Failed to acquire clock 'ref'\n");
 251                return PTR_ERR(prl->clk_ref);
 252        }
 253
 254        ret = clk_prepare_enable(prl->clk_sys);
 255        if (ret)
 256                return ret;
 257
 258        img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL);
 259        img_prl_out_reset(prl);
 260
 261        pm_runtime_enable(&pdev->dev);
 262        if (!pm_runtime_enabled(&pdev->dev)) {
 263                ret = img_prl_out_resume(&pdev->dev);
 264                if (ret)
 265                        goto err_pm_disable;
 266        }
 267
 268        prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO;
 269        prl->dma_data.addr_width = 4;
 270        prl->dma_data.maxburst = 4;
 271
 272        ret = devm_snd_soc_register_component(&pdev->dev,
 273                        &img_prl_out_component,
 274                        &img_prl_out_dai, 1);
 275        if (ret)
 276                goto err_suspend;
 277
 278        ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
 279        if (ret)
 280                goto err_suspend;
 281
 282        return 0;
 283
 284err_suspend:
 285        if (!pm_runtime_status_suspended(&pdev->dev))
 286                img_prl_out_suspend(&pdev->dev);
 287err_pm_disable:
 288        pm_runtime_disable(&pdev->dev);
 289        clk_disable_unprepare(prl->clk_sys);
 290
 291        return ret;
 292}
 293
 294static int img_prl_out_dev_remove(struct platform_device *pdev)
 295{
 296        struct img_prl_out *prl = platform_get_drvdata(pdev);
 297
 298        pm_runtime_disable(&pdev->dev);
 299        if (!pm_runtime_status_suspended(&pdev->dev))
 300                img_prl_out_suspend(&pdev->dev);
 301
 302        clk_disable_unprepare(prl->clk_sys);
 303
 304        return 0;
 305}
 306
 307static const struct of_device_id img_prl_out_of_match[] = {
 308        { .compatible = "img,parallel-out" },
 309        {}
 310};
 311MODULE_DEVICE_TABLE(of, img_prl_out_of_match);
 312
 313static const struct dev_pm_ops img_prl_out_pm_ops = {
 314        SET_RUNTIME_PM_OPS(img_prl_out_suspend,
 315                           img_prl_out_resume, NULL)
 316};
 317
 318static struct platform_driver img_prl_out_driver = {
 319        .driver = {
 320                .name = "img-parallel-out",
 321                .of_match_table = img_prl_out_of_match,
 322                .pm = &img_prl_out_pm_ops
 323        },
 324        .probe = img_prl_out_probe,
 325        .remove = img_prl_out_dev_remove
 326};
 327module_platform_driver(img_prl_out_driver);
 328
 329MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
 330MODULE_DESCRIPTION("IMG Parallel Output Driver");
 331MODULE_LICENSE("GPL v2");
 332