| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2013--2024 Intel Corporation |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bits.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| |
| #include "ipu6-bus.h" |
| #include "ipu6-isys.h" |
| #include "ipu6-isys-csi2.h" |
| #include "ipu6-platform-isys-csi2-reg.h" |
| |
| /* only use BB0, BB2, BB4, and BB6 on PHY0 */ |
| #define IPU6SE_ISYS_PHY_BB_NUM 4 |
| #define IPU6SE_ISYS_PHY_0_BASE 0x10000 |
| |
| #define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) |
| #define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) |
| #define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) |
| #define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) |
| |
| /* |
| * use port_cfg to configure that which data lanes used |
| * +---------+ +------+ +-----+ |
| * | port0 x4<-----| | | | |
| * | | | port | | | |
| * | port1 x2<-----| | | | |
| * | | | <-| PHY | |
| * | port2 x4<-----| | | | |
| * | | |config| | | |
| * | port3 x2<-----| | | | |
| * +---------+ +------+ +-----+ |
| */ |
| static const unsigned int csi2_port_cfg[][3] = { |
| {0, 0, 0x1f}, /* no link */ |
| {4, 0, 0x10}, /* x4 + x4 config */ |
| {2, 0, 0x12}, /* x2 + x2 config */ |
| {1, 0, 0x13}, /* x1 + x1 config */ |
| {2, 1, 0x15}, /* x2x1 + x2x1 config */ |
| {1, 1, 0x16}, /* x1x1 + x1x1 config */ |
| {2, 2, 0x18}, /* x2x2 + x2x2 config */ |
| {1, 2, 0x19} /* x1x2 + x1x2 config */ |
| }; |
| |
| /* port, nlanes, bbindex, portcfg */ |
| static const unsigned int phy_port_cfg[][4] = { |
| /* sip0 */ |
| {0, 1, 0, 0x15}, |
| {0, 2, 0, 0x15}, |
| {0, 4, 0, 0x15}, |
| {0, 4, 2, 0x22}, |
| /* sip1 */ |
| {2, 1, 4, 0x15}, |
| {2, 2, 4, 0x15}, |
| {2, 4, 4, 0x15}, |
| {2, 4, 6, 0x22} |
| }; |
| |
| static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys, |
| unsigned int port, |
| unsigned int nlanes) |
| { |
| struct device *dev = &isys->adev->auxdev.dev; |
| void __iomem *base = isys->adev->isp->base; |
| unsigned int bbnum; |
| u32 val, reg, i; |
| |
| dev_dbg(dev, "port %u with %u lanes", port, nlanes); |
| |
| /* only support <1.5Gbps */ |
| for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) { |
| /* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */ |
| reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); |
| val = readl(base + reg); |
| val |= FIELD_PREP(GENMASK(6, 1), 13); |
| writel(val, base + reg); |
| |
| /* cphy_rx_control1.en_crc1 = 1 */ |
| reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i); |
| val = readl(base + reg); |
| val |= BIT(31); |
| writel(val, base + reg); |
| |
| /* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */ |
| reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i); |
| val = readl(base + reg); |
| val |= BIT(25) | BIT(26); |
| writel(val, base + reg); |
| |
| /* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */ |
| reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); |
| val = readl(base + reg); |
| val |= BIT(0); |
| writel(val, base + reg); |
| } |
| |
| /* Front end config, use minimal channel loss */ |
| for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) { |
| if (phy_port_cfg[i][0] == port && |
| phy_port_cfg[i][1] == nlanes) { |
| bbnum = phy_port_cfg[i][2] / 2; |
| reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum); |
| val = readl(base + reg); |
| val |= phy_port_cfg[i][3]; |
| writel(val, base + reg); |
| } |
| } |
| } |
| |
| static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys) |
| { |
| void __iomem *base = isys->adev->isp->base; |
| u32 val, reg; |
| |
| reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL; |
| val = readl(base + reg); |
| val |= BIT(0); |
| writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL); |
| |
| reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL; |
| val = readl(base + reg); |
| val |= BIT(0); |
| writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL); |
| |
| reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL; |
| val = readl(base + reg); |
| val |= BIT(0); |
| writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL); |
| |
| reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL; |
| val = readl(base + reg); |
| val |= BIT(0); |
| writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL); |
| } |
| |
| static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys, |
| unsigned int port, unsigned int nlanes) |
| { |
| struct device *dev = &isys->adev->auxdev.dev; |
| unsigned int sip = port / 2; |
| unsigned int index; |
| |
| switch (nlanes) { |
| case 1: |
| index = 5; |
| break; |
| case 2: |
| index = 6; |
| break; |
| case 4: |
| index = 1; |
| break; |
| default: |
| dev_err(dev, "lanes nr %u is unsupported\n", nlanes); |
| return -EINVAL; |
| } |
| |
| dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes); |
| |
| writel(csi2_port_cfg[index][2], |
| isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)); |
| |
| return 0; |
| } |
| |
| static void |
| ipu6_isys_csi2_set_timing(struct ipu6_isys *isys, |
| const struct ipu6_isys_csi2_timing *timing, |
| unsigned int port, unsigned int nlanes) |
| { |
| struct device *dev = &isys->adev->auxdev.dev; |
| void __iomem *reg; |
| u32 port_base; |
| u32 i; |
| |
| port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) : |
| CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port); |
| |
| dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes); |
| |
| reg = isys->pdata->base + port_base; |
| reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE; |
| |
| writel(timing->ctermen, reg); |
| |
| reg = isys->pdata->base + port_base; |
| reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE; |
| writel(timing->csettle, reg); |
| |
| for (i = 0; i < nlanes; i++) { |
| reg = isys->pdata->base + port_base; |
| reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i); |
| writel(timing->dtermen, reg); |
| |
| reg = isys->pdata->base + port_base; |
| reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i); |
| writel(timing->dsettle, reg); |
| } |
| } |
| |
| #define DPHY_TIMER_INCR 0x28 |
| int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, |
| struct ipu6_isys_csi2_config *cfg, |
| const struct ipu6_isys_csi2_timing *timing, |
| bool on) |
| { |
| struct device *dev = &isys->adev->auxdev.dev; |
| void __iomem *isys_base = isys->pdata->base; |
| int ret = 0; |
| u32 nlanes; |
| u32 port; |
| |
| if (!on) |
| return 0; |
| |
| port = cfg->port; |
| nlanes = cfg->nlanes; |
| |
| if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { |
| dev_warn(dev, "invalid port ID %d\n", port); |
| return -EINVAL; |
| } |
| |
| ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes); |
| |
| writel(DPHY_TIMER_INCR, |
| isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR); |
| |
| /* set port cfg and rx timing */ |
| ipu6_isys_csi2_set_timing(isys, timing, port, nlanes); |
| |
| ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes); |
| if (ret) |
| return ret; |
| |
| ipu6_isys_csi2_rx_control(isys); |
| |
| return 0; |
| } |