linux/sound/soc/ti/omap-mcbsp-st.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * McBSP Sidetone support
   4 *
   5 * Copyright (C) 2004 Nokia Corporation
   6 * Author: Samuel Ortiz <samuel.ortiz@nokia.com>
   7 *
   8 * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
   9 *          Peter Ujfalusi <peter.ujfalusi@ti.com>
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/init.h>
  14#include <linux/device.h>
  15#include <linux/platform_device.h>
  16#include <linux/interrupt.h>
  17#include <linux/err.h>
  18#include <linux/clk.h>
  19#include <linux/delay.h>
  20#include <linux/io.h>
  21#include <linux/slab.h>
  22#include <linux/pm_runtime.h>
  23
  24#include "omap-mcbsp.h"
  25#include "omap-mcbsp-priv.h"
  26
  27/* OMAP3 sidetone control registers */
  28#define OMAP_ST_REG_REV         0x00
  29#define OMAP_ST_REG_SYSCONFIG   0x10
  30#define OMAP_ST_REG_IRQSTATUS   0x18
  31#define OMAP_ST_REG_IRQENABLE   0x1C
  32#define OMAP_ST_REG_SGAINCR     0x24
  33#define OMAP_ST_REG_SFIRCR      0x28
  34#define OMAP_ST_REG_SSELCR      0x2C
  35
  36/********************** McBSP SSELCR bit definitions ***********************/
  37#define SIDETONEEN              BIT(10)
  38
  39/********************** McBSP Sidetone SYSCONFIG bit definitions ***********/
  40#define ST_AUTOIDLE             BIT(0)
  41
  42/********************** McBSP Sidetone SGAINCR bit definitions *************/
  43#define ST_CH0GAIN(value)       ((value) & 0xffff)      /* Bits 0:15 */
  44#define ST_CH1GAIN(value)       (((value) & 0xffff) << 16) /* Bits 16:31 */
  45
  46/********************** McBSP Sidetone SFIRCR bit definitions **************/
  47#define ST_FIRCOEFF(value)      ((value) & 0xffff)      /* Bits 0:15 */
  48
  49/********************** McBSP Sidetone SSELCR bit definitions **************/
  50#define ST_SIDETONEEN           BIT(0)
  51#define ST_COEFFWREN            BIT(1)
  52#define ST_COEFFWRDONE          BIT(2)
  53
  54struct omap_mcbsp_st_data {
  55        void __iomem *io_base_st;
  56        struct clk *mcbsp_iclk;
  57        bool running;
  58        bool enabled;
  59        s16 taps[128];  /* Sidetone filter coefficients */
  60        int nr_taps;    /* Number of filter coefficients in use */
  61        s16 ch0gain;
  62        s16 ch1gain;
  63};
  64
  65static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val)
  66{
  67        writel_relaxed(val, mcbsp->st_data->io_base_st + reg);
  68}
  69
  70static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg)
  71{
  72        return readl_relaxed(mcbsp->st_data->io_base_st + reg);
  73}
  74
  75#define MCBSP_ST_READ(mcbsp, reg) omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg)
  76#define MCBSP_ST_WRITE(mcbsp, reg, val) \
  77                        omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val)
  78
  79static void omap_mcbsp_st_on(struct omap_mcbsp *mcbsp)
  80{
  81        unsigned int w;
  82
  83        if (mcbsp->pdata->force_ick_on)
  84                mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, true);
  85
  86        /* Disable Sidetone clock auto-gating for normal operation */
  87        w = MCBSP_ST_READ(mcbsp, SYSCONFIG);
  88        MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE));
  89
  90        /* Enable McBSP Sidetone */
  91        w = MCBSP_READ(mcbsp, SSELCR);
  92        MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN);
  93
  94        /* Enable Sidetone from Sidetone Core */
  95        w = MCBSP_ST_READ(mcbsp, SSELCR);
  96        MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN);
  97}
  98
  99static void omap_mcbsp_st_off(struct omap_mcbsp *mcbsp)
 100{
 101        unsigned int w;
 102
 103        w = MCBSP_ST_READ(mcbsp, SSELCR);
 104        MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN));
 105
 106        w = MCBSP_READ(mcbsp, SSELCR);
 107        MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN));
 108
 109        /* Enable Sidetone clock auto-gating to reduce power consumption */
 110        w = MCBSP_ST_READ(mcbsp, SYSCONFIG);
 111        MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE);
 112
 113        if (mcbsp->pdata->force_ick_on)
 114                mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, false);
 115}
 116
 117static void omap_mcbsp_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir)
 118{
 119        u16 val, i;
 120
 121        val = MCBSP_ST_READ(mcbsp, SSELCR);
 122
 123        if (val & ST_COEFFWREN)
 124                MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN));
 125
 126        MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN);
 127
 128        for (i = 0; i < 128; i++)
 129                MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]);
 130
 131        i = 0;
 132
 133        val = MCBSP_ST_READ(mcbsp, SSELCR);
 134        while (!(val & ST_COEFFWRDONE) && (++i < 1000))
 135                val = MCBSP_ST_READ(mcbsp, SSELCR);
 136
 137        MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN));
 138
 139        if (i == 1000)
 140                dev_err(mcbsp->dev, "McBSP FIR load error!\n");
 141}
 142
 143static void omap_mcbsp_st_chgain(struct omap_mcbsp *mcbsp)
 144{
 145        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 146
 147        MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) |
 148                       ST_CH1GAIN(st_data->ch1gain));
 149}
 150
 151static int omap_mcbsp_st_set_chgain(struct omap_mcbsp *mcbsp, int channel,
 152                                    s16 chgain)
 153{
 154        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 155        int ret = 0;
 156
 157        if (!st_data)
 158                return -ENOENT;
 159
 160        spin_lock_irq(&mcbsp->lock);
 161        if (channel == 0)
 162                st_data->ch0gain = chgain;
 163        else if (channel == 1)
 164                st_data->ch1gain = chgain;
 165        else
 166                ret = -EINVAL;
 167
 168        if (st_data->enabled)
 169                omap_mcbsp_st_chgain(mcbsp);
 170        spin_unlock_irq(&mcbsp->lock);
 171
 172        return ret;
 173}
 174
 175static int omap_mcbsp_st_get_chgain(struct omap_mcbsp *mcbsp, int channel,
 176                                    s16 *chgain)
 177{
 178        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 179        int ret = 0;
 180
 181        if (!st_data)
 182                return -ENOENT;
 183
 184        spin_lock_irq(&mcbsp->lock);
 185        if (channel == 0)
 186                *chgain = st_data->ch0gain;
 187        else if (channel == 1)
 188                *chgain = st_data->ch1gain;
 189        else
 190                ret = -EINVAL;
 191        spin_unlock_irq(&mcbsp->lock);
 192
 193        return ret;
 194}
 195
 196static int omap_mcbsp_st_enable(struct omap_mcbsp *mcbsp)
 197{
 198        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 199
 200        if (!st_data)
 201                return -ENODEV;
 202
 203        spin_lock_irq(&mcbsp->lock);
 204        st_data->enabled = 1;
 205        omap_mcbsp_st_start(mcbsp);
 206        spin_unlock_irq(&mcbsp->lock);
 207
 208        return 0;
 209}
 210
 211static int omap_mcbsp_st_disable(struct omap_mcbsp *mcbsp)
 212{
 213        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 214        int ret = 0;
 215
 216        if (!st_data)
 217                return -ENODEV;
 218
 219        spin_lock_irq(&mcbsp->lock);
 220        omap_mcbsp_st_stop(mcbsp);
 221        st_data->enabled = 0;
 222        spin_unlock_irq(&mcbsp->lock);
 223
 224        return ret;
 225}
 226
 227static int omap_mcbsp_st_is_enabled(struct omap_mcbsp *mcbsp)
 228{
 229        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 230
 231        if (!st_data)
 232                return -ENODEV;
 233
 234        return st_data->enabled;
 235}
 236
 237static ssize_t st_taps_show(struct device *dev,
 238                            struct device_attribute *attr, char *buf)
 239{
 240        struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
 241        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 242        ssize_t status = 0;
 243        int i;
 244
 245        spin_lock_irq(&mcbsp->lock);
 246        for (i = 0; i < st_data->nr_taps; i++)
 247                status += sprintf(&buf[status], (i ? ", %d" : "%d"),
 248                                  st_data->taps[i]);
 249        if (i)
 250                status += sprintf(&buf[status], "\n");
 251        spin_unlock_irq(&mcbsp->lock);
 252
 253        return status;
 254}
 255
 256static ssize_t st_taps_store(struct device *dev,
 257                             struct device_attribute *attr,
 258                             const char *buf, size_t size)
 259{
 260        struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
 261        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 262        int val, tmp, status, i = 0;
 263
 264        spin_lock_irq(&mcbsp->lock);
 265        memset(st_data->taps, 0, sizeof(st_data->taps));
 266        st_data->nr_taps = 0;
 267
 268        do {
 269                status = sscanf(buf, "%d%n", &val, &tmp);
 270                if (status < 0 || status == 0) {
 271                        size = -EINVAL;
 272                        goto out;
 273                }
 274                if (val < -32768 || val > 32767) {
 275                        size = -EINVAL;
 276                        goto out;
 277                }
 278                st_data->taps[i++] = val;
 279                buf += tmp;
 280                if (*buf != ',')
 281                        break;
 282                buf++;
 283        } while (1);
 284
 285        st_data->nr_taps = i;
 286
 287out:
 288        spin_unlock_irq(&mcbsp->lock);
 289
 290        return size;
 291}
 292
 293static DEVICE_ATTR_RW(st_taps);
 294
 295static const struct attribute *sidetone_attrs[] = {
 296        &dev_attr_st_taps.attr,
 297        NULL,
 298};
 299
 300static const struct attribute_group sidetone_attr_group = {
 301        .attrs = (struct attribute **)sidetone_attrs,
 302};
 303
 304int omap_mcbsp_st_start(struct omap_mcbsp *mcbsp)
 305{
 306        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 307
 308        if (st_data->enabled && !st_data->running) {
 309                omap_mcbsp_st_fir_write(mcbsp, st_data->taps);
 310                omap_mcbsp_st_chgain(mcbsp);
 311
 312                if (!mcbsp->free) {
 313                        omap_mcbsp_st_on(mcbsp);
 314                        st_data->running = 1;
 315                }
 316        }
 317
 318        return 0;
 319}
 320
 321int omap_mcbsp_st_stop(struct omap_mcbsp *mcbsp)
 322{
 323        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 324
 325        if (st_data->running) {
 326                if (!mcbsp->free) {
 327                        omap_mcbsp_st_off(mcbsp);
 328                        st_data->running = 0;
 329                }
 330        }
 331
 332        return 0;
 333}
 334
 335int omap_mcbsp_st_init(struct platform_device *pdev)
 336{
 337        struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
 338        struct omap_mcbsp_st_data *st_data;
 339        struct resource *res;
 340        int ret;
 341
 342        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone");
 343        if (!res)
 344                return 0;
 345
 346        st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL);
 347        if (!st_data)
 348                return -ENOMEM;
 349
 350        st_data->mcbsp_iclk = clk_get(mcbsp->dev, "ick");
 351        if (IS_ERR(st_data->mcbsp_iclk)) {
 352                dev_warn(mcbsp->dev,
 353                         "Failed to get ick, sidetone might be broken\n");
 354                st_data->mcbsp_iclk = NULL;
 355        }
 356
 357        st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start,
 358                                           resource_size(res));
 359        if (!st_data->io_base_st)
 360                return -ENOMEM;
 361
 362        ret = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group);
 363        if (ret)
 364                return ret;
 365
 366        mcbsp->st_data = st_data;
 367
 368        return 0;
 369}
 370
 371void omap_mcbsp_st_cleanup(struct platform_device *pdev)
 372{
 373        struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
 374
 375        if (mcbsp->st_data) {
 376                sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group);
 377                clk_put(mcbsp->st_data->mcbsp_iclk);
 378        }
 379}
 380
 381static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
 382                                    struct snd_ctl_elem_info *uinfo)
 383{
 384        struct soc_mixer_control *mc =
 385                (struct soc_mixer_control *)kcontrol->private_value;
 386        int max = mc->max;
 387        int min = mc->min;
 388
 389        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 390        uinfo->count = 1;
 391        uinfo->value.integer.min = min;
 392        uinfo->value.integer.max = max;
 393        return 0;
 394}
 395
 396#define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel)                           \
 397static int                                                              \
 398omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc,         \
 399                                       struct snd_ctl_elem_value *uc)   \
 400{                                                                       \
 401        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc);            \
 402        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);    \
 403        struct soc_mixer_control *mc =                                  \
 404                (struct soc_mixer_control *)kc->private_value;          \
 405        int max = mc->max;                                              \
 406        int min = mc->min;                                              \
 407        int val = uc->value.integer.value[0];                           \
 408                                                                        \
 409        if (val < min || val > max)                                     \
 410                return -EINVAL;                                         \
 411                                                                        \
 412        /* OMAP McBSP implementation uses index values 0..4 */          \
 413        return omap_mcbsp_st_set_chgain(mcbsp, channel, val);           \
 414}                                                                       \
 415                                                                        \
 416static int                                                              \
 417omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc,         \
 418                                       struct snd_ctl_elem_value *uc)   \
 419{                                                                       \
 420        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc);            \
 421        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);    \
 422        s16 chgain;                                                     \
 423                                                                        \
 424        if (omap_mcbsp_st_get_chgain(mcbsp, channel, &chgain))          \
 425                return -EAGAIN;                                         \
 426                                                                        \
 427        uc->value.integer.value[0] = chgain;                            \
 428        return 0;                                                       \
 429}
 430
 431OMAP_MCBSP_ST_CHANNEL_VOLUME(0)
 432OMAP_MCBSP_ST_CHANNEL_VOLUME(1)
 433
 434static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
 435                                  struct snd_ctl_elem_value *ucontrol)
 436{
 437        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 438        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 439        u8 value = ucontrol->value.integer.value[0];
 440
 441        if (value == omap_mcbsp_st_is_enabled(mcbsp))
 442                return 0;
 443
 444        if (value)
 445                omap_mcbsp_st_enable(mcbsp);
 446        else
 447                omap_mcbsp_st_disable(mcbsp);
 448
 449        return 1;
 450}
 451
 452static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
 453                                  struct snd_ctl_elem_value *ucontrol)
 454{
 455        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 456        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 457
 458        ucontrol->value.integer.value[0] = omap_mcbsp_st_is_enabled(mcbsp);
 459        return 0;
 460}
 461
 462#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax,                \
 463                                      xhandler_get, xhandler_put)       \
 464{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,             \
 465        .info = omap_mcbsp_st_info_volsw,                               \
 466        .get = xhandler_get, .put = xhandler_put,                       \
 467        .private_value = (unsigned long)&(struct soc_mixer_control)     \
 468        {.min = xmin, .max = xmax} }
 469
 470#define OMAP_MCBSP_ST_CONTROLS(port)                                      \
 471static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \
 472SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0,              \
 473               omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),           \
 474OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \
 475                              -32768, 32767,                              \
 476                              omap_mcbsp_get_st_ch0_volume,               \
 477                              omap_mcbsp_set_st_ch0_volume),              \
 478OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \
 479                              -32768, 32767,                              \
 480                              omap_mcbsp_get_st_ch1_volume,               \
 481                              omap_mcbsp_set_st_ch1_volume),              \
 482}
 483
 484OMAP_MCBSP_ST_CONTROLS(2);
 485OMAP_MCBSP_ST_CONTROLS(3);
 486
 487int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id)
 488{
 489        struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
 490        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 491
 492        if (!mcbsp->st_data) {
 493                dev_warn(mcbsp->dev, "No sidetone data for port\n");
 494                return 0;
 495        }
 496
 497        switch (port_id) {
 498        case 2: /* McBSP 2 */
 499                return snd_soc_add_dai_controls(cpu_dai,
 500                                        omap_mcbsp2_st_controls,
 501                                        ARRAY_SIZE(omap_mcbsp2_st_controls));
 502        case 3: /* McBSP 3 */
 503                return snd_soc_add_dai_controls(cpu_dai,
 504                                        omap_mcbsp3_st_controls,
 505                                        ARRAY_SIZE(omap_mcbsp3_st_controls));
 506        default:
 507                dev_err(mcbsp->dev, "Port %d not supported\n", port_id);
 508                break;
 509        }
 510
 511        return -EINVAL;
 512}
 513EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
 514