diff --git a/drivers/mfd/wcd9310-core.c b/drivers/mfd/wcd9310-core.c index 84eadef47d2..588f9502c72 100644 --- a/drivers/mfd/wcd9310-core.c +++ b/drivers/mfd/wcd9310-core.c @@ -370,6 +370,13 @@ static int tabla_device_init(struct tabla *tabla, int irq) mutex_init(&tabla->io_lock); mutex_init(&tabla->xfer_lock); + + mutex_init(&tabla->pm_lock); + tabla->wlock_holders = 0; + tabla->pm_state = TABLA_PM_SLEEPABLE; + init_waitqueue_head(&tabla->pm_wq); + wake_lock_init(&tabla->wlock, WAKE_LOCK_IDLE, "wcd9310-irq"); + dev_set_drvdata(tabla->dev, tabla); tabla_bring_up(tabla); @@ -397,19 +404,22 @@ err_irq: tabla_irq_exit(tabla); err: tabla_bring_down(tabla); + wake_lock_destroy(&tabla->wlock); + mutex_destroy(&tabla->pm_lock); mutex_destroy(&tabla->io_lock); mutex_destroy(&tabla->xfer_lock); return ret; } + static void tabla_device_exit(struct tabla *tabla) { tabla_irq_exit(tabla); tabla_bring_down(tabla); tabla_free_reset(tabla); + mutex_destroy(&tabla->pm_lock); + wake_lock_destroy(&tabla->wlock); mutex_destroy(&tabla->io_lock); mutex_destroy(&tabla->xfer_lock); - slim_remove_device(tabla->slim_slave); - kfree(tabla); } @@ -707,13 +717,13 @@ int tabla_i2c_read_device(unsigned short reg, } int tabla_i2c_read(struct tabla *tabla, unsigned short reg, - int bytes, void *dest, bool interface_reg) + int bytes, void *dest, bool interface_reg) { return tabla_i2c_read_device(reg, bytes, dest); } int tabla_i2c_write(struct tabla *tabla, unsigned short reg, - int bytes, void *src, bool interface_reg) + int bytes, void *src, bool interface_reg) { return tabla_i2c_write_device(reg, src, bytes); } @@ -799,7 +809,13 @@ fail: static int __devexit tabla_i2c_remove(struct i2c_client *client) { + struct tabla *tabla; + pr_debug("exit\n"); + tabla = dev_get_drvdata(&client->dev); + tabla_device_exit(tabla); + tabla_disable_supplies(tabla); + kfree(tabla); return 0; } @@ -935,6 +951,7 @@ err_tabla: err: return ret; } + static int tabla_slim_remove(struct slim_device *pdev) { struct tabla *tabla; @@ -948,14 +965,103 @@ static int tabla_slim_remove(struct slim_device *pdev) tabla = slim_get_devicedata(pdev); tabla_device_exit(tabla); tabla_disable_supplies(tabla); + slim_remove_device(tabla->slim_slave); kfree(tabla); return 0; } + +static int tabla_resume(struct tabla *tabla) +{ + int ret = 0; + + pr_debug("%s: enter\n", __func__); + mutex_lock(&tabla->pm_lock); + if (tabla->pm_state == TABLA_PM_ASLEEP) { + pr_debug("%s: resuming system, state %d, wlock %d\n", __func__, + tabla->pm_state, tabla->wlock_holders); + tabla->pm_state = TABLA_PM_SLEEPABLE; + } else { + pr_warn("%s: system is already awake, state %d wlock %d\n", + __func__, tabla->pm_state, tabla->wlock_holders); + } + mutex_unlock(&tabla->pm_lock); + wake_up_all(&tabla->pm_wq); + + return ret; +} + +static int tabla_slim_resume(struct slim_device *sldev) +{ + struct tabla *tabla = slim_get_devicedata(sldev); + return tabla_resume(tabla); +} + +static int tabla_i2c_resume(struct i2c_client *i2cdev) +{ + struct tabla *tabla = dev_get_drvdata(&i2cdev->dev); + return tabla_resume(tabla); +} + +static int tabla_suspend(struct tabla *tabla, pm_message_t pmesg) +{ + int ret = 0; + + pr_debug("%s: enter\n", __func__); + /* wake_lock() can be called after this suspend chain call started. + * thus suspend can be called while wlock is being held */ + mutex_lock(&tabla->pm_lock); + if (tabla->pm_state == TABLA_PM_SLEEPABLE) { + pr_debug("%s: suspending system, state %d, wlock %d\n", + __func__, tabla->pm_state, tabla->wlock_holders); + tabla->pm_state = TABLA_PM_ASLEEP; + } else if (tabla->pm_state == TABLA_PM_AWAKE) { + /* unlock to wait for pm_state == TABLA_PM_SLEEPABLE + * then set to TABLA_PM_ASLEEP */ + pr_debug("%s: waiting to suspend system, state %d, wlock %d\n", + __func__, tabla->pm_state, tabla->wlock_holders); + mutex_unlock(&tabla->pm_lock); + if (!(wait_event_timeout(tabla->pm_wq, + tabla_pm_cmpxchg(tabla, + TABLA_PM_SLEEPABLE, + TABLA_PM_ASLEEP) == + TABLA_PM_SLEEPABLE, + HZ))) { + pr_debug("%s: suspend failed state %d, wlock %d\n", + __func__, tabla->pm_state, + tabla->wlock_holders); + ret = -EBUSY; + } else { + pr_debug("%s: done, state %d, wlock %d\n", __func__, + tabla->pm_state, tabla->wlock_holders); + } + mutex_lock(&tabla->pm_lock); + } else if (tabla->pm_state == TABLA_PM_ASLEEP) { + pr_warn("%s: system is already suspended, state %d, wlock %dn", + __func__, tabla->pm_state, tabla->wlock_holders); + } + mutex_unlock(&tabla->pm_lock); + + return ret; +} + +static int tabla_slim_suspend(struct slim_device *sldev, pm_message_t pmesg) +{ + struct tabla *tabla = slim_get_devicedata(sldev); + return tabla_suspend(tabla, pmesg); +} + +static int tabla_i2c_suspend(struct i2c_client *i2cdev, pm_message_t pmesg) +{ + struct tabla *tabla = dev_get_drvdata(&i2cdev->dev); + return tabla_suspend(tabla, pmesg); +} + static const struct slim_device_id slimtest_id[] = { {"tabla-slim", 0}, {} }; + static struct slim_driver tabla_slim_driver = { .driver = { .name = "tabla-slim", @@ -964,6 +1070,8 @@ static struct slim_driver tabla_slim_driver = { .probe = tabla_slim_probe, .remove = tabla_slim_remove, .id_table = slimtest_id, + .resume = tabla_slim_resume, + .suspend = tabla_slim_suspend, }; static const struct slim_device_id slimtest2x_id[] = { @@ -979,6 +1087,8 @@ static struct slim_driver tabla2x_slim_driver = { .probe = tabla_slim_probe, .remove = tabla_slim_remove, .id_table = slimtest2x_id, + .resume = tabla_slim_resume, + .suspend = tabla_slim_suspend, }; #define TABLA_I2C_TOP_LEVEL 0 @@ -996,13 +1106,15 @@ static struct i2c_device_id tabla_id_table[] = { MODULE_DEVICE_TABLE(i2c, tabla_id_table); static struct i2c_driver tabla_i2c_driver = { - .driver = { - .owner = THIS_MODULE, - .name = "tabla-i2c-core", + .driver = { + .owner = THIS_MODULE, + .name = "tabla-i2c-core", }, - .id_table = tabla_id_table, - .probe = tabla_i2c_probe, - .remove = __devexit_p(tabla_i2c_remove), + .id_table = tabla_id_table, + .probe = tabla_i2c_probe, + .remove = __devexit_p(tabla_i2c_remove), + .resume = tabla_i2c_resume, + .suspend = tabla_i2c_suspend, }; static int __init tabla_init(void) diff --git a/drivers/mfd/wcd9310-irq.c b/drivers/mfd/wcd9310-irq.c index fef13230286..c6a9c23cbf8 100644 --- a/drivers/mfd/wcd9310-irq.c +++ b/drivers/mfd/wcd9310-irq.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,60 @@ static struct irq_chip tabla_irq_chip = { .irq_enable = tabla_irq_enable, }; +enum tabla_pm_state tabla_pm_cmpxchg(struct tabla *tabla, enum tabla_pm_state o, + enum tabla_pm_state n) +{ + enum tabla_pm_state old; + mutex_lock(&tabla->pm_lock); + old = tabla->pm_state; + if (old == o) + tabla->pm_state = n; + mutex_unlock(&tabla->pm_lock); + return old; +} +EXPORT_SYMBOL_GPL(tabla_pm_cmpxchg); + +void tabla_lock_sleep(struct tabla *tabla) +{ + enum tabla_pm_state os; + + /* tabla_{lock/unlock}_sleep will be called by tabla_irq_thread + * and its subroutines only motly. + * but btn0_lpress_fn is not tabla_irq_thread's subroutine and + * it can race with tabla_irq_thread. + * so need to embrace wlock_holders with mutex. + */ + mutex_lock(&tabla->pm_lock); + if (tabla->wlock_holders++ == 0) + wake_lock(&tabla->wlock); + mutex_unlock(&tabla->pm_lock); + while (!wait_event_timeout(tabla->pm_wq, + ((os = tabla_pm_cmpxchg(tabla, TABLA_PM_SLEEPABLE, + TABLA_PM_AWAKE)) == + TABLA_PM_SLEEPABLE || + (os == TABLA_PM_AWAKE)), + 5 * HZ)) { + pr_err("%s: system didn't resume within 5000ms, state %d, " + "wlock %d\n", __func__, tabla->pm_state, + tabla->wlock_holders); + WARN_ON_ONCE(1); + } + wake_up_all(&tabla->pm_wq); +} +EXPORT_SYMBOL_GPL(tabla_lock_sleep); + +void tabla_unlock_sleep(struct tabla *tabla) +{ + mutex_lock(&tabla->pm_lock); + if (--tabla->wlock_holders == 0) { + tabla->pm_state = TABLA_PM_SLEEPABLE; + wake_unlock(&tabla->wlock); + } + mutex_unlock(&tabla->pm_lock); + wake_up_all(&tabla->pm_wq); +} +EXPORT_SYMBOL_GPL(tabla_unlock_sleep); + static irqreturn_t tabla_irq_thread(int irq, void *data) { int ret; @@ -90,11 +145,13 @@ static irqreturn_t tabla_irq_thread(int irq, void *data) u8 status[TABLA_NUM_IRQ_REGS]; unsigned int i; + tabla_lock_sleep(tabla); ret = tabla_bulk_read(tabla, TABLA_A_INTR_STATUS0, TABLA_NUM_IRQ_REGS, status); if (ret < 0) { dev_err(tabla->dev, "Failed to read interrupt status: %d\n", ret); + tabla_unlock_sleep(tabla); return IRQ_NONE; } /* Apply masking */ @@ -127,6 +184,7 @@ static irqreturn_t tabla_irq_thread(int irq, void *data) break; } } + tabla_unlock_sleep(tabla); return IRQ_HANDLED; } diff --git a/include/linux/mfd/wcd9310/core.h b/include/linux/mfd/wcd9310/core.h index 982803d1ec5..8605ac6076e 100644 --- a/include/linux/mfd/wcd9310/core.h +++ b/include/linux/mfd/wcd9310/core.h @@ -14,6 +14,7 @@ #define __MFD_TABLA_CORE_H__ #include +#include #define TABLA_NUM_IRQ_REGS 3 @@ -55,6 +56,12 @@ enum { TABLA_NUM_IRQS, }; +enum tabla_pm_state { + TABLA_PM_SLEEPABLE, + TABLA_PM_AWAKE, + TABLA_PM_ASLEEP, +}; + struct tabla { struct device *dev; struct slim_device *slim; @@ -78,23 +85,33 @@ struct tabla { int bytes, void *src, bool interface_reg); struct regulator_bulk_data *supplies; + + enum tabla_pm_state pm_state; + struct mutex pm_lock; + /* pm_wq notifies change of pm_state */ + wait_queue_head_t pm_wq; + struct wake_lock wlock; + int wlock_holders; }; int tabla_reg_read(struct tabla *tabla, unsigned short reg); -int tabla_reg_write(struct tabla *tabla, unsigned short reg, - u8 val); +int tabla_reg_write(struct tabla *tabla, unsigned short reg, u8 val); int tabla_interface_reg_read(struct tabla *tabla, unsigned short reg); -int tabla_interface_reg_write(struct tabla *tabla, unsigned short reg, - u8 val); -int tabla_bulk_read(struct tabla *tabla, unsigned short reg, - int count, u8 *buf); -int tabla_bulk_write(struct tabla *tabla, unsigned short reg, - int count, u8 *buf); +int tabla_interface_reg_write(struct tabla *tabla, unsigned short reg, u8 val); +int tabla_bulk_read(struct tabla *tabla, unsigned short reg, int count, + u8 *buf); +int tabla_bulk_write(struct tabla *tabla, unsigned short reg, int count, + u8 *buf); int tabla_irq_init(struct tabla *tabla); void tabla_irq_exit(struct tabla *tabla); int tabla_get_logical_addresses(u8 *pgd_la, u8 *inf_la); int tabla_get_intf_type(void); +void tabla_lock_sleep(struct tabla *tabla); +void tabla_unlock_sleep(struct tabla *tabla); +enum tabla_pm_state tabla_pm_cmpxchg(struct tabla *tabla, enum tabla_pm_state o, + enum tabla_pm_state n); + static inline int tabla_request_irq(struct tabla *tabla, int irq, irq_handler_t handler, const char *name, void *data) diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c index d4e9ecf077b..7c2073d1e5f 100644 --- a/sound/soc/codecs/wcd9310.c +++ b/sound/soc/codecs/wcd9310.c @@ -185,11 +185,6 @@ struct tabla_priv { struct work_struct hphlocp_work; /* reporting left hph ocp off */ struct work_struct hphrocp_work; /* reporting right hph ocp off */ - /* pm_cnt holds number of sleep lock holders + 1 - * so if pm_cnt is 1 system is sleep-able. */ - atomic_t pm_cnt; - wait_queue_head_t pm_wq; - u8 hphlocp_cnt; /* headphone left ocp retry */ u8 hphrocp_cnt; /* headphone right ocp retry */ @@ -3331,24 +3326,6 @@ static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec, return 0; } -static void tabla_lock_sleep(struct tabla_priv *tabla) -{ - int ret; - while (!(ret = wait_event_timeout(tabla->pm_wq, - atomic_inc_not_zero(&tabla->pm_cnt), - 2 * HZ))) { - pr_err("%s: didn't wake up for 2000ms (%d), pm_cnt %d\n", - __func__, ret, atomic_read(&tabla->pm_cnt)); - WARN_ON_ONCE(1); - } -} - -static void tabla_unlock_sleep(struct tabla_priv *tabla) -{ - atomic_dec(&tabla->pm_cnt); - wake_up(&tabla->pm_wq); -} - static u16 tabla_codec_v_sta_dce(struct snd_soc_codec *codec, bool dce, s16 vin_mv) { @@ -3403,11 +3380,13 @@ static void btn0_lpress_fn(struct work_struct *work) struct tabla_priv *tabla; short bias_value; int dce_mv, sta_mv; + struct tabla *core; pr_debug("%s:\n", __func__); delayed_work = to_delayed_work(work); tabla = container_of(delayed_work, struct tabla_priv, btn0_dwork); + core = dev_get_drvdata(tabla->codec->dev->parent); if (tabla) { if (tabla->button_jack) { @@ -3428,7 +3407,7 @@ static void btn0_lpress_fn(struct work_struct *work) pr_err("%s: Bad tabla private data\n", __func__); } - tabla_unlock_sleep(tabla); + tabla_unlock_sleep(core); } void tabla_mbhc_cal(struct snd_soc_codec *codec) @@ -3877,10 +3856,10 @@ static irqreturn_t tabla_dce_handler(int irq, void *data) TABLA_MBHC_CAL_BTN_DET_PTR(priv->calibration); short btnmeas[d->n_btn_meas + 1]; struct snd_soc_codec *codec = priv->codec; + struct tabla *core = dev_get_drvdata(priv->codec->dev->parent); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); - tabla_lock_sleep(priv); bias_value_dce = tabla_codec_read_dce_result(codec); bias_mv_dce = tabla_codec_sta_dce_v(codec, 1, bias_value_dce); @@ -3926,11 +3905,12 @@ static irqreturn_t tabla_dce_handler(int irq, void *data) /* XXX: assuming button 0 has the lowest micbias voltage */ if (btn == 0) { + tabla_lock_sleep(core); if (schedule_delayed_work(&priv->btn0_dwork, msecs_to_jiffies(400)) == 0) { WARN(1, "Button pressed twice without release" "event\n"); - tabla_unlock_sleep(priv); + tabla_unlock_sleep(core); } } else { pr_debug("%s: Reporting short button %d(0x%x) press\n", @@ -3938,23 +3918,24 @@ static irqreturn_t tabla_dce_handler(int irq, void *data) tabla_snd_soc_jack_report(priv, priv->button_jack, mask, mask); } - } else + } else { pr_debug("%s: bogus button press, too short press?\n", __func__); + } return IRQ_HANDLED; } static irqreturn_t tabla_release_handler(int irq, void *data) { - struct tabla_priv *priv = data; - struct snd_soc_codec *codec = priv->codec; int ret; short mb_v; + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + struct tabla *core = dev_get_drvdata(priv->codec->dev->parent); pr_debug("%s: enter\n", __func__); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE); - tabla_lock_sleep(priv); if (priv->buttons_pressed & SND_JACK_BTN_0) { ret = cancel_delayed_work(&priv->btn0_dwork); @@ -3968,7 +3949,7 @@ static irqreturn_t tabla_release_handler(int irq, void *data) } else { /* if scheduled btn0_dwork is canceled from here, * we have to unlock from here instead btn0_work */ - tabla_unlock_sleep(priv); + tabla_unlock_sleep(core); mb_v = tabla_codec_sta_dce(codec, 0); pr_debug("%s: Mic Voltage on release STA: %d,%d\n", __func__, mb_v, @@ -4006,7 +3987,6 @@ static irqreturn_t tabla_release_handler(int irq, void *data) } tabla_codec_start_hs_polling(codec); - tabla_unlock_sleep(priv); return IRQ_HANDLED; } @@ -4157,7 +4137,6 @@ static irqreturn_t tabla_hs_insert_irq(int irq, void *data) pr_debug("%s: enter\n", __func__); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); - tabla_lock_sleep(priv); is_removal = snd_soc_read(codec, TABLA_A_CDC_MBHC_INT_CTL) & 0x02; snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x03, 0x00); @@ -4229,7 +4208,6 @@ static irqreturn_t tabla_hs_insert_irq(int irq, void *data) } tabla_codec_shutdown_hs_removal_detect(codec); tabla_codec_enable_hs_detect(codec, 1); - tabla_unlock_sleep(priv); return IRQ_HANDLED; } @@ -4308,7 +4286,6 @@ static irqreturn_t tabla_hs_insert_irq(int irq, void *data) tabla_sync_hph_state(priv); } - tabla_unlock_sleep(priv); return IRQ_HANDLED; } @@ -4326,7 +4303,6 @@ static irqreturn_t tabla_hs_remove_irq(int irq, void *data) tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE); - tabla_lock_sleep(priv); usleep_range(generic->t_shutdown_plug_rem, generic->t_shutdown_plug_rem); @@ -4364,7 +4340,6 @@ static irqreturn_t tabla_hs_remove_irq(int irq, void *data) tabla_codec_enable_hs_detect(codec, 1); } - tabla_unlock_sleep(priv); return IRQ_HANDLED; } @@ -4377,8 +4352,6 @@ static irqreturn_t tabla_slimbus_irq(int irq, void *data) int i, j; u8 val; - tabla_lock_sleep(priv); - for (i = 0; i < TABLA_SLIM_NUM_PORT_REG; i++) { slimbus_value = tabla_interface_reg_read(codec->control_data, TABLA_SLIM_PGD_PORT_INT_STATUS0 + i); @@ -4396,7 +4369,6 @@ static irqreturn_t tabla_slimbus_irq(int irq, void *data) TABLA_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); } - tabla_unlock_sleep(priv); return IRQ_HANDLED; } @@ -4742,8 +4714,6 @@ static int tabla_codec_probe(struct snd_soc_codec *codec) tabla->codec = codec; tabla->pdata = dev_get_platdata(codec->dev->parent); tabla->intf_type = tabla_get_intf_type(); - atomic_set(&tabla->pm_cnt, 1); - init_waitqueue_head(&tabla->pm_wq); tabla_update_reg_address(tabla); tabla_update_reg_defaults(codec); @@ -4941,7 +4911,6 @@ static ssize_t codec_debug_write(struct file *filp, buf = (char *)lbuf; debug_tabla_priv->no_mic_headset_override = (*strsep(&buf, " ") == '0') ? false : true; - return rc; } @@ -4954,53 +4923,14 @@ static const struct file_operations codec_debug_ops = { #ifdef CONFIG_PM static int tabla_suspend(struct device *dev) { - int ret = 0, cnt; - struct platform_device *pdev = to_platform_device(dev); - struct tabla_priv *tabla = platform_get_drvdata(pdev); - - cnt = atomic_read(&tabla->pm_cnt); - if (cnt > 0) { - if (wait_event_timeout(tabla->pm_wq, - (atomic_cmpxchg(&tabla->pm_cnt, 1, 0) - == 1), 5 * HZ)) { - dev_dbg(dev, "system suspend pm_cnt %d\n", - atomic_read(&tabla->pm_cnt)); - } else { - dev_err(dev, "%s timed out pm_cnt = %d\n", - __func__, atomic_read(&tabla->pm_cnt)); - WARN_ON_ONCE(1); - ret = -EBUSY; - } - } else if (cnt == 0) - dev_warn(dev, "system is already in suspend, pm_cnt %d\n", - atomic_read(&tabla->pm_cnt)); - else { - WARN(1, "unexpected pm_cnt %d\n", cnt); - ret = -EFAULT; - } - - return ret; + dev_dbg(dev, "%s: system suspend\n", __func__); + return 0; } static int tabla_resume(struct device *dev) { - int ret = 0, cnt; - struct platform_device *pdev = to_platform_device(dev); - struct tabla_priv *tabla = platform_get_drvdata(pdev); - - cnt = atomic_cmpxchg(&tabla->pm_cnt, 0, 1); - if (cnt == 0) { - dev_dbg(dev, "system resume, pm_cnt %d\n", - atomic_read(&tabla->pm_cnt)); - wake_up_all(&tabla->pm_wq); - } else if (cnt > 0) - dev_warn(dev, "system is already awake, pm_cnt %d\n", cnt); - else { - WARN(1, "unexpected pm_cnt %d\n", cnt); - ret = -EFAULT; - } - - return ret; + dev_dbg(dev, "%s: system resume\n", __func__); + return 0; } static const struct dev_pm_ops tabla_pm_ops = {