xref: /linux/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2013--2024 Intel Corporation
4  */
5 
6 #include <linux/bitfield.h>
7 #include <linux/bits.h>
8 #include <linux/device.h>
9 #include <linux/io.h>
10 
11 #include "ipu6-bus.h"
12 #include "ipu6-isys.h"
13 #include "ipu6-isys-csi2.h"
14 #include "ipu6-platform-isys-csi2-reg.h"
15 
16 /* only use BB0, BB2, BB4, and BB6 on PHY0 */
17 #define IPU6SE_ISYS_PHY_BB_NUM		4
18 #define IPU6SE_ISYS_PHY_0_BASE		0x10000
19 
20 #define PHY_CPHY_DLL_OVRD(x)		(0x100 + 0x100 * (x))
21 #define PHY_CPHY_RX_CONTROL1(x)		(0x110 + 0x100 * (x))
22 #define PHY_DPHY_CFG(x)			(0x148 + 0x100 * (x))
23 #define PHY_BB_AFE_CONFIG(x)		(0x174 + 0x100 * (x))
24 
25 /*
26  * use port_cfg to configure that which data lanes used
27  * +---------+     +------+ +-----+
28  * | port0 x4<-----|      | |     |
29  * |         |     | port | |     |
30  * | port1 x2<-----|      | |     |
31  * |         |     |      <-| PHY |
32  * | port2 x4<-----|      | |     |
33  * |         |     |config| |     |
34  * | port3 x2<-----|      | |     |
35  * +---------+     +------+ +-----+
36  */
37 static const unsigned int csi2_port_cfg[][3] = {
38 	{0, 0, 0x1f}, /* no link */
39 	{4, 0, 0x10}, /* x4 + x4 config */
40 	{2, 0, 0x12}, /* x2 + x2 config */
41 	{1, 0, 0x13}, /* x1 + x1 config */
42 	{2, 1, 0x15}, /* x2x1 + x2x1 config */
43 	{1, 1, 0x16}, /* x1x1 + x1x1 config */
44 	{2, 2, 0x18}, /* x2x2 + x2x2 config */
45 	{1, 2, 0x19} /* x1x2 + x1x2 config */
46 };
47 
48 /* port, nlanes, bbindex, portcfg */
49 static const unsigned int phy_port_cfg[][4] = {
50 	/* sip0 */
51 	{0, 1, 0, 0x15},
52 	{0, 2, 0, 0x15},
53 	{0, 4, 0, 0x15},
54 	{0, 4, 2, 0x22},
55 	/* sip1 */
56 	{2, 1, 4, 0x15},
57 	{2, 2, 4, 0x15},
58 	{2, 4, 4, 0x15},
59 	{2, 4, 6, 0x22}
60 };
61 
62 static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys,
63 					      unsigned int port,
64 					      unsigned int nlanes)
65 {
66 	struct device *dev = &isys->adev->auxdev.dev;
67 	void __iomem *base = isys->adev->isp->base;
68 	unsigned int bbnum;
69 	u32 val, reg, i;
70 
71 	dev_dbg(dev, "port %u with %u lanes", port, nlanes);
72 
73 	/* only support <1.5Gbps */
74 	for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) {
75 		/* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */
76 		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
77 		val = readl(base + reg);
78 		val |= FIELD_PREP(GENMASK(6, 1), 13);
79 		writel(val, base + reg);
80 
81 		/* cphy_rx_control1.en_crc1 = 1 */
82 		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i);
83 		val = readl(base + reg);
84 		val |= BIT(31);
85 		writel(val, base + reg);
86 
87 		/* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */
88 		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i);
89 		val = readl(base + reg);
90 		val |= BIT(25) | BIT(26);
91 		writel(val, base + reg);
92 
93 		/* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */
94 		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
95 		val = readl(base + reg);
96 		val |= BIT(0);
97 		writel(val, base + reg);
98 	}
99 
100 	/* Front end config, use minimal channel loss */
101 	for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) {
102 		if (phy_port_cfg[i][0] == port &&
103 		    phy_port_cfg[i][1] == nlanes) {
104 			bbnum = phy_port_cfg[i][2] / 2;
105 			reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum);
106 			val = readl(base + reg);
107 			val |= phy_port_cfg[i][3];
108 			writel(val, base + reg);
109 		}
110 	}
111 }
112 
113 static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys)
114 {
115 	void __iomem *base = isys->adev->isp->base;
116 	u32 val, reg;
117 
118 	reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL;
119 	val = readl(base + reg);
120 	val |= BIT(0);
121 	writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL);
122 
123 	reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL;
124 	val = readl(base + reg);
125 	val |= BIT(0);
126 	writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL);
127 
128 	reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL;
129 	val = readl(base + reg);
130 	val |= BIT(0);
131 	writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL);
132 
133 	reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL;
134 	val = readl(base + reg);
135 	val |= BIT(0);
136 	writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL);
137 }
138 
139 static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys,
140 				       unsigned int port, unsigned int nlanes)
141 {
142 	struct device *dev = &isys->adev->auxdev.dev;
143 	unsigned int sip = port / 2;
144 	unsigned int index;
145 
146 	switch (nlanes) {
147 	case 1:
148 		index = 5;
149 		break;
150 	case 2:
151 		index = 6;
152 		break;
153 	case 4:
154 		index = 1;
155 		break;
156 	default:
157 		dev_err(dev, "lanes nr %u is unsupported\n", nlanes);
158 		return -EINVAL;
159 	}
160 
161 	dev_dbg(dev, "port config for port %u with %u lanes\n",	port, nlanes);
162 
163 	writel(csi2_port_cfg[index][2],
164 	       isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip));
165 
166 	return 0;
167 }
168 
169 static void
170 ipu6_isys_csi2_set_timing(struct ipu6_isys *isys,
171 			  const struct ipu6_isys_csi2_timing *timing,
172 			  unsigned int port, unsigned int nlanes)
173 {
174 	struct device *dev = &isys->adev->auxdev.dev;
175 	void __iomem *reg;
176 	u32 port_base;
177 	u32 i;
178 
179 	port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) :
180 		CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port);
181 
182 	dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes);
183 
184 	reg = isys->pdata->base + port_base;
185 	reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE;
186 
187 	writel(timing->ctermen, reg);
188 
189 	reg = isys->pdata->base + port_base;
190 	reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE;
191 	writel(timing->csettle, reg);
192 
193 	for (i = 0; i < nlanes; i++) {
194 		reg = isys->pdata->base + port_base;
195 		reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i);
196 		writel(timing->dtermen, reg);
197 
198 		reg = isys->pdata->base + port_base;
199 		reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i);
200 		writel(timing->dsettle, reg);
201 	}
202 }
203 
204 #define DPHY_TIMER_INCR	0x28
205 int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
206 				struct ipu6_isys_csi2_config *cfg,
207 				const struct ipu6_isys_csi2_timing *timing,
208 				bool on)
209 {
210 	struct device *dev = &isys->adev->auxdev.dev;
211 	void __iomem *isys_base = isys->pdata->base;
212 	int ret = 0;
213 	u32 nlanes;
214 	u32 port;
215 
216 	if (!on)
217 		return 0;
218 
219 	port = cfg->port;
220 	nlanes = cfg->nlanes;
221 
222 	if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
223 		dev_warn(dev, "invalid port ID %d\n", port);
224 		return -EINVAL;
225 	}
226 
227 	ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes);
228 
229 	writel(DPHY_TIMER_INCR,
230 	       isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR);
231 
232 	/* set port cfg and rx timing */
233 	ipu6_isys_csi2_set_timing(isys, timing, port, nlanes);
234 
235 	ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes);
236 	if (ret)
237 		return ret;
238 
239 	ipu6_isys_csi2_rx_control(isys);
240 
241 	return 0;
242 }
243