mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 04:10:18 +09:00
media: i2c: add dummy sensor for preisp
Change-Id: Ifad0062a8fa01e9e005bda2b8b4e73d09b4de925 Signed-off-by: Hu Kejun <william.hu@rock-chips.com>
This commit is contained in:
@@ -870,6 +870,15 @@ config VIDEO_GC2035
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called gc2035.
|
||||
|
||||
config VIDEO_PREISP_DUMMY_SENSOR
|
||||
tristate "Preisp dummy sensor support"
|
||||
depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API
|
||||
help
|
||||
Support for the preisp dummy sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called pisp_dmy.
|
||||
|
||||
comment "Flash devices"
|
||||
|
||||
config VIDEO_ADP1653
|
||||
|
||||
@@ -103,5 +103,6 @@ obj-$(CONFIG_VIDEO_GC2035) += gc2035.o
|
||||
obj-$(CONFIG_VIDEO_GC2145) += gc2355.o
|
||||
obj-$(CONFIG_VIDEO_SC031GS) += sc031gs.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_PREISP_DUMMY_SENSOR) += preisp-dummy.o
|
||||
obj-$(CONFIG_VIDEO_VM149C) += vm149c.o
|
||||
obj-$(CONFIG_VIDEO_VIRT_CAMERA) += virtual-camera.o
|
||||
|
||||
470
drivers/media/i2c/preisp-dummy.c
Normal file
470
drivers/media/i2c/preisp-dummy.c
Normal file
@@ -0,0 +1,470 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* pisp_dmy driver
|
||||
*
|
||||
* Copyright (C) 2019 Fuzhou Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-async.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/v4l2-fwnode.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/rk-preisp.h>
|
||||
|
||||
#ifndef V4L2_CID_DIGITAL_GAIN
|
||||
#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN
|
||||
#endif
|
||||
|
||||
#define PISP_DMY_XVCLK_FREQ 24000000
|
||||
|
||||
#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default"
|
||||
#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep"
|
||||
|
||||
#define OF_CAMERA_MODULE_REGULATORS "rockchip,regulator-names"
|
||||
#define OF_CAMERA_MODULE_REGULATOR_VOLTAGES "rockchip,regulator-voltages"
|
||||
|
||||
struct pisp_dmy_gpio {
|
||||
int pltfrm_gpio;
|
||||
const char *label;
|
||||
enum of_gpio_flags active_low;
|
||||
};
|
||||
|
||||
struct pisp_dmy_regulator {
|
||||
struct regulator *regulator;
|
||||
u32 min_uV;
|
||||
u32 max_uV;
|
||||
};
|
||||
|
||||
struct pisp_dmy_regulators {
|
||||
u32 cnt;
|
||||
struct pisp_dmy_regulator *regulator;
|
||||
};
|
||||
|
||||
struct pisp_dmy {
|
||||
struct i2c_client *client;
|
||||
struct clk *xvclk;
|
||||
struct gpio_desc *rst_gpio;
|
||||
struct gpio_desc *rst2_gpio;
|
||||
struct gpio_desc *pd_gpio;
|
||||
struct gpio_desc *pd2_gpio;
|
||||
struct gpio_desc *pwd_gpio;
|
||||
struct gpio_desc *pwd2_gpio;
|
||||
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *pins_default;
|
||||
struct pinctrl_state *pins_sleep;
|
||||
|
||||
struct v4l2_subdev subdev;
|
||||
struct media_pad pad;
|
||||
struct mutex mutex;
|
||||
bool power_on;
|
||||
struct pisp_dmy_regulators regulators;
|
||||
};
|
||||
|
||||
#define to_pisp_dmy(sd) container_of(sd, struct pisp_dmy, subdev)
|
||||
|
||||
static int __pisp_dmy_power_on(struct pisp_dmy *pisp_dmy)
|
||||
{
|
||||
u32 i;
|
||||
int ret;
|
||||
struct pisp_dmy_regulator *regulator;
|
||||
struct device *dev = &pisp_dmy->client->dev;
|
||||
|
||||
if (!IS_ERR_OR_NULL(pisp_dmy->pins_default)) {
|
||||
ret = pinctrl_select_state(pisp_dmy->pinctrl,
|
||||
pisp_dmy->pins_default);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "could not set pins. ret=%d\n", ret);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(pisp_dmy->xvclk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to enable xvclk\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pisp_dmy->regulators.regulator) {
|
||||
for (i = 0; i < pisp_dmy->regulators.cnt; i++) {
|
||||
regulator = pisp_dmy->regulators.regulator + i;
|
||||
if (IS_ERR(regulator->regulator))
|
||||
continue;
|
||||
regulator_set_voltage(
|
||||
regulator->regulator,
|
||||
regulator->min_uV,
|
||||
regulator->max_uV);
|
||||
if (regulator_enable(regulator->regulator)) {
|
||||
dev_err(dev,
|
||||
"regulator_enable failed!\n");
|
||||
goto disable_clk;
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep_range(3000, 5000);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pwd_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->pwd_gpio, 1);
|
||||
usleep_range(3000, 5000);
|
||||
}
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pwd2_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->pwd2_gpio, 1);
|
||||
usleep_range(3000, 5000);
|
||||
}
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pd_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->pd_gpio, 1);
|
||||
usleep_range(1500, 2000);
|
||||
}
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pd2_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->pd2_gpio, 1);
|
||||
usleep_range(1500, 2000);
|
||||
}
|
||||
|
||||
if (!IS_ERR(pisp_dmy->rst_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->rst_gpio, 0);
|
||||
usleep_range(1500, 2000);
|
||||
gpiod_direction_output(pisp_dmy->rst_gpio, 1);
|
||||
}
|
||||
|
||||
if (!IS_ERR(pisp_dmy->rst2_gpio)) {
|
||||
gpiod_direction_output(pisp_dmy->rst2_gpio, 0);
|
||||
usleep_range(1500, 2000);
|
||||
gpiod_direction_output(pisp_dmy->rst2_gpio, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk:
|
||||
clk_disable_unprepare(pisp_dmy->xvclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __pisp_dmy_power_off(struct pisp_dmy *pisp_dmy)
|
||||
{
|
||||
u32 i;
|
||||
int ret;
|
||||
struct pisp_dmy_regulator *regulator;
|
||||
struct device *dev = &pisp_dmy->client->dev;
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pd_gpio))
|
||||
gpiod_direction_output(pisp_dmy->pd_gpio, 0);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pd2_gpio))
|
||||
gpiod_direction_output(pisp_dmy->pd2_gpio, 0);
|
||||
|
||||
clk_disable_unprepare(pisp_dmy->xvclk);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->rst_gpio))
|
||||
gpiod_direction_output(pisp_dmy->rst_gpio, 0);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->rst2_gpio))
|
||||
gpiod_direction_output(pisp_dmy->rst2_gpio, 0);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pwd_gpio))
|
||||
gpiod_direction_output(pisp_dmy->pwd_gpio, 0);
|
||||
|
||||
if (!IS_ERR(pisp_dmy->pwd2_gpio))
|
||||
gpiod_direction_output(pisp_dmy->pwd2_gpio, 0);
|
||||
|
||||
if (!IS_ERR_OR_NULL(pisp_dmy->pins_sleep)) {
|
||||
ret = pinctrl_select_state(pisp_dmy->pinctrl,
|
||||
pisp_dmy->pins_sleep);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "could not set pins\n");
|
||||
}
|
||||
|
||||
if (pisp_dmy->regulators.regulator) {
|
||||
for (i = 0; i < pisp_dmy->regulators.cnt; i++) {
|
||||
regulator = pisp_dmy->regulators.regulator + i;
|
||||
if (IS_ERR(regulator->regulator))
|
||||
continue;
|
||||
regulator_disable(regulator->regulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pisp_dmy_power(struct v4l2_subdev *sd, int on)
|
||||
{
|
||||
struct pisp_dmy *pisp_dmy = to_pisp_dmy(sd);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pisp_dmy->mutex);
|
||||
|
||||
/* If the power state is not modified - no work to do. */
|
||||
if (pisp_dmy->power_on == !!on)
|
||||
goto exit;
|
||||
|
||||
if (on) {
|
||||
ret = __pisp_dmy_power_on(pisp_dmy);
|
||||
if (ret < 0)
|
||||
goto exit;
|
||||
|
||||
pisp_dmy->power_on = true;
|
||||
} else {
|
||||
__pisp_dmy_power_off(pisp_dmy);
|
||||
pisp_dmy->power_on = false;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&pisp_dmy->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pisp_dmy_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
struct pisp_dmy *pisp_dmy = to_pisp_dmy(sd);
|
||||
|
||||
return __pisp_dmy_power_on(pisp_dmy);
|
||||
}
|
||||
|
||||
static int pisp_dmy_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
struct pisp_dmy *pisp_dmy = to_pisp_dmy(sd);
|
||||
|
||||
__pisp_dmy_power_off(pisp_dmy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops pisp_dmy_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(pisp_dmy_runtime_suspend,
|
||||
pisp_dmy_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct v4l2_subdev_core_ops pisp_dmy_core_ops = {
|
||||
.s_power = pisp_dmy_power,
|
||||
};
|
||||
|
||||
static const struct v4l2_subdev_ops pisp_dmy_subdev_ops = {
|
||||
.core = &pisp_dmy_core_ops,
|
||||
};
|
||||
|
||||
static int pisp_dmy_analyze_dts(struct pisp_dmy *pisp_dmy)
|
||||
{
|
||||
int ret;
|
||||
int elem_size, elem_index;
|
||||
const char *str = "";
|
||||
struct property *prop;
|
||||
struct pisp_dmy_regulator *regulator;
|
||||
struct device *dev = &pisp_dmy->client->dev;
|
||||
struct device_node *np = of_node_get(dev->of_node);
|
||||
|
||||
pisp_dmy->xvclk = devm_clk_get(dev, "xvclk");
|
||||
if (IS_ERR(pisp_dmy->xvclk)) {
|
||||
dev_err(dev, "Failed to get xvclk\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = clk_set_rate(pisp_dmy->xvclk, PISP_DMY_XVCLK_FREQ);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to set xvclk rate (24MHz)\n");
|
||||
return ret;
|
||||
}
|
||||
if (clk_get_rate(pisp_dmy->xvclk) != PISP_DMY_XVCLK_FREQ)
|
||||
dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n");
|
||||
|
||||
pisp_dmy->pinctrl = devm_pinctrl_get(dev);
|
||||
if (!IS_ERR(pisp_dmy->pinctrl)) {
|
||||
pisp_dmy->pins_default =
|
||||
pinctrl_lookup_state(pisp_dmy->pinctrl,
|
||||
OF_CAMERA_PINCTRL_STATE_DEFAULT);
|
||||
if (IS_ERR(pisp_dmy->pins_default))
|
||||
dev_err(dev, "could not get default pinstate\n");
|
||||
|
||||
pisp_dmy->pins_sleep =
|
||||
pinctrl_lookup_state(pisp_dmy->pinctrl,
|
||||
OF_CAMERA_PINCTRL_STATE_SLEEP);
|
||||
if (IS_ERR(pisp_dmy->pins_sleep))
|
||||
dev_err(dev, "could not get sleep pinstate\n");
|
||||
} else {
|
||||
dev_err(dev, "no pinctrl\n");
|
||||
}
|
||||
|
||||
elem_size = of_property_count_elems_of_size(
|
||||
np,
|
||||
OF_CAMERA_MODULE_REGULATOR_VOLTAGES,
|
||||
sizeof(u32));
|
||||
prop = of_find_property(
|
||||
np,
|
||||
OF_CAMERA_MODULE_REGULATORS,
|
||||
NULL);
|
||||
if (elem_size > 0 && !IS_ERR_OR_NULL(prop)) {
|
||||
pisp_dmy->regulators.regulator =
|
||||
devm_kzalloc(&pisp_dmy->client->dev,
|
||||
elem_size * sizeof(struct pisp_dmy_regulator),
|
||||
GFP_KERNEL);
|
||||
if (!pisp_dmy->regulators.regulator)
|
||||
dev_err(dev, "could not malloc pisp_dmy_regulator\n");
|
||||
|
||||
pisp_dmy->regulators.cnt = elem_size;
|
||||
|
||||
str = NULL;
|
||||
elem_index = 0;
|
||||
regulator = pisp_dmy->regulators.regulator;
|
||||
do {
|
||||
str = of_prop_next_string(prop, str);
|
||||
if (!str) {
|
||||
dev_err(dev, "%s is not match %s in dts\n",
|
||||
OF_CAMERA_MODULE_REGULATORS,
|
||||
OF_CAMERA_MODULE_REGULATOR_VOLTAGES);
|
||||
break;
|
||||
}
|
||||
regulator->regulator =
|
||||
devm_regulator_get_optional(dev, str);
|
||||
if (IS_ERR(regulator->regulator))
|
||||
dev_err(dev, "devm_regulator_get %s failed\n",
|
||||
str);
|
||||
of_property_read_u32_index(
|
||||
np,
|
||||
OF_CAMERA_MODULE_REGULATOR_VOLTAGES,
|
||||
elem_index++,
|
||||
®ulator->min_uV);
|
||||
regulator->max_uV = regulator->min_uV;
|
||||
regulator++;
|
||||
} while (--elem_size);
|
||||
}
|
||||
|
||||
pisp_dmy->pd_gpio = devm_gpiod_get(dev, "pd", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pisp_dmy->pd_gpio))
|
||||
dev_warn(dev, "can not find pd-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->pd_gpio));
|
||||
|
||||
pisp_dmy->pd2_gpio = devm_gpiod_get(dev, "pd2", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pisp_dmy->pd2_gpio))
|
||||
dev_warn(dev, "can not find pd2-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->pd2_gpio));
|
||||
|
||||
pisp_dmy->rst_gpio = devm_gpiod_get(dev, "rst", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pisp_dmy->rst_gpio))
|
||||
dev_warn(dev, "can not find rst-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->rst_gpio));
|
||||
|
||||
pisp_dmy->rst2_gpio = devm_gpiod_get(dev, "rst2", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pisp_dmy->rst2_gpio))
|
||||
dev_warn(dev, "can not find rst2-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->rst2_gpio));
|
||||
|
||||
pisp_dmy->pwd_gpio = devm_gpiod_get(dev, "pwd", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(pisp_dmy->pwd_gpio))
|
||||
dev_warn(dev, "can not find pwd-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->pwd_gpio));
|
||||
|
||||
pisp_dmy->pwd2_gpio = devm_gpiod_get(dev, "pwd2", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(pisp_dmy->pwd2_gpio))
|
||||
dev_warn(dev, "can not find pwd2-gpios, error %ld\n",
|
||||
PTR_ERR(pisp_dmy->pwd2_gpio));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pisp_dmy_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct pisp_dmy *pisp_dmy;
|
||||
struct v4l2_subdev *sd;
|
||||
int ret;
|
||||
|
||||
pisp_dmy = devm_kzalloc(dev, sizeof(*pisp_dmy), GFP_KERNEL);
|
||||
if (!pisp_dmy)
|
||||
return -ENOMEM;
|
||||
|
||||
pisp_dmy->client = client;
|
||||
|
||||
ret = pisp_dmy_analyze_dts(pisp_dmy);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to analyze dts\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&pisp_dmy->mutex);
|
||||
|
||||
sd = &pisp_dmy->subdev;
|
||||
v4l2_i2c_subdev_init(sd, client, &pisp_dmy_subdev_ops);
|
||||
|
||||
__pisp_dmy_power_on(pisp_dmy);
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_idle(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pisp_dmy_remove(struct i2c_client *client)
|
||||
{
|
||||
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
||||
struct pisp_dmy *pisp_dmy = to_pisp_dmy(sd);
|
||||
|
||||
mutex_destroy(&pisp_dmy->mutex);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
if (!pm_runtime_status_suspended(&client->dev))
|
||||
__pisp_dmy_power_off(pisp_dmy);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_OF)
|
||||
static const struct of_device_id pisp_dmy_of_match[] = {
|
||||
{ .compatible = "pisp_dmy" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pisp_dmy_of_match);
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id pisp_dmy_match_id[] = {
|
||||
{ "pisp_dmy", 0 },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct i2c_driver pisp_dmy_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "pisp_dmy",
|
||||
.pm = &pisp_dmy_pm_ops,
|
||||
.of_match_table = of_match_ptr(pisp_dmy_of_match),
|
||||
},
|
||||
.probe = &pisp_dmy_probe,
|
||||
.remove = &pisp_dmy_remove,
|
||||
.id_table = pisp_dmy_match_id,
|
||||
};
|
||||
|
||||
static int __init sensor_mod_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pisp_dmy_i2c_driver);
|
||||
}
|
||||
|
||||
static void __exit sensor_mod_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pisp_dmy_i2c_driver);
|
||||
}
|
||||
|
||||
device_initcall_sync(sensor_mod_init);
|
||||
module_exit(sensor_mod_exit);
|
||||
|
||||
MODULE_DESCRIPTION("preisp dummy sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user