linux/sound/soc/img/img-spdif-out.c
<<
>>
Prefs
   1/*
   2 * IMG SPDIF 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_SPDIF_OUT_TX_FIFO           0x0
  30
  31#define IMG_SPDIF_OUT_CTL               0x4
  32#define IMG_SPDIF_OUT_CTL_FS_MASK       BIT(4)
  33#define IMG_SPDIF_OUT_CTL_CLK_MASK      BIT(2)
  34#define IMG_SPDIF_OUT_CTL_SRT_MASK      BIT(0)
  35
  36#define IMG_SPDIF_OUT_CSL               0x14
  37
  38#define IMG_SPDIF_OUT_CSH_UV            0x18
  39#define IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT  0
  40#define IMG_SPDIF_OUT_CSH_UV_CSH_MASK   0xff
  41
  42struct img_spdif_out {
  43        spinlock_t lock;
  44        void __iomem *base;
  45        struct clk *clk_sys;
  46        struct clk *clk_ref;
  47        struct snd_dmaengine_dai_dma_data dma_data;
  48        struct device *dev;
  49        struct reset_control *rst;
  50};
  51
  52static int img_spdif_out_suspend(struct device *dev)
  53{
  54        struct img_spdif_out *spdif = dev_get_drvdata(dev);
  55
  56        clk_disable_unprepare(spdif->clk_ref);
  57
  58        return 0;
  59}
  60
  61static int img_spdif_out_resume(struct device *dev)
  62{
  63        struct img_spdif_out *spdif = dev_get_drvdata(dev);
  64        int ret;
  65
  66        ret = clk_prepare_enable(spdif->clk_ref);
  67        if (ret) {
  68                dev_err(dev, "clk_enable failed: %d\n", ret);
  69                return ret;
  70        }
  71
  72        return 0;
  73}
  74
  75static inline void img_spdif_out_writel(struct img_spdif_out *spdif, u32 val,
  76                                u32 reg)
  77{
  78        writel(val, spdif->base + reg);
  79}
  80
  81static inline u32 img_spdif_out_readl(struct img_spdif_out *spdif, u32 reg)
  82{
  83        return readl(spdif->base + reg);
  84}
  85
  86static void img_spdif_out_reset(struct img_spdif_out *spdif)
  87{
  88        u32 ctl, status_low, status_high;
  89
  90        ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL) &
  91                        ~IMG_SPDIF_OUT_CTL_SRT_MASK;
  92        status_low = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL);
  93        status_high = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
  94
  95        reset_control_assert(spdif->rst);
  96        reset_control_deassert(spdif->rst);
  97
  98        img_spdif_out_writel(spdif, ctl, IMG_SPDIF_OUT_CTL);
  99        img_spdif_out_writel(spdif, status_low, IMG_SPDIF_OUT_CSL);
 100        img_spdif_out_writel(spdif, status_high, IMG_SPDIF_OUT_CSH_UV);
 101}
 102
 103static int img_spdif_out_info(struct snd_kcontrol *kcontrol,
 104                                        struct snd_ctl_elem_info *uinfo)
 105{
 106        uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
 107        uinfo->count = 1;
 108
 109        return 0;
 110}
 111
 112static int img_spdif_out_get_status_mask(struct snd_kcontrol *kcontrol,
 113                                       struct snd_ctl_elem_value *ucontrol)
 114{
 115        ucontrol->value.iec958.status[0] = 0xff;
 116        ucontrol->value.iec958.status[1] = 0xff;
 117        ucontrol->value.iec958.status[2] = 0xff;
 118        ucontrol->value.iec958.status[3] = 0xff;
 119        ucontrol->value.iec958.status[4] = 0xff;
 120
 121        return 0;
 122}
 123
 124static int img_spdif_out_get_status(struct snd_kcontrol *kcontrol,
 125                                  struct snd_ctl_elem_value *ucontrol)
 126{
 127        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 128        struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai);
 129        u32 reg;
 130        unsigned long flags;
 131
 132        spin_lock_irqsave(&spdif->lock, flags);
 133
 134        reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL);
 135        ucontrol->value.iec958.status[0] = reg & 0xff;
 136        ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff;
 137        ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff;
 138        ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff;
 139
 140        reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
 141        ucontrol->value.iec958.status[4] =
 142                (reg & IMG_SPDIF_OUT_CSH_UV_CSH_MASK) >>
 143                IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT;
 144
 145        spin_unlock_irqrestore(&spdif->lock, flags);
 146
 147        return 0;
 148}
 149
 150static int img_spdif_out_set_status(struct snd_kcontrol *kcontrol,
 151                                  struct snd_ctl_elem_value *ucontrol)
 152{
 153        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 154        struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai);
 155        u32 reg;
 156        unsigned long flags;
 157
 158        reg = ((u32)ucontrol->value.iec958.status[3] << 24);
 159        reg |= ((u32)ucontrol->value.iec958.status[2] << 16);
 160        reg |= ((u32)ucontrol->value.iec958.status[1] << 8);
 161        reg |= (u32)ucontrol->value.iec958.status[0];
 162
 163        spin_lock_irqsave(&spdif->lock, flags);
 164
 165        img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSL);
 166
 167        reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV);
 168        reg &= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK;
 169        reg |= (u32)ucontrol->value.iec958.status[4] <<
 170                        IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT;
 171        img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSH_UV);
 172
 173        spin_unlock_irqrestore(&spdif->lock, flags);
 174
 175        return 0;
 176}
 177
 178static struct snd_kcontrol_new img_spdif_out_controls[] = {
 179        {
 180                .access = SNDRV_CTL_ELEM_ACCESS_READ,
 181                .iface = SNDRV_CTL_ELEM_IFACE_PCM,
 182                .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
 183                .info = img_spdif_out_info,
 184                .get = img_spdif_out_get_status_mask
 185        },
 186        {
 187                .iface = SNDRV_CTL_ELEM_IFACE_PCM,
 188                .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
 189                .info = img_spdif_out_info,
 190                .get = img_spdif_out_get_status,
 191                .put = img_spdif_out_set_status
 192        }
 193};
 194
 195static int img_spdif_out_trigger(struct snd_pcm_substream *substream, int cmd,
 196                        struct snd_soc_dai *dai)
 197{
 198        struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
 199        u32 reg;
 200        unsigned long flags;
 201
 202        switch (cmd) {
 203        case SNDRV_PCM_TRIGGER_START:
 204        case SNDRV_PCM_TRIGGER_RESUME:
 205        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 206                reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL);
 207                reg |= IMG_SPDIF_OUT_CTL_SRT_MASK;
 208                img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL);
 209                break;
 210        case SNDRV_PCM_TRIGGER_STOP:
 211        case SNDRV_PCM_TRIGGER_SUSPEND:
 212        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 213                spin_lock_irqsave(&spdif->lock, flags);
 214                img_spdif_out_reset(spdif);
 215                spin_unlock_irqrestore(&spdif->lock, flags);
 216                break;
 217        default:
 218                return -EINVAL;
 219        }
 220
 221        return 0;
 222}
 223
 224static int img_spdif_out_hw_params(struct snd_pcm_substream *substream,
 225        struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
 226{
 227        struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
 228        unsigned int channels;
 229        long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate;
 230        u32 reg;
 231        snd_pcm_format_t format;
 232
 233        rate = params_rate(params);
 234        format = params_format(params);
 235        channels = params_channels(params);
 236
 237        dev_dbg(spdif->dev, "hw_params rate %ld channels %u format %u\n",
 238                        rate, channels, format);
 239
 240        if (format != SNDRV_PCM_FORMAT_S32_LE)
 241                return -EINVAL;
 242
 243        if (channels != 2)
 244                return -EINVAL;
 245
 246        pre_div_a = clk_round_rate(spdif->clk_ref, rate * 256);
 247        if (pre_div_a < 0)
 248                return pre_div_a;
 249        pre_div_b = clk_round_rate(spdif->clk_ref, rate * 384);
 250        if (pre_div_b < 0)
 251                return pre_div_b;
 252
 253        diff_a = abs((pre_div_a / 256) - rate);
 254        diff_b = abs((pre_div_b / 384) - rate);
 255
 256        /* If diffs are equal, use lower clock rate */
 257        if (diff_a > diff_b)
 258                clk_set_rate(spdif->clk_ref, pre_div_b);
 259        else
 260                clk_set_rate(spdif->clk_ref, pre_div_a);
 261
 262        /*
 263         * Another driver (eg machine driver) may have rejected the above
 264         * change. Get the current rate and set the register bit according to
 265         * the new min diff
 266         */
 267        clk_rate = clk_get_rate(spdif->clk_ref);
 268
 269        diff_a = abs((clk_rate / 256) - rate);
 270        diff_b = abs((clk_rate / 384) - rate);
 271
 272        reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL);
 273        if (diff_a <= diff_b)
 274                reg &= ~IMG_SPDIF_OUT_CTL_CLK_MASK;
 275        else
 276                reg |= IMG_SPDIF_OUT_CTL_CLK_MASK;
 277        img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL);
 278
 279        return 0;
 280}
 281
 282static const struct snd_soc_dai_ops img_spdif_out_dai_ops = {
 283        .trigger = img_spdif_out_trigger,
 284        .hw_params = img_spdif_out_hw_params
 285};
 286
 287static int img_spdif_out_dai_probe(struct snd_soc_dai *dai)
 288{
 289        struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai);
 290
 291        snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
 292
 293        snd_soc_add_dai_controls(dai, img_spdif_out_controls,
 294                        ARRAY_SIZE(img_spdif_out_controls));
 295
 296        return 0;
 297}
 298
 299static struct snd_soc_dai_driver img_spdif_out_dai = {
 300        .probe = img_spdif_out_dai_probe,
 301        .playback = {
 302                .channels_min = 2,
 303                .channels_max = 2,
 304                .rates = SNDRV_PCM_RATE_8000_192000,
 305                .formats = SNDRV_PCM_FMTBIT_S32_LE
 306        },
 307        .ops = &img_spdif_out_dai_ops
 308};
 309
 310static const struct snd_soc_component_driver img_spdif_out_component = {
 311        .name = "img-spdif-out"
 312};
 313
 314static int img_spdif_out_probe(struct platform_device *pdev)
 315{
 316        struct img_spdif_out *spdif;
 317        struct resource *res;
 318        void __iomem *base;
 319        int ret;
 320        struct device *dev = &pdev->dev;
 321
 322        spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
 323        if (!spdif)
 324                return -ENOMEM;
 325
 326        platform_set_drvdata(pdev, spdif);
 327
 328        spdif->dev = &pdev->dev;
 329
 330        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 331        base = devm_ioremap_resource(&pdev->dev, res);
 332        if (IS_ERR(base))
 333                return PTR_ERR(base);
 334
 335        spdif->base = base;
 336
 337        spdif->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst");
 338        if (IS_ERR(spdif->rst)) {
 339                if (PTR_ERR(spdif->rst) != -EPROBE_DEFER)
 340                        dev_err(&pdev->dev, "No top level reset found\n");
 341                return PTR_ERR(spdif->rst);
 342        }
 343
 344        spdif->clk_sys = devm_clk_get(&pdev->dev, "sys");
 345        if (IS_ERR(spdif->clk_sys)) {
 346                if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER)
 347                        dev_err(dev, "Failed to acquire clock 'sys'\n");
 348                return PTR_ERR(spdif->clk_sys);
 349        }
 350
 351        spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
 352        if (IS_ERR(spdif->clk_ref)) {
 353                if (PTR_ERR(spdif->clk_ref) != -EPROBE_DEFER)
 354                        dev_err(dev, "Failed to acquire clock 'ref'\n");
 355                return PTR_ERR(spdif->clk_ref);
 356        }
 357
 358        ret = clk_prepare_enable(spdif->clk_sys);
 359        if (ret)
 360                return ret;
 361
 362        img_spdif_out_writel(spdif, IMG_SPDIF_OUT_CTL_FS_MASK,
 363                                IMG_SPDIF_OUT_CTL);
 364
 365        img_spdif_out_reset(spdif);
 366
 367        pm_runtime_enable(&pdev->dev);
 368        if (!pm_runtime_enabled(&pdev->dev)) {
 369                ret = img_spdif_out_resume(&pdev->dev);
 370                if (ret)
 371                        goto err_pm_disable;
 372        }
 373
 374        spin_lock_init(&spdif->lock);
 375
 376        spdif->dma_data.addr = res->start + IMG_SPDIF_OUT_TX_FIFO;
 377        spdif->dma_data.addr_width = 4;
 378        spdif->dma_data.maxburst = 4;
 379
 380        ret = devm_snd_soc_register_component(&pdev->dev,
 381                        &img_spdif_out_component,
 382                        &img_spdif_out_dai, 1);
 383        if (ret)
 384                goto err_suspend;
 385
 386        ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
 387        if (ret)
 388                goto err_suspend;
 389
 390        dev_dbg(&pdev->dev, "Probe successful\n");
 391
 392        return 0;
 393
 394err_suspend:
 395        if (!pm_runtime_status_suspended(&pdev->dev))
 396                img_spdif_out_suspend(&pdev->dev);
 397err_pm_disable:
 398        pm_runtime_disable(&pdev->dev);
 399        clk_disable_unprepare(spdif->clk_sys);
 400
 401        return ret;
 402}
 403
 404static int img_spdif_out_dev_remove(struct platform_device *pdev)
 405{
 406        struct img_spdif_out *spdif = platform_get_drvdata(pdev);
 407
 408        pm_runtime_disable(&pdev->dev);
 409        if (!pm_runtime_status_suspended(&pdev->dev))
 410                img_spdif_out_suspend(&pdev->dev);
 411
 412        clk_disable_unprepare(spdif->clk_sys);
 413
 414        return 0;
 415}
 416
 417static const struct of_device_id img_spdif_out_of_match[] = {
 418        { .compatible = "img,spdif-out" },
 419        {}
 420};
 421MODULE_DEVICE_TABLE(of, img_spdif_out_of_match);
 422
 423static const struct dev_pm_ops img_spdif_out_pm_ops = {
 424        SET_RUNTIME_PM_OPS(img_spdif_out_suspend,
 425                           img_spdif_out_resume, NULL)
 426};
 427
 428static struct platform_driver img_spdif_out_driver = {
 429        .driver = {
 430                .name = "img-spdif-out",
 431                .of_match_table = img_spdif_out_of_match,
 432                .pm = &img_spdif_out_pm_ops
 433        },
 434        .probe = img_spdif_out_probe,
 435        .remove = img_spdif_out_dev_remove
 436};
 437module_platform_driver(img_spdif_out_driver);
 438
 439MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
 440MODULE_DESCRIPTION("IMG SPDIF Output driver");
 441MODULE_LICENSE("GPL v2");
 442