Commit 6827ca57 authored by Kai-Heng Feng's avatar Kai-Heng Feng Committed by Ulf Hansson

memstick: rtsx_usb_ms: Support runtime power management

In order to let host's parent device, rtsx_usb, to use USB remote wake
up signaling to do card detection, it needs to be suspended. Hence it's
necessary to add runtime PM support for the memstick host.

To keep memstick host stays suspended when it's not in use, convert the
card detection function from kthread to delayed_work, which can be
scheduled when the host is resumed and can be canceled when the host is
suspended.

Put the device to suspend when there's no card and the power mode is
MEMSTICK_POWER_OFF.
Signed-off-by: default avatarKai-Heng Feng <kai.heng.feng@canonical.com>
Tested-by: default avatarOleksandr Natalenko <oleksandr@natalenko.name>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent ba9d5f83
...@@ -40,15 +40,14 @@ struct rtsx_usb_ms { ...@@ -40,15 +40,14 @@ struct rtsx_usb_ms {
struct mutex host_mutex; struct mutex host_mutex;
struct work_struct handle_req; struct work_struct handle_req;
struct delayed_work poll_card;
struct task_struct *detect_ms;
struct completion detect_ms_exit;
u8 ssc_depth; u8 ssc_depth;
unsigned int clock; unsigned int clock;
int power_mode; int power_mode;
unsigned char ifmode; unsigned char ifmode;
bool eject; bool eject;
bool system_suspending;
}; };
static inline struct device *ms_dev(struct rtsx_usb_ms *host) static inline struct device *ms_dev(struct rtsx_usb_ms *host)
...@@ -545,7 +544,7 @@ static void rtsx_usb_ms_handle_req(struct work_struct *work) ...@@ -545,7 +544,7 @@ static void rtsx_usb_ms_handle_req(struct work_struct *work)
host->req->error); host->req->error);
} }
} while (!rc); } while (!rc);
pm_runtime_put(ms_dev(host)); pm_runtime_put_sync(ms_dev(host));
} }
} }
...@@ -585,14 +584,14 @@ static int rtsx_usb_ms_set_param(struct memstick_host *msh, ...@@ -585,14 +584,14 @@ static int rtsx_usb_ms_set_param(struct memstick_host *msh,
break; break;
if (value == MEMSTICK_POWER_ON) { if (value == MEMSTICK_POWER_ON) {
pm_runtime_get_sync(ms_dev(host)); pm_runtime_get_noresume(ms_dev(host));
err = ms_power_on(host); err = ms_power_on(host);
if (err)
pm_runtime_put_noidle(ms_dev(host));
} else if (value == MEMSTICK_POWER_OFF) { } else if (value == MEMSTICK_POWER_OFF) {
err = ms_power_off(host); err = ms_power_off(host);
if (host->msh->card) if (!err)
pm_runtime_put_noidle(ms_dev(host)); pm_runtime_put_noidle(ms_dev(host));
else
pm_runtime_put(ms_dev(host));
} else } else
err = -EINVAL; err = -EINVAL;
if (!err) if (!err)
...@@ -638,12 +637,16 @@ static int rtsx_usb_ms_set_param(struct memstick_host *msh, ...@@ -638,12 +637,16 @@ static int rtsx_usb_ms_set_param(struct memstick_host *msh,
} }
out: out:
mutex_unlock(&ucr->dev_mutex); mutex_unlock(&ucr->dev_mutex);
pm_runtime_put(ms_dev(host)); pm_runtime_put_sync(ms_dev(host));
/* power-on delay */ /* power-on delay */
if (param == MEMSTICK_POWER && value == MEMSTICK_POWER_ON) if (param == MEMSTICK_POWER && value == MEMSTICK_POWER_ON) {
usleep_range(10000, 12000); usleep_range(10000, 12000);
if (!host->eject)
schedule_delayed_work(&host->poll_card, 100);
}
dev_dbg(ms_dev(host), "%s: return = %d\n", __func__, err); dev_dbg(ms_dev(host), "%s: return = %d\n", __func__, err);
return err; return err;
} }
...@@ -654,9 +657,24 @@ static int rtsx_usb_ms_suspend(struct device *dev) ...@@ -654,9 +657,24 @@ static int rtsx_usb_ms_suspend(struct device *dev)
struct rtsx_usb_ms *host = dev_get_drvdata(dev); struct rtsx_usb_ms *host = dev_get_drvdata(dev);
struct memstick_host *msh = host->msh; struct memstick_host *msh = host->msh;
dev_dbg(ms_dev(host), "--> %s\n", __func__); /* Since we use rtsx_usb's resume callback to runtime resume its
* children to implement remote wakeup signaling, this causes
* rtsx_usb_ms' runtime resume callback runs after its suspend
* callback:
* rtsx_usb_ms_suspend()
* rtsx_usb_resume()
* -> rtsx_usb_ms_runtime_resume()
* -> memstick_detect_change()
*
* rtsx_usb_suspend()
*
* To avoid this, skip runtime resume/suspend if system suspend is
* underway.
*/
host->system_suspending = true;
memstick_suspend_host(msh); memstick_suspend_host(msh);
return 0; return 0;
} }
...@@ -665,26 +683,58 @@ static int rtsx_usb_ms_resume(struct device *dev) ...@@ -665,26 +683,58 @@ static int rtsx_usb_ms_resume(struct device *dev)
struct rtsx_usb_ms *host = dev_get_drvdata(dev); struct rtsx_usb_ms *host = dev_get_drvdata(dev);
struct memstick_host *msh = host->msh; struct memstick_host *msh = host->msh;
dev_dbg(ms_dev(host), "--> %s\n", __func__);
memstick_resume_host(msh); memstick_resume_host(msh);
host->system_suspending = false;
return 0; return 0;
} }
#endif /* CONFIG_PM_SLEEP */ #endif /* CONFIG_PM_SLEEP */
/* #ifdef CONFIG_PM
* Thread function of ms card slot detection. The thread starts right after static int rtsx_usb_ms_runtime_suspend(struct device *dev)
* successful host addition. It stops while the driver removal function sets
* host->eject true.
*/
static int rtsx_usb_detect_ms_card(void *__host)
{ {
struct rtsx_usb_ms *host = (struct rtsx_usb_ms *)__host; struct rtsx_usb_ms *host = dev_get_drvdata(dev);
if (host->system_suspending)
return 0;
if (host->msh->card || host->power_mode != MEMSTICK_POWER_OFF)
return -EAGAIN;
return 0;
}
static int rtsx_usb_ms_runtime_resume(struct device *dev)
{
struct rtsx_usb_ms *host = dev_get_drvdata(dev);
if (host->system_suspending)
return 0;
memstick_detect_change(host->msh);
return 0;
}
#endif /* CONFIG_PM */
static const struct dev_pm_ops rtsx_usb_ms_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rtsx_usb_ms_suspend, rtsx_usb_ms_resume)
SET_RUNTIME_PM_OPS(rtsx_usb_ms_runtime_suspend, rtsx_usb_ms_runtime_resume, NULL)
};
static void rtsx_usb_ms_poll_card(struct work_struct *work)
{
struct rtsx_usb_ms *host = container_of(work, struct rtsx_usb_ms,
poll_card.work);
struct rtsx_ucr *ucr = host->ucr; struct rtsx_ucr *ucr = host->ucr;
u8 val = 0;
int err; int err;
u8 val;
if (host->eject || host->power_mode != MEMSTICK_POWER_ON)
return;
for (;;) {
pm_runtime_get_sync(ms_dev(host)); pm_runtime_get_sync(ms_dev(host));
mutex_lock(&ucr->dev_mutex); mutex_lock(&ucr->dev_mutex);
...@@ -708,15 +758,10 @@ static int rtsx_usb_detect_ms_card(void *__host) ...@@ -708,15 +758,10 @@ static int rtsx_usb_detect_ms_card(void *__host)
} }
poll_again: poll_again:
pm_runtime_put(ms_dev(host)); pm_runtime_put_sync(ms_dev(host));
if (host->eject)
break;
schedule_timeout_idle(HZ);
}
complete(&host->detect_ms_exit); if (!host->eject && host->power_mode == MEMSTICK_POWER_ON)
return 0; schedule_delayed_work(&host->poll_card, 100);
} }
static int rtsx_usb_ms_drv_probe(struct platform_device *pdev) static int rtsx_usb_ms_drv_probe(struct platform_device *pdev)
...@@ -747,40 +792,36 @@ static int rtsx_usb_ms_drv_probe(struct platform_device *pdev) ...@@ -747,40 +792,36 @@ static int rtsx_usb_ms_drv_probe(struct platform_device *pdev)
mutex_init(&host->host_mutex); mutex_init(&host->host_mutex);
INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req); INIT_WORK(&host->handle_req, rtsx_usb_ms_handle_req);
init_completion(&host->detect_ms_exit); INIT_DELAYED_WORK(&host->poll_card, rtsx_usb_ms_poll_card);
host->detect_ms = kthread_create(rtsx_usb_detect_ms_card, host,
"rtsx_usb_ms_%d", pdev->id);
if (IS_ERR(host->detect_ms)) {
dev_dbg(&(pdev->dev),
"Unable to create polling thread.\n");
err = PTR_ERR(host->detect_ms);
goto err_out;
}
msh->request = rtsx_usb_ms_request; msh->request = rtsx_usb_ms_request;
msh->set_param = rtsx_usb_ms_set_param; msh->set_param = rtsx_usb_ms_set_param;
msh->caps = MEMSTICK_CAP_PAR4; msh->caps = MEMSTICK_CAP_PAR4;
pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(ms_dev(host));
pm_runtime_set_active(ms_dev(host));
pm_runtime_enable(ms_dev(host));
err = memstick_add_host(msh); err = memstick_add_host(msh);
if (err) if (err)
goto err_out; goto err_out;
wake_up_process(host->detect_ms); pm_runtime_put(ms_dev(host));
return 0; return 0;
err_out: err_out:
memstick_free_host(msh); memstick_free_host(msh);
pm_runtime_disable(ms_dev(host)); pm_runtime_disable(ms_dev(host));
pm_runtime_put_noidle(ms_dev(host));
return err; return err;
} }
static int rtsx_usb_ms_drv_remove(struct platform_device *pdev) static int rtsx_usb_ms_drv_remove(struct platform_device *pdev)
{ {
struct rtsx_usb_ms *host = platform_get_drvdata(pdev); struct rtsx_usb_ms *host = platform_get_drvdata(pdev);
struct memstick_host *msh; struct memstick_host *msh = host->msh;
int err; int err;
msh = host->msh;
host->eject = true; host->eject = true;
cancel_work_sync(&host->handle_req); cancel_work_sync(&host->handle_req);
...@@ -798,7 +839,6 @@ static int rtsx_usb_ms_drv_remove(struct platform_device *pdev) ...@@ -798,7 +839,6 @@ static int rtsx_usb_ms_drv_remove(struct platform_device *pdev)
} }
mutex_unlock(&host->host_mutex); mutex_unlock(&host->host_mutex);
wait_for_completion(&host->detect_ms_exit);
memstick_remove_host(msh); memstick_remove_host(msh);
memstick_free_host(msh); memstick_free_host(msh);
...@@ -817,9 +857,6 @@ static int rtsx_usb_ms_drv_remove(struct platform_device *pdev) ...@@ -817,9 +857,6 @@ static int rtsx_usb_ms_drv_remove(struct platform_device *pdev)
return 0; return 0;
} }
static SIMPLE_DEV_PM_OPS(rtsx_usb_ms_pm_ops,
rtsx_usb_ms_suspend, rtsx_usb_ms_resume);
static struct platform_device_id rtsx_usb_ms_ids[] = { static struct platform_device_id rtsx_usb_ms_ids[] = {
{ {
.name = "rtsx_usb_ms", .name = "rtsx_usb_ms",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment