Commit 69d2a734 authored by Akinobu Mita's avatar Akinobu Mita Committed by Mauro Carvalho Chehab
Browse files

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: default avatarMatt Ranostay <matt.ranostay@konsulko.com>
Acked-by: default avatarSakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: default avatarAkinobu Mita <akinobu.mita@gmail.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+samsung@kernel.org>
parent e0fe5c7b
Loading
Loading
Loading
Loading
+139 −2
Original line number Diff line number Diff line
@@ -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,