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        u16 w;
 146        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 147
 148        w = MCBSP_ST_READ(mcbsp, SSELCR);
 149
 150        MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) |
 151                       ST_CH1GAIN(st_data->ch1gain));
 152}
 153
 154static int omap_mcbsp_st_set_chgain(struct omap_mcbsp *mcbsp, int channel,
 155                                    s16 chgain)
 156{
 157        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 158        int ret = 0;
 159
 160        if (!st_data)
 161                return -ENOENT;
 162
 163        spin_lock_irq(&mcbsp->lock);
 164        if (channel == 0)
 165                st_data->ch0gain = chgain;
 166        else if (channel == 1)
 167                st_data->ch1gain = chgain;
 168        else
 169                ret = -EINVAL;
 170
 171        if (st_data->enabled)
 172                omap_mcbsp_st_chgain(mcbsp);
 173        spin_unlock_irq(&mcbsp->lock);
 174
 175        return ret;
 176}
 177
 178static int omap_mcbsp_st_get_chgain(struct omap_mcbsp *mcbsp, int channel,
 179                                    s16 *chgain)
 180{
 181        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 182        int ret = 0;
 183
 184        if (!st_data)
 185                return -ENOENT;
 186
 187        spin_lock_irq(&mcbsp->lock);
 188        if (channel == 0)
 189                *chgain = st_data->ch0gain;
 190        else if (channel == 1)
 191                *chgain = st_data->ch1gain;
 192        else
 193                ret = -EINVAL;
 194        spin_unlock_irq(&mcbsp->lock);
 195
 196        return ret;
 197}
 198
 199static int omap_mcbsp_st_enable(struct omap_mcbsp *mcbsp)
 200{
 201        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 202
 203        if (!st_data)
 204                return -ENODEV;
 205
 206        spin_lock_irq(&mcbsp->lock);
 207        st_data->enabled = 1;
 208        omap_mcbsp_st_start(mcbsp);
 209        spin_unlock_irq(&mcbsp->lock);
 210
 211        return 0;
 212}
 213
 214static int omap_mcbsp_st_disable(struct omap_mcbsp *mcbsp)
 215{
 216        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 217        int ret = 0;
 218
 219        if (!st_data)
 220                return -ENODEV;
 221
 222        spin_lock_irq(&mcbsp->lock);
 223        omap_mcbsp_st_stop(mcbsp);
 224        st_data->enabled = 0;
 225        spin_unlock_irq(&mcbsp->lock);
 226
 227        return ret;
 228}
 229
 230static int omap_mcbsp_st_is_enabled(struct omap_mcbsp *mcbsp)
 231{
 232        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 233
 234        if (!st_data)
 235                return -ENODEV;
 236
 237        return st_data->enabled;
 238}
 239
 240static ssize_t st_taps_show(struct device *dev,
 241                            struct device_attribute *attr, char *buf)
 242{
 243        struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
 244        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 245        ssize_t status = 0;
 246        int i;
 247
 248        spin_lock_irq(&mcbsp->lock);
 249        for (i = 0; i < st_data->nr_taps; i++)
 250                status += sprintf(&buf[status], (i ? ", %d" : "%d"),
 251                                  st_data->taps[i]);
 252        if (i)
 253                status += sprintf(&buf[status], "\n");
 254        spin_unlock_irq(&mcbsp->lock);
 255
 256        return status;
 257}
 258
 259static ssize_t st_taps_store(struct device *dev,
 260                             struct device_attribute *attr,
 261                             const char *buf, size_t size)
 262{
 263        struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
 264        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 265        int val, tmp, status, i = 0;
 266
 267        spin_lock_irq(&mcbsp->lock);
 268        memset(st_data->taps, 0, sizeof(st_data->taps));
 269        st_data->nr_taps = 0;
 270
 271        do {
 272                status = sscanf(buf, "%d%n", &val, &tmp);
 273                if (status < 0 || status == 0) {
 274                        size = -EINVAL;
 275                        goto out;
 276                }
 277                if (val < -32768 || val > 32767) {
 278                        size = -EINVAL;
 279                        goto out;
 280                }
 281                st_data->taps[i++] = val;
 282                buf += tmp;
 283                if (*buf != ',')
 284                        break;
 285                buf++;
 286        } while (1);
 287
 288        st_data->nr_taps = i;
 289
 290out:
 291        spin_unlock_irq(&mcbsp->lock);
 292
 293        return size;
 294}
 295
 296static DEVICE_ATTR_RW(st_taps);
 297
 298static const struct attribute *sidetone_attrs[] = {
 299        &dev_attr_st_taps.attr,
 300        NULL,
 301};
 302
 303static const struct attribute_group sidetone_attr_group = {
 304        .attrs = (struct attribute **)sidetone_attrs,
 305};
 306
 307int omap_mcbsp_st_start(struct omap_mcbsp *mcbsp)
 308{
 309        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 310
 311        if (st_data->enabled && !st_data->running) {
 312                omap_mcbsp_st_fir_write(mcbsp, st_data->taps);
 313                omap_mcbsp_st_chgain(mcbsp);
 314
 315                if (!mcbsp->free) {
 316                        omap_mcbsp_st_on(mcbsp);
 317                        st_data->running = 1;
 318                }
 319        }
 320
 321        return 0;
 322}
 323
 324int omap_mcbsp_st_stop(struct omap_mcbsp *mcbsp)
 325{
 326        struct omap_mcbsp_st_data *st_data = mcbsp->st_data;
 327
 328        if (st_data->running) {
 329                if (!mcbsp->free) {
 330                        omap_mcbsp_st_off(mcbsp);
 331                        st_data->running = 0;
 332                }
 333        }
 334
 335        return 0;
 336}
 337
 338int omap_mcbsp_st_init(struct platform_device *pdev)
 339{
 340        struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
 341        struct omap_mcbsp_st_data *st_data;
 342        struct resource *res;
 343        int ret;
 344
 345        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone");
 346        if (!res)
 347                return 0;
 348
 349        st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL);
 350        if (!st_data)
 351                return -ENOMEM;
 352
 353        st_data->mcbsp_iclk = clk_get(mcbsp->dev, "ick");
 354        if (IS_ERR(st_data->mcbsp_iclk)) {
 355                dev_warn(mcbsp->dev,
 356                         "Failed to get ick, sidetone might be broken\n");
 357                st_data->mcbsp_iclk = NULL;
 358        }
 359
 360        st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start,
 361                                           resource_size(res));
 362        if (!st_data->io_base_st)
 363                return -ENOMEM;
 364
 365        ret = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group);
 366        if (ret)
 367                return ret;
 368
 369        mcbsp->st_data = st_data;
 370
 371        return 0;
 372}
 373
 374void omap_mcbsp_st_cleanup(struct platform_device *pdev)
 375{
 376        struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev);
 377
 378        if (mcbsp->st_data) {
 379                sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group);
 380                clk_put(mcbsp->st_data->mcbsp_iclk);
 381        }
 382}
 383
 384static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol,
 385                                    struct snd_ctl_elem_info *uinfo)
 386{
 387        struct soc_mixer_control *mc =
 388                (struct soc_mixer_control *)kcontrol->private_value;
 389        int max = mc->max;
 390        int min = mc->min;
 391
 392        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 393        uinfo->count = 1;
 394        uinfo->value.integer.min = min;
 395        uinfo->value.integer.max = max;
 396        return 0;
 397}
 398
 399#define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel)                           \
 400static int                                                              \
 401omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc,         \
 402                                       struct snd_ctl_elem_value *uc)   \
 403{                                                                       \
 404        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc);            \
 405        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);    \
 406        struct soc_mixer_control *mc =                                  \
 407                (struct soc_mixer_control *)kc->private_value;          \
 408        int max = mc->max;                                              \
 409        int min = mc->min;                                              \
 410        int val = uc->value.integer.value[0];                           \
 411                                                                        \
 412        if (val < min || val > max)                                     \
 413                return -EINVAL;                                         \
 414                                                                        \
 415        /* OMAP McBSP implementation uses index values 0..4 */          \
 416        return omap_mcbsp_st_set_chgain(mcbsp, channel, val);           \
 417}                                                                       \
 418                                                                        \
 419static int                                                              \
 420omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc,         \
 421                                       struct snd_ctl_elem_value *uc)   \
 422{                                                                       \
 423        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc);            \
 424        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);    \
 425        s16 chgain;                                                     \
 426                                                                        \
 427        if (omap_mcbsp_st_get_chgain(mcbsp, channel, &chgain))          \
 428                return -EAGAIN;                                         \
 429                                                                        \
 430        uc->value.integer.value[0] = chgain;                            \
 431        return 0;                                                       \
 432}
 433
 434OMAP_MCBSP_ST_CHANNEL_VOLUME(0)
 435OMAP_MCBSP_ST_CHANNEL_VOLUME(1)
 436
 437static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol,
 438                                  struct snd_ctl_elem_value *ucontrol)
 439{
 440        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 441        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 442        u8 value = ucontrol->value.integer.value[0];
 443
 444        if (value == omap_mcbsp_st_is_enabled(mcbsp))
 445                return 0;
 446
 447        if (value)
 448                omap_mcbsp_st_enable(mcbsp);
 449        else
 450                omap_mcbsp_st_disable(mcbsp);
 451
 452        return 1;
 453}
 454
 455static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol,
 456                                  struct snd_ctl_elem_value *ucontrol)
 457{
 458        struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
 459        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 460
 461        ucontrol->value.integer.value[0] = omap_mcbsp_st_is_enabled(mcbsp);
 462        return 0;
 463}
 464
 465#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax,                \
 466                                      xhandler_get, xhandler_put)       \
 467{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,             \
 468        .info = omap_mcbsp_st_info_volsw,                               \
 469        .get = xhandler_get, .put = xhandler_put,                       \
 470        .private_value = (unsigned long)&(struct soc_mixer_control)     \
 471        {.min = xmin, .max = xmax} }
 472
 473#define OMAP_MCBSP_ST_CONTROLS(port)                                      \
 474static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \
 475SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0,              \
 476               omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode),           \
 477OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \
 478                              -32768, 32767,                              \
 479                              omap_mcbsp_get_st_ch0_volume,               \
 480                              omap_mcbsp_set_st_ch0_volume),              \
 481OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \
 482                              -32768, 32767,                              \
 483                              omap_mcbsp_get_st_ch1_volume,               \
 484                              omap_mcbsp_set_st_ch1_volume),              \
 485}
 486
 487OMAP_MCBSP_ST_CONTROLS(2);
 488OMAP_MCBSP_ST_CONTROLS(3);
 489
 490int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id)
 491{
 492        struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 493        struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai);
 494
 495        if (!mcbsp->st_data) {
 496                dev_warn(mcbsp->dev, "No sidetone data for port\n");
 497                return 0;
 498        }
 499
 500        switch (port_id) {
 501        case 2: /* McBSP 2 */
 502                return snd_soc_add_dai_controls(cpu_dai,
 503                                        omap_mcbsp2_st_controls,
 504                                        ARRAY_SIZE(omap_mcbsp2_st_controls));
 505        case 3: /* McBSP 3 */
 506                return snd_soc_add_dai_controls(cpu_dai,
 507                                        omap_mcbsp3_st_controls,
 508                                        ARRAY_SIZE(omap_mcbsp3_st_controls));
 509        default:
 510                dev_err(mcbsp->dev, "Port %d not supported\n", port_id);
 511                break;
 512        }
 513
 514        return -EINVAL;
 515}
 516EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls);
 517