aboutsummaryrefslogtreecommitdiff
path: root/drivers/media/platform/hisi/isp/isp-csiphy.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/hisi/isp/isp-csiphy.c')
-rw-r--r--drivers/media/platform/hisi/isp/isp-csiphy.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/drivers/media/platform/hisi/isp/isp-csiphy.c b/drivers/media/platform/hisi/isp/isp-csiphy.c
new file mode 100644
index 000000000000..f6c050fc0814
--- /dev/null
+++ b/drivers/media/platform/hisi/isp/isp-csiphy.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * isp-csiphy.c
+ *
+ * Copyright (C) 2018 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "isp.h"
+
+#define TSTCODE_SETREG8(reg_base, addr, value) \
+ do { \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL1_REG,\
+ (1 << 16) | addr); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 2); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 0); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL1_REG, value); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 2); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 0); \
+ } while (0)
+
+#define TSTCODE_GETREG8(reg_base, addr, value)\
+ do { \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL1_REG,\
+ (1 << 16) | addr); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 2); \
+ isp_writel(reg_base, CSI2IF_PHY_TEST_CTRL0_REG, 0); \
+ (value) = \
+ ((isp_readl(reg_base, CSI2IF_PHY_TEST_CTRL1_REG) >> 8) &\
+ (0x000000ff)); \
+ } while (0)
+
+#define ISP_CSIPHY_NAME "isp_csiphy"
+
+struct csiphy_format {
+ u32 code;
+ u8 bpp;
+};
+
+static const struct csiphy_format csiphy_formats_8x16[] = {
+ { MEDIA_BUS_FMT_UYVY8_2X8, 8 },
+ { MEDIA_BUS_FMT_VYUY8_2X8, 8 },
+ { MEDIA_BUS_FMT_YUYV8_2X8, 8 },
+ { MEDIA_BUS_FMT_YVYU8_2X8, 8 },
+ { MEDIA_BUS_FMT_SBGGR8_1X8, 8 },
+ { MEDIA_BUS_FMT_SGBRG8_1X8, 8 },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, 8 },
+ { MEDIA_BUS_FMT_SRGGB8_1X8, 8 },
+ { MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
+ { MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
+ { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
+ { MEDIA_BUS_FMT_Y10_1X10, 10 },
+};
+
+static const struct csiphy_format csiphy_formats_8x96[] = {
+ { MEDIA_BUS_FMT_UYVY8_2X8, 8 },
+ { MEDIA_BUS_FMT_VYUY8_2X8, 8 },
+ { MEDIA_BUS_FMT_YUYV8_2X8, 8 },
+ { MEDIA_BUS_FMT_YVYU8_2X8, 8 },
+ { MEDIA_BUS_FMT_SBGGR8_1X8, 8 },
+ { MEDIA_BUS_FMT_SGBRG8_1X8, 8 },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, 8 },
+ { MEDIA_BUS_FMT_SRGGB8_1X8, 8 },
+ { MEDIA_BUS_FMT_SBGGR10_1X10, 10 },
+ { MEDIA_BUS_FMT_SGBRG10_1X10, 10 },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, 10 },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, 10 },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, 12 },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, 12 },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, 12 },
+ { MEDIA_BUS_FMT_SRGGB12_1X12, 12 },
+ { MEDIA_BUS_FMT_SBGGR14_1X14, 14 },
+ { MEDIA_BUS_FMT_SGBRG14_1X14, 14 },
+ { MEDIA_BUS_FMT_SGRBG14_1X14, 14 },
+ { MEDIA_BUS_FMT_SRGGB14_1X14, 14 },
+ { MEDIA_BUS_FMT_Y10_1X10, 10 },
+};
+
+static int csi2if_dphy_init(char __iomem *base, unsigned char settle_time)
+{
+ unsigned char value = 0;
+
+ if (settle_time > 0) { /* configure settle time mannually */
+ TSTCODE_SETREG8(base, LANE0_SETTLE, settle_time);
+ TSTCODE_SETREG8(base, LANE1_SETTLE, settle_time);
+ TSTCODE_SETREG8(base, LANE2_SETTLE, settle_time);
+ TSTCODE_SETREG8(base, LANE3_SETTLE, settle_time);
+ TSTCODE_SETREG8(base, CFG_CLK_DETECT, 0x01);
+ } else { /* enable clock detect */
+ TSTCODE_SETREG8(base, LANE0_SETTLE, 0x01);
+ TSTCODE_SETREG8(base, LANE0_ADDITION, 0x05);
+ TSTCODE_SETREG8(base, LANE1_SETTLE, 0x01);
+ TSTCODE_SETREG8(base, LANE1_ADDITION, 0x05);
+ TSTCODE_SETREG8(base, LANE2_SETTLE, 0x01);
+ TSTCODE_SETREG8(base, LANE2_ADDITION, 0x05);
+ TSTCODE_SETREG8(base, LANE3_SETTLE, 0x01);
+ TSTCODE_SETREG8(base, LANE3_ADDITION, 0x05);
+ }
+
+ /* setup time and hold time for dphy v1.2 */
+ TSTCODE_SETREG8(base, LANE0_DESKEW_1, 0x0D);
+ TSTCODE_SETREG8(base, LANE1_DESKEW_1, 0x0D);
+ TSTCODE_SETREG8(base, LANE2_DESKEW_1, 0x0D);
+ TSTCODE_SETREG8(base, LANE3_DESKEW_1, 0x0D);
+ TSTCODE_SETREG8(base, LANE0_DESKEW_3, 0x03);
+ TSTCODE_SETREG8(base, LANE1_DESKEW_3, 0x03);
+ TSTCODE_SETREG8(base, LANE2_DESKEW_3, 0x03);
+ TSTCODE_SETREG8(base, LANE3_DESKEW_3, 0x03);
+
+ TSTCODE_SETREG8(base, CFG_CLK_ATTR, 0x50);
+
+ TSTCODE_GETREG8(base, LANE0_SETTLE, value);
+ pr_info("### LANE0_SETTLE = %d", value);
+
+ TSTCODE_GETREG8(base, LANE1_SETTLE, value);
+ pr_info("### LANE1_SETTLE = %d", value);
+
+ TSTCODE_GETREG8(base, LANE2_SETTLE, value);
+ pr_info("### LANE2_SETTLE = %d", value);
+
+ TSTCODE_GETREG8(base, LANE3_SETTLE, value);
+ pr_info("### LANE3_SETTLE = %d", value);
+
+ TSTCODE_GETREG8(base, CFG_CLK_DETECT, value);
+ pr_info("### CFG_CLK_DETECT = 0x%x", value);
+
+ TSTCODE_GETREG8(base, CFG_CLK_ATTR, value);
+ pr_info("### CFG_CLK_ATTR = 0x%x", value);
+
+ return 0;
+}
+
+int csi2if_enable(struct csiphy_device *csiphy, unsigned char num_lanes,
+ unsigned char settle_time)
+{
+ unsigned int phy_rx;
+ unsigned int phy_state;
+
+ if (num_lanes > 4 || 0 == num_lanes) {
+ dev_err(csiphy->isp->dev, "number of lanes %d out of range!!\n",
+ num_lanes);
+ return -EINVAL;
+ }
+
+ /* de-assert the shutdown signal*/
+ isp_writel(csiphy->base, CSI2IF_PHY_SHUTDOWNZ_REG, 0);
+ isp_writel(csiphy->base, CSI2IF_DPHY_RSTZ_REG, 0);
+ isp_writel(csiphy->base, CSI2IF_CSI2_RESETN_REG, 0);
+
+ isp_writel(csiphy->base, CSI2IF_PHY_TEST_CTRL0_REG, 1);
+ isp_writel(csiphy->base, CSI2IF_PHY_TEST_CTRL0_REG, 0);
+ mdelay(1);
+
+ isp_writel(csiphy->base, CSI2IF_PHY_SHUTDOWNZ_REG, 1);
+ isp_writel(csiphy->base, CSI2IF_N_LANES_REG, (num_lanes-1));
+
+ isp_writel(csiphy->base, CSI2IF_DPHY_RSTZ_REG, 1);
+ isp_writel(csiphy->base, CSI2IF_CSI2_RESETN_REG, 1);
+
+ /* Configure HUAWEI D-PHY */
+ if (csi2if_dphy_init(csiphy->base, settle_time) < 0)
+ return -1;
+
+ /* confirm the D-PHY is in right state */
+ phy_rx = isp_readl(csiphy->base, CSI2IF_PHY_RX_REG);
+ phy_state = isp_readl(csiphy->base, CSI2IF_PHY_STOPSTATE_REG);
+
+ if ((isp_readl_field(phy_state, CSI2IF_PHY_STOPSTATEDATA_0) == 0) &&
+ (isp_readl_field(phy_state, CSI2IF_PHY_STOPSTATECLK) == 0)) {
+
+ pr_info("### not all data and clock lanes in stop state!\n");
+ pr_info("phy_rx = 0x%x, phy_state = %x\n", phy_rx, phy_state);
+ }
+
+ if (isp_readl_field(phy_state, CSI2IF_PHY_RXCLKACTIVEHS) == 0)
+ pr_info("### D-PHY is not receiving a clock!\n");
+
+ mdelay(1);
+
+ phy_rx = isp_readl(csiphy->base, CSI2IF_PHY_RX_REG);
+ phy_state = isp_readl(csiphy->base, CSI2IF_PHY_STOPSTATE_REG);
+ pr_info("### D-PHY state: phy_rx = 0x%x, phy_state = 0x%x\n",
+ phy_rx, phy_state);
+
+ return 0;
+}
+
+int csi2if_disable(struct csiphy_device *csiphy)
+{
+ isp_writel(csiphy->base, CSI2IF_CSI2_RESETN_REG, 0);
+ isp_writel(csiphy->base, CSI2IF_PHY_SHUTDOWNZ_REG, 0);
+
+ return 0;
+}
+
+int isp_ispss_reset_all(struct isp *isp)
+{
+ isp_writel(isp->ispss_ctrl, 0x060, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x064, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x068, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x06C, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x070, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x074, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x078, 0xFFFFFFFF);
+ isp_writel(isp->ispss_ctrl, 0x374, 0x00000003);
+
+ mdelay(1);
+
+ isp_writel(isp->ispss_ctrl, 0x060, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x064, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x068, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x06C, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x070, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x074, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x078, 0x00000000);
+ isp_writel(isp->ispss_ctrl, 0x374, 0x00000000);
+
+ mdelay(1);
+
+ return 0;
+
+}
+
+void isp_ispss_clk_enable(struct isp *isp)
+{
+ /* enable all clock of isp sub-modules */
+ isp_writel(isp->ispss_ctrl, 0x010, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x014, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x018, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x01C, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x020, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x024, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x028, 0xffffffff);
+ isp_writel(isp->ispss_ctrl, 0x364, 0x00000003);
+}
+
+/*
+ *
+ * csiphy_set_power - Power on/off CSIPHY module
+ * @sd: CSIPHY V4L2 subdevice
+ * @on: Requested power state
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int csiphy_set_power(struct v4l2_subdev *sd, int on)
+{
+ struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
+ struct isp *isp = csiphy->isp;
+ int ret = 0;
+
+ ret = isp_enable_clocks(isp->clks, isp->dev);
+ if (ret < 0)
+ return ret;
+
+ isp_ispss_clk_enable(isp);
+ mdelay(100);
+
+ usleep_range(5000, 15000);
+
+ return ret;
+}
+
+/*
+ * csiphy_set_stream - Enable/disable streaming on CSIPHY module
+ * @sd: CSIPHY V4L2 subdevice
+ * @enable: Requested streaming state
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int csiphy_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct csiphy_device *csiphy = v4l2_get_subdevdata(sd);
+ struct isp *isp = csiphy->isp;
+ int ret;
+
+ isp_ispss_reset_all(isp);
+ mdelay(100);
+
+ ret = csi2if_enable(csiphy, 2, 0);
+ if (ret < 0)
+ return ret;
+
+ isp_ispss_clear_irq_state(isp);
+
+ isp_ispss_enable_irq(isp);
+
+ pr_info("%s: %d\n", __func__, __LINE__);
+ return ret;
+}
+
+/*
+ * csiphy_init_formats - Initialize formats on all pads
+ * @sd: CSIPHY V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int csiphy_init_formats(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format = {
+ .pad = ISP_CSIPHY_PAD_SINK,
+ .which = fh ? V4L2_SUBDEV_FORMAT_TRY :
+ V4L2_SUBDEV_FORMAT_ACTIVE,
+ .format = {
+ .code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .width = 1920,
+ .height = 1080
+ }
+ };
+
+ return 0;
+}
+
+/*
+ * isp_csiphy_subdev_init - Initialize CSIPHY device structure and resources
+ * @csiphy: CSIPHY device
+ * @res: CSIPHY module resources table
+ * @id: CSIPHY module id
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int isp_csiphy_subdev_init(struct isp *isp,
+ struct csiphy_device *csiphy,
+ const struct resources *res, u8 id)
+{
+ struct device *dev = isp->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct resource *r;
+
+ csiphy->isp = isp;
+ csiphy->id = id;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, res->reg);
+ csiphy->base = devm_ioremap_resource(dev, r);
+ if (IS_ERR(csiphy->base)) {
+ dev_err(dev, "could not map memory\n");
+ return PTR_ERR(csiphy->base);
+ }
+
+ return 0;
+}
+
+/*
+ * csiphy_link_setup - Setup CSIPHY connections
+ * @entity: Pointer to media entity structure
+ * @local: Pointer to local pad
+ * @remote: Pointer to remote pad
+ * @flags: Link flags
+ *
+ * Rreturn 0 on success
+ */
+static int csiphy_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ if ((local->flags & MEDIA_PAD_FL_SOURCE) &&
+ (flags & MEDIA_LNK_FL_ENABLED)) {
+ struct v4l2_subdev *sd;
+ struct csiphy_device *csiphy;
+ struct sr_device *sr;
+
+ if (media_entity_remote_pad(local))
+ return -EBUSY;
+
+ sd = media_entity_to_v4l2_subdev(entity);
+ csiphy = v4l2_get_subdevdata(sd);
+
+ sd = media_entity_to_v4l2_subdev(remote->entity);
+ sr = v4l2_get_subdevdata(sd);
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops csiphy_core_ops = {
+ .s_power = csiphy_set_power,
+};
+
+static const struct v4l2_subdev_video_ops csiphy_video_ops = {
+ .s_stream = csiphy_set_stream,
+};
+
+static const struct v4l2_subdev_ops csiphy_v4l2_ops = {
+ .core = &csiphy_core_ops,
+ .video = &csiphy_video_ops,
+};
+
+static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = {
+ .open = csiphy_init_formats,
+};
+
+static const struct media_entity_operations csiphy_media_ops = {
+ .link_setup = csiphy_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * isp_csiphy_register_entity - Register subdev node for CSIPHY module
+ * @csiphy: CSIPHY device
+ * @v4l2_dev: V4L2 device
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+int isp_csiphy_register_entity(struct csiphy_device *csiphy,
+ struct v4l2_device *v4l2_dev)
+{
+ struct v4l2_subdev *sd = &csiphy->subdev;
+ struct media_pad *pads = csiphy->pads;
+ struct device *dev = csiphy->isp->dev;
+ int ret;
+
+ v4l2_subdev_init(sd, &csiphy_v4l2_ops);
+ sd->internal_ops = &csiphy_v4l2_internal_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d",
+ ISP_CSIPHY_NAME, csiphy->id);
+ v4l2_set_subdevdata(sd, csiphy);
+
+ ret = csiphy_init_formats(sd, NULL);
+ if (ret < 0) {
+ dev_err(dev, "Failed to init format: %d\n", ret);
+ return ret;
+ }
+
+ pads[ISP_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[ISP_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+
+ sd->entity.function = MEDIA_ENT_F_IO_V4L;
+ sd->entity.ops = &csiphy_media_ops;
+ ret = media_entity_pads_init(&sd->entity, ISP_CSIPHY_PADS_NUM, pads);
+ if (ret < 0) {
+ dev_err(dev, "Failed to init media entity: %d\n", ret);
+ return ret;
+ }
+
+ ret = v4l2_device_register_subdev(v4l2_dev, sd);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register subdev: %d\n", ret);
+ media_entity_cleanup(&sd->entity);
+ }
+
+ return ret;
+}
+
+/*
+ * isp_csiphy_unregister_entity - Unregister CSIPHY module subdev node
+ * @csiphy: CSIPHY device
+ */
+void isp_csiphy_unregister_entity(struct csiphy_device *csiphy)
+{
+ v4l2_device_unregister_subdev(&csiphy->subdev);
+ media_entity_cleanup(&csiphy->subdev.entity);
+}