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