1 // SPDX-License-Identifier: GPL-2.0+ 2 // 3 // soc-jack.c -- ALSA SoC jack handling 4 // 5 // Copyright 2008 Wolfson Microelectronics PLC. 6 // 7 // Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 8 9 #include <sound/jack.h> 10 #include <sound/soc.h> 11 #include <linux/gpio/consumer.h> 12 #include <linux/interrupt.h> 13 #include <linux/workqueue.h> 14 #include <linux/delay.h> 15 #include <linux/export.h> 16 #include <linux/suspend.h> 17 #include <trace/events/asoc.h> 18 19 /** 20 * snd_soc_jack_report - Report the current status for a jack 21 * 22 * @jack: the jack 23 * @status: a bitmask of enum snd_jack_type values that are currently detected. 24 * @mask: a bitmask of enum snd_jack_type values that being reported. 25 * 26 * If configured using snd_soc_jack_add_pins() then the associated 27 * DAPM pins will be enabled or disabled as appropriate and DAPM 28 * synchronised. 29 * 30 * Note: This function uses mutexes and should be called from a 31 * context which can sleep (such as a workqueue). 32 */ 33 void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) 34 { 35 struct snd_soc_dapm_context *dapm; 36 struct snd_soc_jack_pin *pin; 37 unsigned int sync = 0; 38 39 if (!jack || !jack->jack) 40 return; 41 trace_snd_soc_jack_report(jack, mask, status); 42 43 dapm = &jack->card->dapm; 44 45 mutex_lock(&jack->mutex); 46 47 jack->status &= ~mask; 48 jack->status |= status & mask; 49 50 trace_snd_soc_jack_notify(jack, status); 51 52 list_for_each_entry(pin, &jack->pins, list) { 53 int enable = pin->mask & jack->status; 54 55 if (pin->invert) 56 enable = !enable; 57 58 if (enable) 59 snd_soc_dapm_enable_pin(dapm, pin->pin); 60 else 61 snd_soc_dapm_disable_pin(dapm, pin->pin); 62 63 /* we need to sync for this case only */ 64 sync = 1; 65 } 66 67 /* Report before the DAPM sync to help users updating micbias status */ 68 blocking_notifier_call_chain(&jack->notifier, jack->status, jack); 69 70 if (sync) 71 snd_soc_dapm_sync(dapm); 72 73 snd_jack_report(jack->jack, jack->status); 74 75 mutex_unlock(&jack->mutex); 76 } 77 EXPORT_SYMBOL_GPL(snd_soc_jack_report); 78 79 /** 80 * snd_soc_jack_add_zones - Associate voltage zones with jack 81 * 82 * @jack: ASoC jack 83 * @count: Number of zones 84 * @zones: Array of zones 85 * 86 * After this function has been called the zones specified in the 87 * array will be associated with the jack. 88 */ 89 int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count, 90 struct snd_soc_jack_zone *zones) 91 { 92 int i; 93 94 for (i = 0; i < count; i++) { 95 INIT_LIST_HEAD(&zones[i].list); 96 list_add(&(zones[i].list), &jack->jack_zones); 97 } 98 return 0; 99 } 100 EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones); 101 102 /** 103 * snd_soc_jack_get_type - Based on the mic bias value, this function returns 104 * the type of jack from the zones declared in the jack type 105 * 106 * @jack: ASoC jack 107 * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in 108 * 109 * Based on the mic bias value passed, this function helps identify 110 * the type of jack from the already declared jack zones 111 */ 112 int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage) 113 { 114 struct snd_soc_jack_zone *zone; 115 116 list_for_each_entry(zone, &jack->jack_zones, list) { 117 if (micbias_voltage >= zone->min_mv && 118 micbias_voltage < zone->max_mv) 119 return zone->jack_type; 120 } 121 return 0; 122 } 123 EXPORT_SYMBOL_GPL(snd_soc_jack_get_type); 124 125 /** 126 * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack 127 * 128 * @jack: ASoC jack created with snd_soc_card_jack_new_pins() 129 * @count: Number of pins 130 * @pins: Array of pins 131 * 132 * After this function has been called the DAPM pins specified in the 133 * pins array will have their status updated to reflect the current 134 * state of the jack whenever the jack status is updated. 135 */ 136 int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, 137 struct snd_soc_jack_pin *pins) 138 { 139 int i; 140 141 for (i = 0; i < count; i++) { 142 if (!pins[i].pin) { 143 dev_err(jack->card->dev, "ASoC: No name for pin %d\n", 144 i); 145 return -EINVAL; 146 } 147 if (!pins[i].mask) { 148 dev_err(jack->card->dev, "ASoC: No mask for pin %d" 149 " (%s)\n", i, pins[i].pin); 150 return -EINVAL; 151 } 152 153 INIT_LIST_HEAD(&pins[i].list); 154 list_add(&(pins[i].list), &jack->pins); 155 snd_jack_add_new_kctl(jack->jack, pins[i].pin, pins[i].mask); 156 } 157 158 /* Update to reflect the last reported status; canned jack 159 * implementations are likely to set their state before the 160 * card has an opportunity to associate pins. 161 */ 162 snd_soc_jack_report(jack, 0, 0); 163 164 return 0; 165 } 166 EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); 167 168 /** 169 * snd_soc_jack_notifier_register - Register a notifier for jack status 170 * 171 * @jack: ASoC jack 172 * @nb: Notifier block to register 173 * 174 * Register for notification of the current status of the jack. Note 175 * that it is not possible to report additional jack events in the 176 * callback from the notifier, this is intended to support 177 * applications such as enabling electrical detection only when a 178 * mechanical detection event has occurred. 179 */ 180 void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, 181 struct notifier_block *nb) 182 { 183 blocking_notifier_chain_register(&jack->notifier, nb); 184 } 185 EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register); 186 187 /** 188 * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status 189 * 190 * @jack: ASoC jack 191 * @nb: Notifier block to unregister 192 * 193 * Stop notifying for status changes. 194 */ 195 void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, 196 struct notifier_block *nb) 197 { 198 blocking_notifier_chain_unregister(&jack->notifier, nb); 199 } 200 EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister); 201 202 #ifdef CONFIG_GPIOLIB 203 struct jack_gpio_tbl { 204 int count; 205 struct snd_soc_jack *jack; 206 struct snd_soc_jack_gpio *gpios; 207 }; 208 209 /* gpio detect */ 210 static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) 211 { 212 struct snd_soc_jack *jack = gpio->jack; 213 int enable; 214 int report; 215 216 enable = gpiod_get_value_cansleep(gpio->desc); 217 if (gpio->invert) 218 enable = !enable; 219 220 if (enable) 221 report = gpio->report; 222 else 223 report = 0; 224 225 if (gpio->jack_status_check) 226 report = gpio->jack_status_check(gpio->data); 227 228 snd_soc_jack_report(jack, report, gpio->report); 229 } 230 231 /* irq handler for gpio pin */ 232 static irqreturn_t gpio_handler(int irq, void *data) 233 { 234 struct snd_soc_jack_gpio *gpio = data; 235 struct device *dev = gpio->jack->card->dev; 236 237 trace_snd_soc_jack_irq(gpio->name); 238 239 if (device_may_wakeup(dev)) 240 pm_wakeup_event(dev, gpio->debounce_time + 50); 241 242 queue_delayed_work(system_power_efficient_wq, &gpio->work, 243 msecs_to_jiffies(gpio->debounce_time)); 244 245 return IRQ_HANDLED; 246 } 247 248 /* gpio work */ 249 static void gpio_work(struct work_struct *work) 250 { 251 struct snd_soc_jack_gpio *gpio; 252 253 gpio = container_of(work, struct snd_soc_jack_gpio, work.work); 254 snd_soc_jack_gpio_detect(gpio); 255 } 256 257 static int snd_soc_jack_pm_notifier(struct notifier_block *nb, 258 unsigned long action, void *data) 259 { 260 struct snd_soc_jack_gpio *gpio = 261 container_of(nb, struct snd_soc_jack_gpio, pm_notifier); 262 263 switch (action) { 264 case PM_POST_SUSPEND: 265 case PM_POST_HIBERNATION: 266 case PM_POST_RESTORE: 267 /* 268 * Use workqueue so we do not have to care about running 269 * concurrently with work triggered by the interrupt handler. 270 */ 271 queue_delayed_work(system_power_efficient_wq, &gpio->work, 0); 272 break; 273 } 274 275 return NOTIFY_DONE; 276 } 277 278 static void jack_free_gpios(struct snd_soc_jack *jack, int count, 279 struct snd_soc_jack_gpio *gpios) 280 { 281 int i; 282 283 for (i = 0; i < count; i++) { 284 gpiod_unexport(gpios[i].desc); 285 unregister_pm_notifier(&gpios[i].pm_notifier); 286 free_irq(gpiod_to_irq(gpios[i].desc), &gpios[i]); 287 cancel_delayed_work_sync(&gpios[i].work); 288 gpiod_put(gpios[i].desc); 289 gpios[i].jack = NULL; 290 } 291 } 292 293 static void jack_devres_free_gpios(struct device *dev, void *res) 294 { 295 struct jack_gpio_tbl *tbl = res; 296 297 jack_free_gpios(tbl->jack, tbl->count, tbl->gpios); 298 } 299 300 /** 301 * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack 302 * 303 * @jack: ASoC jack 304 * @count: number of pins 305 * @gpios: array of gpio pins 306 * 307 * This function will request gpio, set data direction and request irq 308 * for each gpio in the array. 309 */ 310 int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, 311 struct snd_soc_jack_gpio *gpios) 312 { 313 int i, ret; 314 struct jack_gpio_tbl *tbl; 315 316 tbl = devres_alloc(jack_devres_free_gpios, sizeof(*tbl), GFP_KERNEL); 317 if (!tbl) 318 return -ENOMEM; 319 tbl->jack = jack; 320 tbl->count = count; 321 tbl->gpios = gpios; 322 323 for (i = 0; i < count; i++) { 324 if (!gpios[i].name) { 325 dev_err(jack->card->dev, 326 "ASoC: No name for gpio at index %d\n", i); 327 ret = -EINVAL; 328 goto undo; 329 } 330 331 if (gpios[i].desc) { 332 /* Already have a GPIO descriptor. */ 333 goto got_gpio; 334 } else if (gpios[i].gpiod_dev) { 335 /* Get a GPIO descriptor */ 336 gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, 337 gpios[i].name, 338 gpios[i].idx, GPIOD_IN); 339 if (IS_ERR(gpios[i].desc)) { 340 ret = PTR_ERR(gpios[i].desc); 341 dev_err(gpios[i].gpiod_dev, 342 "ASoC: Cannot get gpio at index %d: %d", 343 i, ret); 344 goto undo; 345 } 346 } else { 347 dev_err(jack->card->dev, "ASoC: Invalid gpio at index %d\n", i); 348 ret = -EINVAL; 349 goto undo; 350 } 351 got_gpio: 352 INIT_DELAYED_WORK(&gpios[i].work, gpio_work); 353 gpios[i].jack = jack; 354 355 ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc), 356 gpio_handler, 357 IRQF_SHARED | 358 IRQF_TRIGGER_RISING | 359 IRQF_TRIGGER_FALLING, 360 gpios[i].name, 361 &gpios[i]); 362 if (ret < 0) 363 goto undo; 364 365 if (gpios[i].wake) { 366 ret = irq_set_irq_wake(gpiod_to_irq(gpios[i].desc), 1); 367 if (ret != 0) 368 dev_err(jack->card->dev, 369 "ASoC: Failed to mark GPIO at index %d as wake source: %d\n", 370 i, ret); 371 } 372 373 /* 374 * Register PM notifier so we do not miss state transitions 375 * happening while system is asleep. 376 */ 377 gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier; 378 register_pm_notifier(&gpios[i].pm_notifier); 379 380 /* Expose GPIO value over sysfs for diagnostic purposes */ 381 gpiod_export(gpios[i].desc, false); 382 383 /* Update initial jack status */ 384 schedule_delayed_work(&gpios[i].work, 385 msecs_to_jiffies(gpios[i].debounce_time)); 386 } 387 388 devres_add(jack->card->dev, tbl); 389 return 0; 390 391 undo: 392 jack_free_gpios(jack, i, gpios); 393 devres_free(tbl); 394 395 return ret; 396 } 397 EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios); 398 399 /** 400 * snd_soc_jack_add_gpiods - Associate GPIO descriptor pins with an ASoC jack 401 * 402 * @gpiod_dev: GPIO consumer device 403 * @jack: ASoC jack 404 * @count: number of pins 405 * @gpios: array of gpio pins 406 * 407 * This function will request gpio, set data direction and request irq 408 * for each gpio in the array. 409 */ 410 int snd_soc_jack_add_gpiods(struct device *gpiod_dev, 411 struct snd_soc_jack *jack, 412 int count, struct snd_soc_jack_gpio *gpios) 413 { 414 int i; 415 416 for (i = 0; i < count; i++) 417 gpios[i].gpiod_dev = gpiod_dev; 418 419 return snd_soc_jack_add_gpios(jack, count, gpios); 420 } 421 EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpiods); 422 423 /** 424 * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack 425 * 426 * @jack: ASoC jack 427 * @count: number of pins 428 * @gpios: array of gpio pins 429 * 430 * Release gpio and irq resources for gpio pins associated with an ASoC jack. 431 */ 432 void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, 433 struct snd_soc_jack_gpio *gpios) 434 { 435 jack_free_gpios(jack, count, gpios); 436 devres_destroy(jack->card->dev, jack_devres_free_gpios, NULL, NULL); 437 } 438 EXPORT_SYMBOL_GPL(snd_soc_jack_free_gpios); 439 #endif /* CONFIG_GPIOLIB */ 440
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.