diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index f1fe8f72..5ea4d8fd 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -12,3 +12,4 @@ obj-m += nv_imx390.o obj-m += nv_ov5693.o obj-m += max96712.o obj-m += nv_ar0234.o +obj-m += lt6911uxc.o diff --git a/drivers/media/i2c/lt6911uxc.c b/drivers/media/i2c/lt6911uxc.c new file mode 100644 index 00000000..e47857a7 --- /dev/null +++ b/drivers/media/i2c/lt6911uxc.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +/* + * Lontium LT6911UXC HDMI-CSI bridge driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../platform/tegra/camera/camera_gpio.h" + +static const struct of_device_id lt6911uxc_of_match[] = { + { .compatible = "nvidia,lt6911uxc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, lt6911uxc_of_match); + +static const u32 ctrl_cid_list[] = { + TEGRA_CAMERA_CID_SENSOR_MODE_ID, +}; + +static const int lt6911uxc_60fps[] = { + 60, +}; + +static const int lt6911uxc_30fps[] = { + 30, +}; + +struct lt6911uxc { + struct i2c_client *i2c_client; + struct v4l2_subdev *subdev; + u16 fine_integ_time; + u32 frame_length; + struct camera_common_data *s_data; + struct tegracam_device *tc_dev; +}; + +static const struct camera_common_frmfmt lt6911uxc_frmfmt[] = { + {{1920, 1080}, lt6911uxc_60fps, 1, 0, 0}, + {{3840, 2160}, lt6911uxc_60fps, 1, 0, 1}, + {{1280, 720}, lt6911uxc_60fps, 1, 0, 2}, +}; + +static const struct regmap_config sensor_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +static int lt6911uxc_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + dev_dbg(&client->dev, "%s:\n", __func__); + + return 0; +} +static int lt6911uxc_set_group_hold(struct tegracam_device *tc_dev, bool val) +{ + /* lt6911uxc does not support group hold */ + return 0; +} + +/* As soon as the reset pin is released, the bridge starts streaming */ +static int lt6911uxc_start_streaming(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 1); + else + gpio_set_value(pw->reset_gpio, 1); + } + return 0; +} + +static int lt6911uxc_stop_streaming(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 0); + else + gpio_set_value(pw->reset_gpio, 0); + } + return 0; +} + +static int lt6911uxc_set_mode(struct tegracam_device *tc_dev) +{ + /* Width and Height taken care of by the firmware */ + return 0; +} + +static inline int lt6911uxc_read_reg(struct camera_common_data *s_data, + u16 addr, u8 *val) +{ + int err = 0; + u32 reg_val = 0; + + err = regmap_read(s_data->regmap, addr, ®_val); + *val = reg_val & 0xff; + + return err; +} + +static inline int lt6911uxc_write_reg(struct camera_common_data *s_data, + u16 addr, u8 val) +{ + return 0; +} + +static int lt6911uxc_power_get(struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct clk *parent; + int err = 0; + + if (!pdata) { + dev_err(dev, "pdata missing\n"); + return -EFAULT; + } + + /* Sensor MCLK (aka. INCK) */ + if (pdata->mclk_name) { + pw->mclk = devm_clk_get(dev, pdata->mclk_name); + if (IS_ERR(pw->mclk)) { + dev_err(dev, "unable to get clock %s\n", + pdata->mclk_name); + return PTR_ERR(pw->mclk); + } + + if (pdata->parentclk_name) { + parent = devm_clk_get(dev, pdata->parentclk_name); + if (IS_ERR(parent)) { + dev_err(dev, "unable to get parent clock %s", + pdata->parentclk_name); + } else + clk_set_parent(pw->mclk, parent); + } + } + + /* analog 3.3v */ + if (pdata->regulators.avdd) + err |= camera_common_regulator_get(dev, + &pw->avdd, pdata->regulators.avdd); + if (err) { + dev_err(dev, "%s: unable to get regulator(s)\n", __func__); + goto done; + } + + /* dig 1.2v */ + if (pdata->regulators.dvdd) + err |= camera_common_regulator_get(dev, + &pw->dvdd, pdata->regulators.dvdd); + if (err) { + dev_err(dev, "%s: unable to get regulator(s)\n", __func__); + goto done; + } + + /* vdd 1.8v */ + if (pdata->regulators.iovdd) + err |= camera_common_regulator_get(dev, + &pw->avdd, pdata->regulators.iovdd); + if (err) { + dev_err(dev, "%s: unable to get regulator(s)\n", __func__); + goto done; + } + + /* Reset or ENABLE GPIO */ + pw->reset_gpio = pdata->reset_gpio; + err = gpio_request(pw->reset_gpio, "cam_reset_gpio"); + if (err < 0) { + dev_err(dev, "%s: unable to request reset_gpio (%d)\n", + __func__, err); + goto done; + } + +done: + pw->state = SWITCH_OFF; + + return err; +} + +static int lt6911uxc_power_put(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (unlikely(!pw)) + return -EFAULT; + + if (likely(pw->dvdd)) + devm_regulator_put(pw->dvdd); + + if (likely(pw->avdd)) + devm_regulator_put(pw->avdd); + + if (likely(pw->iovdd)) + devm_regulator_put(pw->iovdd); + + pw->dvdd = NULL; + pw->avdd = NULL; + pw->iovdd = NULL; + + if (likely(pw->reset_gpio)) + gpio_free(pw->reset_gpio); + + return 0; +} + +static struct camera_common_pdata *lt6911uxc_parse_dt( + struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct device_node *np = dev->of_node; + struct camera_common_pdata *board_priv_pdata; + const struct of_device_id *match; + struct camera_common_pdata *ret = NULL; + int err = 0; + int gpio; + + if (!np) + return NULL; + + match = of_match_device(lt6911uxc_of_match, dev); + if (!match) { + dev_err(dev, "Failed to find matching dt id\n"); + return NULL; + } + + board_priv_pdata = devm_kzalloc(dev, + sizeof(*board_priv_pdata), GFP_KERNEL); + if (!board_priv_pdata) + return NULL; + + gpio = of_get_named_gpio(np, "reset-gpios", 0); + if (gpio < 0) { + if (gpio == -EPROBE_DEFER) + ret = ERR_PTR(-EPROBE_DEFER); + dev_err(dev, "reset-gpios not found\n"); + goto error; + } + board_priv_pdata->reset_gpio = (unsigned int)gpio; + + err = of_property_read_string(np, "mclk", &board_priv_pdata->mclk_name); + if (err) + dev_err(dev, "mclk absent,assuming sensor driven externally\n"); + + err = of_property_read_string(np, "avdd-reg", + &board_priv_pdata->regulators.avdd); + err |= of_property_read_string(np, "iovdd-reg", + &board_priv_pdata->regulators.iovdd); + err |= of_property_read_string(np, "dvdd-reg", + &board_priv_pdata->regulators.dvdd); + if (err) + dev_dbg(dev, "avdd, iovdd and/or dvdd reglrs. not present, \ + assume sensor powered independently\n"); + + board_priv_pdata->has_eeprom = + of_property_read_bool(np, "has-eeprom"); + + return board_priv_pdata; + +error: + devm_kfree(dev, board_priv_pdata); + + return ret; +} + +static int lt6911uxc_power_on(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + if (pdata && pdata->power_on) { + err = pdata->power_on(pw); + if (err) + dev_err(dev, "%s failed.\n", __func__); + else + pw->state = SWITCH_ON; + return err; + } + + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 0); + else + gpio_set_value(pw->reset_gpio, 0); + } + + usleep_range(10, 20); + + if (pw->avdd) { + err = regulator_enable(pw->avdd); + if (err) + goto lt6911uxc_avdd_fail; + } + + if (pw->iovdd) { + err = regulator_enable(pw->iovdd); + if (err) + goto lt6911uxc_iovdd_fail; + } + /* Need to wait for atleat 1 ms before turning up 1.2v */ + usleep_range(1000, 1200); + if (pw->dvdd) { + err = regulator_enable(pw->dvdd); + if (err) + goto lt6911uxc_dvdd_fail; + } + + usleep_range(10, 20); + + pw->state = SWITCH_ON; + + return 0; + +lt6911uxc_dvdd_fail: + regulator_disable(pw->iovdd); + +lt6911uxc_iovdd_fail: + regulator_disable(pw->avdd); + +lt6911uxc_avdd_fail: + dev_err(dev, "%s failed.\n", __func__); + + return -ENODEV; +} + +static int lt6911uxc_power_off(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + if (pdata && pdata->power_off) { + err = pdata->power_off(pw); + if (err) { + dev_err(dev, "%s failed.\n", __func__); + return err; + } + } else { + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 0); + else + gpio_set_value(pw->reset_gpio, 0); + } + } + + usleep_range(10, 20); + + if (pw->dvdd) + regulator_disable(pw->dvdd); + if (pw->iovdd) + regulator_disable(pw->iovdd); + if (pw->avdd) + regulator_disable(pw->avdd); + + pw->state = SWITCH_OFF; + + return 0; +} + +static int lt6911uxc_board_setup(struct lt6911uxc *priv) +{ + struct camera_common_data *s_data = priv->s_data; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + int err = 0; + + if (pdata->mclk_name) { + err = camera_common_mclk_enable(s_data); + if (err) { + dev_err(dev, "error turning on mclk (%d)\n", err); + goto done; + } + } + + err = lt6911uxc_power_on(s_data); + if (err) { + dev_err(dev, "error during power on sensor (%d)\n", err); + goto err_power_on; + } + +err_power_on: + if (pdata->mclk_name) + camera_common_mclk_disable(s_data); + +done: + return err; +} + +static struct tegracam_ctrl_ops lt6911uxc_ctrl_ops = { + .set_group_hold = lt6911uxc_set_group_hold, +}; + +static const struct v4l2_subdev_internal_ops lt6911uxc_subdev_internal_ops = { + .open = lt6911uxc_open, +}; + +static struct camera_common_sensor_ops lt6911uxc_common_ops = { + .numfrmfmts = ARRAY_SIZE(lt6911uxc_frmfmt), + .frmfmt_table = lt6911uxc_frmfmt, + .power_on = lt6911uxc_power_on, + .power_off = lt6911uxc_power_off, + .write_reg = lt6911uxc_write_reg, + .read_reg = lt6911uxc_read_reg, + .parse_dt = lt6911uxc_parse_dt, + .power_get = lt6911uxc_power_get, + .power_put = lt6911uxc_power_put, + .set_mode = lt6911uxc_set_mode, + .start_streaming = lt6911uxc_start_streaming, + .stop_streaming = lt6911uxc_stop_streaming, +}; + + +static int lt6911uxc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tegracam_device *tc_dev; + struct lt6911uxc *priv; + int err; + + dev_err(dev, "probing lt6911uxc v4l2 sensor at addr 0x%0x\n", + client->addr); + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return -EINVAL; + + priv = devm_kzalloc(dev, + sizeof(struct lt6911uxc), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + tc_dev = devm_kzalloc(dev, + sizeof(struct tegracam_device), GFP_KERNEL); + if (!tc_dev) + return -ENOMEM; + + priv->i2c_client = tc_dev->client = client; + tc_dev->dev = dev; + strncpy(tc_dev->name, "lt6911uxc", sizeof(tc_dev->name)); + tc_dev->dev_regmap_config = &sensor_regmap_config; + tc_dev->sensor_ops = <6911uxc_common_ops; + tc_dev->v4l2sd_internal_ops = <6911uxc_subdev_internal_ops; + tc_dev->tcctrl_ops = <6911uxc_ctrl_ops; + + err = tegracam_device_register(tc_dev); + if (err) { + dev_err(dev, "tegra camera driver registration failed\n"); + return err; + } + priv->tc_dev = tc_dev; + priv->s_data = tc_dev->s_data; + priv->subdev = &tc_dev->s_data->subdev; + tegracam_set_privdata(tc_dev, (void *)priv); + + err = lt6911uxc_board_setup(priv); + if (err) { + tegracam_device_unregister(tc_dev); + dev_err(dev, "board setup failed\n"); + return err; + } + + err = tegracam_v4l2subdev_register(tc_dev, true); + if (err) { + dev_err(dev, "tegra camera subdev registration failed\n"); + return err; + } + + dev_err(dev, "detected lt6911uxc sensor\n"); + + return 0; +} + +#if (KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE) +static int lt6911uxc_remove(struct i2c_client *client) +#else +static void lt6911uxc_remove(struct i2c_client *client) +#endif +{ + struct camera_common_data *s_data = to_camera_common_data(&client->dev); + struct lt6911uxc *priv = (struct lt6911uxc *)s_data->priv; + + tegracam_v4l2subdev_unregister(priv->tc_dev); + tegracam_device_unregister(priv->tc_dev); + +#if (KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE) + return 0; +#endif +} + +static const struct i2c_device_id lt6911uxc_id[] = { + { "lt6911uxc", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lt6911uxc_id); + +static struct i2c_driver lt6911uxc_i2c_driver = { + .driver = { + .name = "lt6911uxc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(lt6911uxc_of_match), + }, + .probe = lt6911uxc_probe, + .remove = lt6911uxc_remove, + .id_table = lt6911uxc_id, +}; +module_i2c_driver(lt6911uxc_i2c_driver); +MODULE_DESCRIPTION("Media Controller driver for Lontium LTX6911UXC"); +MODULE_AUTHOR("Anubhav Rai "); +MODULE_LICENSE("GPL v2");