Commit | Line | Data |
---|---|---|
8f83f268 JQ |
1 | /* |
2 | * Copyright (c) 2014 MediaTek Inc. | |
3 | * Author: Jie Qiu <jie.qiu@mediatek.com> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | */ | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/i2c.h> | |
17 | #include <linux/time.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/errno.h> | |
20 | #include <linux/err.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/clk.h> | |
23 | #include <linux/slab.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/iopoll.h> | |
26 | #include <linux/of_address.h> | |
27 | #include <linux/of_irq.h> | |
28 | #include <linux/of_platform.h> | |
29 | ||
30 | #define SIF1_CLOK (288) | |
31 | #define DDC_DDCMCTL0 (0x0) | |
32 | #define DDCM_ODRAIN BIT(31) | |
33 | #define DDCM_CLK_DIV_OFFSET (16) | |
34 | #define DDCM_CLK_DIV_MASK (0xfff << 16) | |
35 | #define DDCM_CS_STATUS BIT(4) | |
36 | #define DDCM_SCL_STATE BIT(3) | |
37 | #define DDCM_SDA_STATE BIT(2) | |
38 | #define DDCM_SM0EN BIT(1) | |
39 | #define DDCM_SCL_STRECH BIT(0) | |
40 | #define DDC_DDCMCTL1 (0x4) | |
41 | #define DDCM_ACK_OFFSET (16) | |
42 | #define DDCM_ACK_MASK (0xff << 16) | |
43 | #define DDCM_PGLEN_OFFSET (8) | |
44 | #define DDCM_PGLEN_MASK (0x7 << 8) | |
45 | #define DDCM_SIF_MODE_OFFSET (4) | |
46 | #define DDCM_SIF_MODE_MASK (0x7 << 4) | |
47 | #define DDCM_START (0x1) | |
48 | #define DDCM_WRITE_DATA (0x2) | |
49 | #define DDCM_STOP (0x3) | |
50 | #define DDCM_READ_DATA_NO_ACK (0x4) | |
51 | #define DDCM_READ_DATA_ACK (0x5) | |
52 | #define DDCM_TRI BIT(0) | |
53 | #define DDC_DDCMD0 (0x8) | |
54 | #define DDCM_DATA3 (0xff << 24) | |
55 | #define DDCM_DATA2 (0xff << 16) | |
56 | #define DDCM_DATA1 (0xff << 8) | |
57 | #define DDCM_DATA0 (0xff << 0) | |
58 | #define DDC_DDCMD1 (0xc) | |
59 | #define DDCM_DATA7 (0xff << 24) | |
60 | #define DDCM_DATA6 (0xff << 16) | |
61 | #define DDCM_DATA5 (0xff << 8) | |
62 | #define DDCM_DATA4 (0xff << 0) | |
63 | ||
64 | struct mtk_hdmi_ddc { | |
65 | struct i2c_adapter adap; | |
66 | struct clk *clk; | |
67 | void __iomem *regs; | |
68 | }; | |
69 | ||
70 | static inline void sif_set_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, | |
71 | unsigned int val) | |
72 | { | |
73 | writel(readl(ddc->regs + offset) | val, ddc->regs + offset); | |
74 | } | |
75 | ||
76 | static inline void sif_clr_bit(struct mtk_hdmi_ddc *ddc, unsigned int offset, | |
77 | unsigned int val) | |
78 | { | |
79 | writel(readl(ddc->regs + offset) & ~val, ddc->regs + offset); | |
80 | } | |
81 | ||
82 | static inline bool sif_bit_is_set(struct mtk_hdmi_ddc *ddc, unsigned int offset, | |
83 | unsigned int val) | |
84 | { | |
85 | return (readl(ddc->regs + offset) & val) == val; | |
86 | } | |
87 | ||
88 | static inline void sif_write_mask(struct mtk_hdmi_ddc *ddc, unsigned int offset, | |
89 | unsigned int mask, unsigned int shift, | |
90 | unsigned int val) | |
91 | { | |
92 | unsigned int tmp; | |
93 | ||
94 | tmp = readl(ddc->regs + offset); | |
95 | tmp &= ~mask; | |
96 | tmp |= (val << shift) & mask; | |
97 | writel(tmp, ddc->regs + offset); | |
98 | } | |
99 | ||
100 | static inline unsigned int sif_read_mask(struct mtk_hdmi_ddc *ddc, | |
101 | unsigned int offset, unsigned int mask, | |
102 | unsigned int shift) | |
103 | { | |
104 | return (readl(ddc->regs + offset) & mask) >> shift; | |
105 | } | |
106 | ||
107 | static void ddcm_trigger_mode(struct mtk_hdmi_ddc *ddc, int mode) | |
108 | { | |
109 | u32 val; | |
110 | ||
111 | sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK, | |
112 | DDCM_SIF_MODE_OFFSET, mode); | |
113 | sif_set_bit(ddc, DDC_DDCMCTL1, DDCM_TRI); | |
114 | readl_poll_timeout(ddc->regs + DDC_DDCMCTL1, val, | |
115 | (val & DDCM_TRI) != DDCM_TRI, 4, 20000); | |
116 | } | |
117 | ||
118 | static int mtk_hdmi_ddc_read_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) | |
119 | { | |
120 | struct device *dev = ddc->adap.dev.parent; | |
121 | u32 remain_count, ack_count, ack_final, read_count, temp_count; | |
122 | u32 index = 0; | |
123 | u32 ack; | |
124 | int i; | |
125 | ||
126 | ddcm_trigger_mode(ddc, DDCM_START); | |
127 | sif_write_mask(ddc, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01); | |
128 | sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, | |
129 | 0x00); | |
130 | ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); | |
131 | ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); | |
132 | dev_dbg(dev, "ack = 0x%x\n", ack); | |
133 | if (ack != 0x01) { | |
134 | dev_err(dev, "i2c ack err!\n"); | |
135 | return -ENXIO; | |
136 | } | |
137 | ||
138 | remain_count = msg->len; | |
139 | ack_count = (msg->len - 1) / 8; | |
140 | ack_final = 0; | |
141 | ||
142 | while (remain_count > 0) { | |
143 | if (ack_count > 0) { | |
144 | read_count = 8; | |
145 | ack_final = 0; | |
146 | ack_count--; | |
147 | } else { | |
148 | read_count = remain_count; | |
149 | ack_final = 1; | |
150 | } | |
151 | ||
152 | sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, | |
153 | DDCM_PGLEN_OFFSET, read_count - 1); | |
154 | ddcm_trigger_mode(ddc, (ack_final == 1) ? | |
155 | DDCM_READ_DATA_NO_ACK : | |
156 | DDCM_READ_DATA_ACK); | |
157 | ||
158 | ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, | |
159 | DDCM_ACK_OFFSET); | |
160 | temp_count = 0; | |
161 | while (((ack & (1 << temp_count)) != 0) && (temp_count < 8)) | |
162 | temp_count++; | |
163 | if (((ack_final == 1) && (temp_count != (read_count - 1))) || | |
164 | ((ack_final == 0) && (temp_count != read_count))) { | |
165 | dev_err(dev, "Address NACK! ACK(0x%x)\n", ack); | |
166 | break; | |
167 | } | |
168 | ||
169 | for (i = read_count; i >= 1; i--) { | |
170 | int shift; | |
171 | int offset; | |
172 | ||
173 | if (i > 4) { | |
174 | offset = DDC_DDCMD1; | |
175 | shift = (i - 5) * 8; | |
176 | } else { | |
177 | offset = DDC_DDCMD0; | |
178 | shift = (i - 1) * 8; | |
179 | } | |
180 | ||
181 | msg->buf[index + i - 1] = sif_read_mask(ddc, offset, | |
182 | 0xff << shift, | |
183 | shift); | |
184 | } | |
185 | ||
186 | remain_count -= read_count; | |
187 | index += read_count; | |
188 | } | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | static int mtk_hdmi_ddc_write_msg(struct mtk_hdmi_ddc *ddc, struct i2c_msg *msg) | |
194 | { | |
195 | struct device *dev = ddc->adap.dev.parent; | |
196 | u32 ack; | |
197 | ||
198 | ddcm_trigger_mode(ddc, DDCM_START); | |
199 | sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1); | |
200 | sif_write_mask(ddc, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]); | |
201 | sif_write_mask(ddc, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, | |
202 | 0x1); | |
203 | ddcm_trigger_mode(ddc, DDCM_WRITE_DATA); | |
204 | ||
205 | ack = sif_read_mask(ddc, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); | |
206 | dev_dbg(dev, "ack = %d\n", ack); | |
207 | ||
208 | if (ack != 0x03) { | |
209 | dev_err(dev, "i2c ack err!\n"); | |
210 | return -EIO; | |
211 | } | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
216 | static int mtk_hdmi_ddc_xfer(struct i2c_adapter *adapter, | |
217 | struct i2c_msg *msgs, int num) | |
218 | { | |
219 | struct mtk_hdmi_ddc *ddc = adapter->algo_data; | |
220 | struct device *dev = adapter->dev.parent; | |
221 | int ret; | |
222 | int i; | |
223 | ||
224 | if (!ddc) { | |
225 | dev_err(dev, "invalid arguments\n"); | |
226 | return -EINVAL; | |
227 | } | |
228 | ||
229 | sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SCL_STRECH); | |
230 | sif_set_bit(ddc, DDC_DDCMCTL0, DDCM_SM0EN); | |
231 | sif_clr_bit(ddc, DDC_DDCMCTL0, DDCM_ODRAIN); | |
232 | ||
233 | if (sif_bit_is_set(ddc, DDC_DDCMCTL1, DDCM_TRI)) { | |
234 | dev_err(dev, "ddc line is busy!\n"); | |
235 | return -EBUSY; | |
236 | } | |
237 | ||
238 | sif_write_mask(ddc, DDC_DDCMCTL0, DDCM_CLK_DIV_MASK, | |
239 | DDCM_CLK_DIV_OFFSET, SIF1_CLOK); | |
240 | ||
241 | for (i = 0; i < num; i++) { | |
242 | struct i2c_msg *msg = &msgs[i]; | |
243 | ||
244 | dev_dbg(dev, "i2c msg, adr:0x%x, flags:%d, len :0x%x\n", | |
245 | msg->addr, msg->flags, msg->len); | |
246 | ||
247 | if (msg->flags & I2C_M_RD) | |
248 | ret = mtk_hdmi_ddc_read_msg(ddc, msg); | |
249 | else | |
250 | ret = mtk_hdmi_ddc_write_msg(ddc, msg); | |
251 | if (ret < 0) | |
252 | goto xfer_end; | |
253 | } | |
254 | ||
255 | ddcm_trigger_mode(ddc, DDCM_STOP); | |
256 | ||
257 | return i; | |
258 | ||
259 | xfer_end: | |
260 | ddcm_trigger_mode(ddc, DDCM_STOP); | |
261 | dev_err(dev, "ddc failed!\n"); | |
262 | return ret; | |
263 | } | |
264 | ||
265 | static u32 mtk_hdmi_ddc_func(struct i2c_adapter *adapter) | |
266 | { | |
267 | return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; | |
268 | } | |
269 | ||
270 | static const struct i2c_algorithm mtk_hdmi_ddc_algorithm = { | |
271 | .master_xfer = mtk_hdmi_ddc_xfer, | |
272 | .functionality = mtk_hdmi_ddc_func, | |
273 | }; | |
274 | ||
275 | static int mtk_hdmi_ddc_probe(struct platform_device *pdev) | |
276 | { | |
277 | struct device *dev = &pdev->dev; | |
278 | struct mtk_hdmi_ddc *ddc; | |
279 | struct resource *mem; | |
280 | int ret; | |
281 | ||
282 | ddc = devm_kzalloc(dev, sizeof(struct mtk_hdmi_ddc), GFP_KERNEL); | |
283 | if (!ddc) | |
284 | return -ENOMEM; | |
285 | ||
286 | ddc->clk = devm_clk_get(dev, "ddc-i2c"); | |
287 | if (IS_ERR(ddc->clk)) { | |
288 | dev_err(dev, "get ddc_clk failed: %p ,\n", ddc->clk); | |
289 | return PTR_ERR(ddc->clk); | |
290 | } | |
291 | ||
292 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
293 | ddc->regs = devm_ioremap_resource(&pdev->dev, mem); | |
294 | if (IS_ERR(ddc->regs)) | |
295 | return PTR_ERR(ddc->regs); | |
296 | ||
297 | ret = clk_prepare_enable(ddc->clk); | |
298 | if (ret) { | |
299 | dev_err(dev, "enable ddc clk failed!\n"); | |
300 | return ret; | |
301 | } | |
302 | ||
303 | strlcpy(ddc->adap.name, "mediatek-hdmi-ddc", sizeof(ddc->adap.name)); | |
304 | ddc->adap.owner = THIS_MODULE; | |
305 | ddc->adap.class = I2C_CLASS_DDC; | |
306 | ddc->adap.algo = &mtk_hdmi_ddc_algorithm; | |
307 | ddc->adap.retries = 3; | |
308 | ddc->adap.dev.of_node = dev->of_node; | |
309 | ddc->adap.algo_data = ddc; | |
310 | ddc->adap.dev.parent = &pdev->dev; | |
311 | ||
312 | ret = i2c_add_adapter(&ddc->adap); | |
313 | if (ret < 0) { | |
314 | dev_err(dev, "failed to add bus to i2c core\n"); | |
315 | goto err_clk_disable; | |
316 | } | |
317 | ||
318 | platform_set_drvdata(pdev, ddc); | |
319 | ||
320 | dev_dbg(dev, "ddc->adap: %p\n", &ddc->adap); | |
321 | dev_dbg(dev, "ddc->clk: %p\n", ddc->clk); | |
322 | dev_dbg(dev, "physical adr: %pa, end: %pa\n", &mem->start, | |
323 | &mem->end); | |
324 | ||
325 | return 0; | |
326 | ||
327 | err_clk_disable: | |
328 | clk_disable_unprepare(ddc->clk); | |
329 | return ret; | |
330 | } | |
331 | ||
332 | static int mtk_hdmi_ddc_remove(struct platform_device *pdev) | |
333 | { | |
334 | struct mtk_hdmi_ddc *ddc = platform_get_drvdata(pdev); | |
335 | ||
336 | i2c_del_adapter(&ddc->adap); | |
337 | clk_disable_unprepare(ddc->clk); | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
342 | static const struct of_device_id mtk_hdmi_ddc_match[] = { | |
343 | { .compatible = "mediatek,mt8173-hdmi-ddc", }, | |
344 | {}, | |
345 | }; | |
346 | ||
347 | struct platform_driver mtk_hdmi_ddc_driver = { | |
348 | .probe = mtk_hdmi_ddc_probe, | |
349 | .remove = mtk_hdmi_ddc_remove, | |
350 | .driver = { | |
351 | .name = "mediatek-hdmi-ddc", | |
352 | .of_match_table = mtk_hdmi_ddc_match, | |
353 | }, | |
354 | }; | |
355 | ||
356 | MODULE_AUTHOR("Jie Qiu <jie.qiu@mediatek.com>"); | |
357 | MODULE_DESCRIPTION("MediaTek HDMI DDC Driver"); | |
358 | MODULE_LICENSE("GPL v2"); |