omap-mcbsp-st.c (13077B)
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);