diff options
Diffstat (limited to 'sound/soc/soc-jack.c')
| -rw-r--r-- | sound/soc/soc-jack.c | 268 |
1 files changed, 214 insertions, 54 deletions
diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 28346fb2e70..d0d98810af9 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -13,15 +13,17 @@ #include <sound/jack.h> #include <sound/soc.h> -#include <sound/soc-dapm.h> #include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/delay.h> +#include <linux/export.h> +#include <trace/events/asoc.h> /** * snd_soc_jack_new - Create a new jack - * @card: ASoC card + * @codec: ASoC codec * @id: an identifying string for this jack * @type: a bitmask of enum snd_jack_type values that can be detected by * this jack @@ -32,13 +34,16 @@ * Returns zero if successful, or a negative error code on failure. * On success jack will be initialised. */ -int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, +int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, struct snd_soc_jack *jack) { - jack->card = card; + mutex_init(&jack->mutex); + jack->codec = codec; INIT_LIST_HEAD(&jack->pins); + INIT_LIST_HEAD(&jack->jack_zones); + BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); - return snd_jack_new(card->codec->card, id, type, &jack->jack); + return snd_jack_new(codec->card->snd_card, id, type, &jack->jack); } EXPORT_SYMBOL_GPL(snd_soc_jack_new); @@ -58,49 +63,101 @@ EXPORT_SYMBOL_GPL(snd_soc_jack_new); */ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) { - struct snd_soc_codec *codec = jack->card->codec; + struct snd_soc_codec *codec; + struct snd_soc_dapm_context *dapm; struct snd_soc_jack_pin *pin; + unsigned int sync = 0; int enable; - int oldstatus; - if (!jack) { - WARN_ON_ONCE(!jack); + trace_snd_soc_jack_report(jack, mask, status); + + if (!jack) return; - } - mutex_lock(&codec->mutex); + codec = jack->codec; + dapm = &codec->dapm; - oldstatus = jack->status; + mutex_lock(&jack->mutex); jack->status &= ~mask; - jack->status |= status; + jack->status |= status & mask; - /* The DAPM sync is expensive enough to be worth skipping */ - if (jack->status == oldstatus) - goto out; + trace_snd_soc_jack_notify(jack, status); list_for_each_entry(pin, &jack->pins, list) { - enable = pin->mask & status; + enable = pin->mask & jack->status; if (pin->invert) enable = !enable; if (enable) - snd_soc_dapm_enable_pin(codec, pin->pin); + snd_soc_dapm_enable_pin(dapm, pin->pin); else - snd_soc_dapm_disable_pin(codec, pin->pin); + snd_soc_dapm_disable_pin(dapm, pin->pin); + + /* we need to sync for this case only */ + sync = 1; } - snd_soc_dapm_sync(codec); + /* Report before the DAPM sync to help users updating micbias status */ + blocking_notifier_call_chain(&jack->notifier, jack->status, jack); - snd_jack_report(jack->jack, status); + if (sync) + snd_soc_dapm_sync(dapm); -out: - mutex_unlock(&codec->mutex); + snd_jack_report(jack->jack, jack->status); + + mutex_unlock(&jack->mutex); } EXPORT_SYMBOL_GPL(snd_soc_jack_report); /** + * snd_soc_jack_add_zones - Associate voltage zones with jack + * + * @jack: ASoC jack + * @count: Number of zones + * @zone: Array of zones + * + * After this function has been called the zones specified in the + * array will be associated with the jack. + */ +int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_zone *zones) +{ + int i; + + for (i = 0; i < count; i++) { + INIT_LIST_HEAD(&zones[i].list); + list_add(&(zones[i].list), &jack->jack_zones); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones); + +/** + * snd_soc_jack_get_type - Based on the mic bias value, this function returns + * the type of jack from the zones declared in the jack type + * + * @jack: ASoC jack + * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in + * + * Based on the mic bias value passed, this function helps identify + * the type of jack from the already declared jack zones + */ +int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage) +{ + struct snd_soc_jack_zone *zone; + + list_for_each_entry(zone, &jack->jack_zones, list) { + if (micbias_voltage >= zone->min_mv && + micbias_voltage < zone->max_mv) + return zone->jack_type; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_get_type); + +/** * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack * * @jack: ASoC jack @@ -118,12 +175,13 @@ int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, for (i = 0; i < count; i++) { if (!pins[i].pin) { - printk(KERN_ERR "No name for pin %d\n", i); + dev_err(jack->codec->dev, "ASoC: No name for pin %d\n", + i); return -EINVAL; } if (!pins[i].mask) { - printk(KERN_ERR "No mask for pin %d (%s)\n", i, - pins[i].pin); + dev_err(jack->codec->dev, "ASoC: No mask for pin %d" + " (%s)\n", i, pins[i].pin); return -EINVAL; } @@ -141,6 +199,40 @@ int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, } EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); +/** + * snd_soc_jack_notifier_register - Register a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to register + * + * Register for notification of the current status of the jack. Note + * that it is not possible to report additional jack events in the + * callback from the notifier, this is intended to support + * applications such as enabling electrical detection only when a + * mechanical detection event has occurred. + */ +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_register(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register); + +/** + * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to unregister + * + * Stop notifying for status changes. + */ +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister); + #ifdef CONFIG_GPIOLIB /* gpio detect */ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) @@ -149,10 +241,7 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) int enable; int report; - if (gpio->debounce_time > 0) - mdelay(gpio->debounce_time); - - enable = gpio_get_value(gpio->gpio); + enable = gpiod_get_value_cansleep(gpio->desc); if (gpio->invert) enable = !enable; @@ -161,6 +250,9 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) else report = 0; + if (gpio->jack_status_check) + report = gpio->jack_status_check(gpio->data); + snd_soc_jack_report(jack, report, gpio->report); } @@ -168,8 +260,15 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) static irqreturn_t gpio_handler(int irq, void *data) { struct snd_soc_jack_gpio *gpio = data; + struct device *dev = gpio->jack->codec->card->dev; - schedule_work(&gpio->work); + trace_snd_soc_jack_irq(gpio->name); + + if (device_may_wakeup(dev)) + pm_wakeup_event(dev, gpio->debounce_time + 50); + + queue_delayed_work(system_power_efficient_wq, &gpio->work, + msecs_to_jiffies(gpio->debounce_time)); return IRQ_HANDLED; } @@ -179,7 +278,7 @@ static void gpio_work(struct work_struct *work) { struct snd_soc_jack_gpio *gpio; - gpio = container_of(work, struct snd_soc_jack_gpio, work); + gpio = container_of(work, struct snd_soc_jack_gpio, work.work); snd_soc_jack_gpio_detect(gpio); } @@ -199,37 +298,72 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, int i, ret; for (i = 0; i < count; i++) { - if (!gpio_is_valid(gpios[i].gpio)) { - printk(KERN_ERR "Invalid gpio %d\n", - gpios[i].gpio); - ret = -EINVAL; - goto undo; - } if (!gpios[i].name) { - printk(KERN_ERR "No name for gpio %d\n", - gpios[i].gpio); + dev_err(jack->codec->dev, + "ASoC: No name for gpio at index %d\n", i); ret = -EINVAL; goto undo; } - ret = gpio_request(gpios[i].gpio, gpios[i].name); - if (ret) - goto undo; + if (gpios[i].gpiod_dev) { + /* GPIO descriptor */ + gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, + gpios[i].name, + gpios[i].idx); + if (IS_ERR(gpios[i].desc)) { + ret = PTR_ERR(gpios[i].desc); + dev_err(gpios[i].gpiod_dev, + "ASoC: Cannot get gpio at index %d: %d", + i, ret); + goto undo; + } + } else { + /* legacy GPIO number */ + if (!gpio_is_valid(gpios[i].gpio)) { + dev_err(jack->codec->dev, + "ASoC: Invalid gpio %d\n", + gpios[i].gpio); + ret = -EINVAL; + goto undo; + } + + ret = gpio_request(gpios[i].gpio, gpios[i].name); + if (ret) + goto undo; + + gpios[i].desc = gpio_to_desc(gpios[i].gpio); + } - ret = gpio_direction_input(gpios[i].gpio); + ret = gpiod_direction_input(gpios[i].desc); if (ret) goto err; - ret = request_irq(gpio_to_irq(gpios[i].gpio), - gpio_handler, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - jack->card->dev->driver->name, - &gpios[i]); - if (ret) + INIT_DELAYED_WORK(&gpios[i].work, gpio_work); + gpios[i].jack = jack; + + ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc), + gpio_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + gpios[i].name, + &gpios[i]); + if (ret < 0) goto err; - INIT_WORK(&gpios[i].work, gpio_work); - gpios[i].jack = jack; + if (gpios[i].wake) { + ret = irq_set_irq_wake(gpiod_to_irq(gpios[i].desc), 1); + if (ret != 0) + dev_err(jack->codec->dev, + "ASoC: Failed to mark GPIO at index %d as wake source: %d\n", + i, ret); + } + + /* Expose GPIO value over sysfs for diagnostic purposes */ + gpiod_export(gpios[i].desc, false); + + /* Update initial jack status */ + schedule_delayed_work(&gpios[i].work, + msecs_to_jiffies(gpios[i].debounce_time)); } return 0; @@ -244,6 +378,30 @@ undo: EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios); /** + * snd_soc_jack_add_gpiods - Associate GPIO descriptor pins with an ASoC jack + * + * @gpiod_dev: GPIO consumer device + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * This function will request gpio, set data direction and request irq + * for each gpio in the array. + */ +int snd_soc_jack_add_gpiods(struct device *gpiod_dev, + struct snd_soc_jack *jack, + int count, struct snd_soc_jack_gpio *gpios) +{ + int i; + + for (i = 0; i < count; i++) + gpios[i].gpiod_dev = gpiod_dev; + + return snd_soc_jack_add_gpios(jack, count, gpios); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpiods); + +/** * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack * * @jack: ASoC jack @@ -258,8 +416,10 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, int i; for (i = 0; i < count; i++) { - free_irq(gpio_to_irq(gpios[i].gpio), &gpios[i]); - gpio_free(gpios[i].gpio); + gpiod_unexport(gpios[i].desc); + free_irq(gpiod_to_irq(gpios[i].desc), &gpios[i]); + cancel_delayed_work_sync(&gpios[i].work); + gpiod_put(gpios[i].desc); gpios[i].jack = NULL; } } |
