media: ov5675: add device-tree support and support runtime PM
authorQuentin Schulz <quentin.schulz@theobroma-systems.com>
Wed, 8 Jun 2022 13:44:18 +0000 (15:44 +0200)
committerMauro Carvalho Chehab <mchehab@kernel.org>
Mon, 6 Feb 2023 07:44:04 +0000 (08:44 +0100)
Until now, this driver only supported ACPI. This adds support for
Device Tree too while enabling clock and regulators in runtime PM.

Signed-off-by: Quentin Schulz <quentin.schulz@theobroma-systems.com>
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
drivers/media/i2c/ov5675.c

index a6e6b36..6fb849d 100644 (file)
@@ -3,10 +3,14 @@
 
 #include <asm/unaligned.h>
 #include <linux/acpi.h>
+#include <linux/clk.h>
 #include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-device.h>
 #include <media/v4l2-fwnode.h>
@@ -17,7 +21,7 @@
 
 #define OV5675_LINK_FREQ_450MHZ                450000000ULL
 #define OV5675_SCLK                    90000000LL
-#define OV5675_MCLK                    19200000
+#define OV5675_XVCLK_19_2              19200000
 #define OV5675_DATA_LANES              2
 #define OV5675_RGB_DEPTH               10
 
 
 #define to_ov5675(_sd)                 container_of(_sd, struct ov5675, sd)
 
+static const char * const ov5675_supply_names[] = {
+       "avdd",         /* Analog power */
+       "dovdd",        /* Digital I/O power */
+       "dvdd",         /* Digital core power */
+};
+
+#define OV5675_NUM_SUPPLIES    ARRAY_SIZE(ov5675_supply_names)
+
 enum {
        OV5675_LINK_FREQ_900MBPS,
 };
@@ -484,6 +496,9 @@ struct ov5675 {
        struct v4l2_subdev sd;
        struct media_pad pad;
        struct v4l2_ctrl_handler ctrl_handler;
+       struct clk *xvclk;
+       struct gpio_desc *reset_gpio;
+       struct regulator_bulk_data supplies[OV5675_NUM_SUPPLIES];
 
        /* V4L2 Controls */
        struct v4l2_ctrl *link_freq;
@@ -946,6 +961,56 @@ static int ov5675_set_stream(struct v4l2_subdev *sd, int enable)
        return ret;
 }
 
+static int ov5675_power_off(struct device *dev)
+{
+       /* 512 xvclk cycles after the last SCCB transation or MIPI frame end */
+       u32 delay_us = DIV_ROUND_UP(512, OV5675_XVCLK_19_2 / 1000 / 1000);
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov5675 *ov5675 = to_ov5675(sd);
+
+       usleep_range(delay_us, delay_us * 2);
+
+       clk_disable_unprepare(ov5675->xvclk);
+       gpiod_set_value_cansleep(ov5675->reset_gpio, 1);
+       regulator_bulk_disable(OV5675_NUM_SUPPLIES, ov5675->supplies);
+
+       return 0;
+}
+
+static int ov5675_power_on(struct device *dev)
+{
+       u32 delay_us = DIV_ROUND_UP(8192, OV5675_XVCLK_19_2 / 1000 / 1000);
+       struct v4l2_subdev *sd = dev_get_drvdata(dev);
+       struct ov5675 *ov5675 = to_ov5675(sd);
+       int ret;
+
+       ret = clk_prepare_enable(ov5675->xvclk);
+       if (ret < 0) {
+               dev_err(dev, "failed to enable xvclk: %d\n", ret);
+               return ret;
+       }
+
+       gpiod_set_value_cansleep(ov5675->reset_gpio, 1);
+
+       ret = regulator_bulk_enable(OV5675_NUM_SUPPLIES, ov5675->supplies);
+       if (ret) {
+               clk_disable_unprepare(ov5675->xvclk);
+               return ret;
+       }
+
+       /* Reset pulse should be at least 2ms and reset gpio released only once
+        * regulators are stable.
+        */
+       usleep_range(2000, 2200);
+
+       gpiod_set_value_cansleep(ov5675->reset_gpio, 0);
+
+       /* 8192 xvclk cycles prior to the first SCCB transation */
+       usleep_range(delay_us, delay_us * 2);
+
+       return 0;
+}
+
 static int __maybe_unused ov5675_suspend(struct device *dev)
 {
        struct v4l2_subdev *sd = dev_get_drvdata(dev);
@@ -1108,32 +1173,60 @@ static const struct v4l2_subdev_internal_ops ov5675_internal_ops = {
        .open = ov5675_open,
 };
 
-static int ov5675_check_hwcfg(struct device *dev)
+static int ov5675_get_hwcfg(struct ov5675 *ov5675, struct device *dev)
 {
        struct fwnode_handle *ep;
        struct fwnode_handle *fwnode = dev_fwnode(dev);
        struct v4l2_fwnode_endpoint bus_cfg = {
                .bus_type = V4L2_MBUS_CSI2_DPHY
        };
-       u32 mclk;
+       u32 xvclk_rate;
        int ret;
        unsigned int i, j;
 
        if (!fwnode)
                return -ENXIO;
 
-       ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk);
+       ov5675->xvclk = devm_clk_get_optional(dev, NULL);
+       if (IS_ERR(ov5675->xvclk))
+               return dev_err_probe(dev, PTR_ERR(ov5675->xvclk),
+                                    "failed to get xvclk: %ld\n",
+                                    PTR_ERR(ov5675->xvclk));
 
-       if (ret) {
-               dev_err(dev, "can't get clock frequency");
-               return ret;
+       if (ov5675->xvclk) {
+               xvclk_rate = clk_get_rate(ov5675->xvclk);
+       } else {
+               ret = fwnode_property_read_u32(fwnode, "clock-frequency",
+                                              &xvclk_rate);
+
+               if (ret) {
+                       dev_err(dev, "can't get clock frequency");
+                       return ret;
+               }
        }
 
-       if (mclk != OV5675_MCLK) {
-               dev_err(dev, "external clock %d is not supported", mclk);
+       if (xvclk_rate != OV5675_XVCLK_19_2) {
+               dev_err(dev, "external clock rate %u is unsupported",
+                       xvclk_rate);
                return -EINVAL;
        }
 
+       ov5675->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+                                                    GPIOD_OUT_HIGH);
+       if (IS_ERR(ov5675->reset_gpio)) {
+               ret = PTR_ERR(ov5675->reset_gpio);
+               dev_err(dev, "failed to get reset-gpios: %d\n", ret);
+               return ret;
+       }
+
+       for (i = 0; i < OV5675_NUM_SUPPLIES; i++)
+               ov5675->supplies[i].supply = ov5675_supply_names[i];
+
+       ret = devm_regulator_bulk_get(dev, OV5675_NUM_SUPPLIES,
+                                     ov5675->supplies);
+       if (ret)
+               return ret;
+
        ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
        if (!ep)
                return -ENXIO;
@@ -1187,6 +1280,10 @@ static void ov5675_remove(struct i2c_client *client)
        v4l2_ctrl_handler_free(sd->ctrl_handler);
        pm_runtime_disable(&client->dev);
        mutex_destroy(&ov5675->mutex);
+
+       if (!pm_runtime_status_suspended(&client->dev))
+               ov5675_power_off(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
 }
 
 static int ov5675_probe(struct i2c_client *client)
@@ -1195,25 +1292,31 @@ static int ov5675_probe(struct i2c_client *client)
        bool full_power;
        int ret;
 
-       ret = ov5675_check_hwcfg(&client->dev);
+       ov5675 = devm_kzalloc(&client->dev, sizeof(*ov5675), GFP_KERNEL);
+       if (!ov5675)
+               return -ENOMEM;
+
+       ret = ov5675_get_hwcfg(ov5675, &client->dev);
        if (ret) {
-               dev_err(&client->dev, "failed to check HW configuration: %d",
+               dev_err(&client->dev, "failed to get HW configuration: %d",
                        ret);
                return ret;
        }
 
-       ov5675 = devm_kzalloc(&client->dev, sizeof(*ov5675), GFP_KERNEL);
-       if (!ov5675)
-               return -ENOMEM;
-
        v4l2_i2c_subdev_init(&ov5675->sd, client, &ov5675_subdev_ops);
 
+       ret = ov5675_power_on(&client->dev);
+       if (ret) {
+               dev_err(&client->dev, "failed to power on: %d\n", ret);
+               return ret;
+       }
+
        full_power = acpi_dev_state_d0(&client->dev);
        if (full_power) {
                ret = ov5675_identify_module(ov5675);
                if (ret) {
                        dev_err(&client->dev, "failed to find sensor: %d", ret);
-                       return ret;
+                       goto probe_power_off;
                }
        }
 
@@ -1243,11 +1346,6 @@ static int ov5675_probe(struct i2c_client *client)
                goto probe_error_media_entity_cleanup;
        }
 
-       /*
-        * Device is already turned on by i2c-core with ACPI domain PM.
-        * Enable runtime PM and turn off the device.
-        */
-
        /* Set the device's state to active if it's in D0 state. */
        if (full_power)
                pm_runtime_set_active(&client->dev);
@@ -1262,12 +1360,15 @@ probe_error_media_entity_cleanup:
 probe_error_v4l2_ctrl_handler_free:
        v4l2_ctrl_handler_free(ov5675->sd.ctrl_handler);
        mutex_destroy(&ov5675->mutex);
+probe_power_off:
+       ov5675_power_off(&client->dev);
 
        return ret;
 }
 
 static const struct dev_pm_ops ov5675_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(ov5675_suspend, ov5675_resume)
+       SET_RUNTIME_PM_OPS(ov5675_power_off, ov5675_power_on, NULL)
 };
 
 #ifdef CONFIG_ACPI
@@ -1279,11 +1380,18 @@ static const struct acpi_device_id ov5675_acpi_ids[] = {
 MODULE_DEVICE_TABLE(acpi, ov5675_acpi_ids);
 #endif
 
+static const struct of_device_id ov5675_of_match[] = {
+       { .compatible = "ovti,ov5675", },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ov5675_of_match);
+
 static struct i2c_driver ov5675_i2c_driver = {
        .driver = {
                .name = "ov5675",
                .pm = &ov5675_pm_ops,
                .acpi_match_table = ACPI_PTR(ov5675_acpi_ids),
+               .of_match_table = ov5675_of_match,
        },
        .probe_new = ov5675_probe,
        .remove = ov5675_remove,