media: video-i2c: support runtime PM

AMG88xx has a register for setting operating mode.  This adds support
runtime PM by changing the operating mode.

The instruction for changing sleep mode to normal mode is from the
reference specifications.

https://docid81hrs3j1.cloudfront.net/medialibrary/2017/11/PANA-S-A0002141979-1.pdf

Reviewed-by: Matt Ranostay <matt.ranostay@konsulko.com>
Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
This commit is contained in:
Akinobu Mita 2018-11-22 11:13:56 -05:00 committed by Mauro Carvalho Chehab
parent e0fe5c7b22
commit 69d2a734c5

View File

@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/sched.h>
#include <linux/slab.h>
@ -94,10 +95,23 @@ struct video_i2c_chip {
/* xfer function */
int (*xfer)(struct video_i2c_data *data, char *buf);
/* power control function */
int (*set_power)(struct video_i2c_data *data, bool on);
/* hwmon init function */
int (*hwmon_init)(struct video_i2c_data *data);
};
/* Power control register */
#define AMG88XX_REG_PCTL 0x00
#define AMG88XX_PCTL_NORMAL 0x00
#define AMG88XX_PCTL_SLEEP 0x10
/* Reset register */
#define AMG88XX_REG_RST 0x01
#define AMG88XX_RST_FLAG 0x30
#define AMG88XX_RST_INIT 0x3f
/* Frame rate register */
#define AMG88XX_REG_FPSC 0x02
#define AMG88XX_FPSC_1FPS BIT(0)
@ -127,6 +141,59 @@ static int amg88xx_setup(struct video_i2c_data *data)
return regmap_update_bits(data->regmap, AMG88XX_REG_FPSC, mask, val);
}
static int amg88xx_set_power_on(struct video_i2c_data *data)
{
int ret;
ret = regmap_write(data->regmap, AMG88XX_REG_PCTL, AMG88XX_PCTL_NORMAL);
if (ret)
return ret;
msleep(50);
ret = regmap_write(data->regmap, AMG88XX_REG_RST, AMG88XX_RST_INIT);
if (ret)
return ret;
usleep_range(2000, 3000);
ret = regmap_write(data->regmap, AMG88XX_REG_RST, AMG88XX_RST_FLAG);
if (ret)
return ret;
/*
* Wait two frames before reading thermistor and temperature registers
*/
msleep(200);
return 0;
}
static int amg88xx_set_power_off(struct video_i2c_data *data)
{
int ret;
ret = regmap_write(data->regmap, AMG88XX_REG_PCTL, AMG88XX_PCTL_SLEEP);
if (ret)
return ret;
/*
* Wait for a while to avoid resuming normal mode immediately after
* entering sleep mode, otherwise the device occasionally goes wrong
* (thermistor and temperature registers are not updated at all)
*/
msleep(100);
return 0;
}
static int amg88xx_set_power(struct video_i2c_data *data, bool on)
{
if (on)
return amg88xx_set_power_on(data);
return amg88xx_set_power_off(data);
}
#if IS_ENABLED(CONFIG_HWMON)
static const u32 amg88xx_temp_config[] = {
@ -158,7 +225,15 @@ static int amg88xx_read(struct device *dev, enum hwmon_sensor_types type,
__le16 buf;
int tmp;
tmp = pm_runtime_get_sync(regmap_get_device(data->regmap));
if (tmp < 0) {
pm_runtime_put_noidle(regmap_get_device(data->regmap));
return tmp;
}
tmp = regmap_bulk_read(data->regmap, AMG88XX_REG_TTHL, &buf, 2);
pm_runtime_mark_last_busy(regmap_get_device(data->regmap));
pm_runtime_put_autosuspend(regmap_get_device(data->regmap));
if (tmp)
return tmp;
@ -217,6 +292,7 @@ static const struct video_i2c_chip video_i2c_chip[] = {
.regmap_config = &amg88xx_regmap_config,
.setup = &amg88xx_setup,
.xfer = &amg88xx_xfer,
.set_power = amg88xx_set_power,
.hwmon_init = amg88xx_hwmon_init,
},
};
@ -343,14 +419,21 @@ static void video_i2c_del_list(struct vb2_queue *vq, enum vb2_buffer_state state
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct video_i2c_data *data = vb2_get_drv_priv(vq);
struct device *dev = regmap_get_device(data->regmap);
int ret;
if (data->kthread_vid_cap)
return 0;
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
pm_runtime_put_noidle(dev);
goto error_del_list;
}
ret = data->chip->setup(data);
if (ret)
goto error_del_list;
goto error_rpm_put;
data->sequence = 0;
data->kthread_vid_cap = kthread_run(video_i2c_thread_vid_cap, data,
@ -359,6 +442,9 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count)
if (!ret)
return 0;
error_rpm_put:
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
error_del_list:
video_i2c_del_list(vq, VB2_BUF_STATE_QUEUED);
@ -374,6 +460,8 @@ static void stop_streaming(struct vb2_queue *vq)
kthread_stop(data->kthread_vid_cap);
data->kthread_vid_cap = NULL;
pm_runtime_mark_last_busy(regmap_get_device(data->regmap));
pm_runtime_put_autosuspend(regmap_get_device(data->regmap));
video_i2c_del_list(vq, VB2_BUF_STATE_ERROR);
}
@ -648,6 +736,16 @@ static int video_i2c_probe(struct i2c_client *client,
video_set_drvdata(&data->vdev, data);
i2c_set_clientdata(client, data);
ret = data->chip->set_power(data, true);
if (ret)
goto error_unregister_device;
pm_runtime_get_noresume(&client->dev);
pm_runtime_set_active(&client->dev);
pm_runtime_enable(&client->dev);
pm_runtime_set_autosuspend_delay(&client->dev, 2000);
pm_runtime_use_autosuspend(&client->dev);
if (data->chip->hwmon_init) {
ret = data->chip->hwmon_init(data);
if (ret < 0) {
@ -658,10 +756,19 @@ static int video_i2c_probe(struct i2c_client *client,
ret = video_register_device(&data->vdev, VFL_TYPE_GRABBER, -1);
if (ret < 0)
goto error_unregister_device;
goto error_pm_disable;
pm_runtime_mark_last_busy(&client->dev);
pm_runtime_put_autosuspend(&client->dev);
return 0;
error_pm_disable:
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
pm_runtime_put_noidle(&client->dev);
data->chip->set_power(data, false);
error_unregister_device:
v4l2_device_unregister(v4l2_dev);
mutex_destroy(&data->lock);
@ -680,11 +787,40 @@ static int video_i2c_remove(struct i2c_client *client)
{
struct video_i2c_data *data = i2c_get_clientdata(client);
pm_runtime_get_sync(&client->dev);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
pm_runtime_put_noidle(&client->dev);
data->chip->set_power(data, false);
video_unregister_device(&data->vdev);
return 0;
}
#ifdef CONFIG_PM
static int video_i2c_pm_runtime_suspend(struct device *dev)
{
struct video_i2c_data *data = i2c_get_clientdata(to_i2c_client(dev));
return data->chip->set_power(data, false);
}
static int video_i2c_pm_runtime_resume(struct device *dev)
{
struct video_i2c_data *data = i2c_get_clientdata(to_i2c_client(dev));
return data->chip->set_power(data, true);
}
#endif
static const struct dev_pm_ops video_i2c_pm_ops = {
SET_RUNTIME_PM_OPS(video_i2c_pm_runtime_suspend,
video_i2c_pm_runtime_resume, NULL)
};
static const struct i2c_device_id video_i2c_id_table[] = {
{ "amg88xx", AMG88XX },
{}
@ -701,6 +837,7 @@ static struct i2c_driver video_i2c_driver = {
.driver = {
.name = VIDEO_I2C_DRIVER,
.of_match_table = video_i2c_of_match,
.pm = &video_i2c_pm_ops,
},
.probe = video_i2c_probe,
.remove = video_i2c_remove,