• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) Rockchip Electronics Co.Ltd
4  * Author:
5  *      Algea Cao <algea.cao@rock-chips.com>
6  */
7 #include <linux/interrupt.h>
8 #include <linux/io.h>
9 #include <linux/module.h>
10 #include <linux/platform_device.h>
11 #include <linux/sched.h>
12 #include <linux/slab.h>
13 
14 #include <drm/drm_edid.h>
15 #include <drm/bridge/dw_hdmi.h>
16 
17 #include <media/cec.h>
18 #include <media/cec-notifier.h>
19 
20 #include "dw-hdmi-qp-cec.h"
21 
22 enum {
23 	CEC_TX_CONTROL		= 0x1000,
24 	CEC_CTRL_CLEAR		= BIT(0),
25 	CEC_CTRL_START		= BIT(0),
26 
27 	CEC_STAT_DONE		= BIT(0),
28 	CEC_STAT_NACK		= BIT(1),
29 	CEC_STAT_ARBLOST	= BIT(2),
30 	CEC_STAT_LINE_ERR	= BIT(3),
31 	CEC_STAT_RETRANS_FAIL	= BIT(4),
32 	CEC_STAT_DISCARD	= BIT(5),
33 	CEC_STAT_TX_BUSY	= BIT(8),
34 	CEC_STAT_RX_BUSY	= BIT(9),
35 	CEC_STAT_DRIVE_ERR	= BIT(10),
36 	CEC_STAT_EOM		= BIT(11),
37 	CEC_STAT_NOTIFY_ERR	= BIT(12),
38 
39 	CEC_CONFIG		= 0x1008,
40 	CEC_ADDR		= 0x100c,
41 	CEC_TX_CNT		= 0x1020,
42 	CEC_RX_CNT		= 0x1040,
43 	CEC_TX_DATA3_0		= 0x1024,
44 	CEC_RX_DATA3_0		= 0x1044,
45 	CEC_LOCK_CONTROL	= 0x1054,
46 
47 	CEC_INT_STATUS		= 0x4000,
48 	CEC_INT_MASK_N		= 0x4004,
49 	CEC_INT_CLEAR		= 0x4008,
50 };
51 
52 struct dw_hdmi_qp_cec {
53 	struct dw_hdmi_qp *hdmi;
54 	const struct dw_hdmi_qp_cec_ops *ops;
55 	u32 addresses;
56 	struct cec_adapter *adap;
57 	struct cec_msg rx_msg;
58 	unsigned int tx_status;
59 	bool tx_done;
60 	bool rx_done;
61 	struct cec_notifier *notify;
62 	int irq;
63 };
64 
dw_hdmi_qp_write(struct dw_hdmi_qp_cec * cec,u32 val,int offset)65 static void dw_hdmi_qp_write(struct dw_hdmi_qp_cec *cec, u32 val, int offset)
66 {
67 	cec->ops->write(cec->hdmi, val, offset);
68 }
69 
dw_hdmi_qp_read(struct dw_hdmi_qp_cec * cec,int offset)70 static u32 dw_hdmi_qp_read(struct dw_hdmi_qp_cec *cec, int offset)
71 {
72 	return cec->ops->read(cec->hdmi, offset);
73 }
74 
dw_hdmi_qp_cec_log_addr(struct cec_adapter * adap,u8 logical_addr)75 static int dw_hdmi_qp_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
76 {
77 	struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
78 
79 	if (logical_addr == CEC_LOG_ADDR_INVALID)
80 		cec->addresses = 0;
81 	else
82 		cec->addresses |= BIT(logical_addr) | BIT(15);
83 
84 	dw_hdmi_qp_write(cec, cec->addresses, CEC_ADDR);
85 
86 	return 0;
87 }
88 
dw_hdmi_qp_cec_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)89 static int dw_hdmi_qp_cec_transmit(struct cec_adapter *adap, u8 attempts,
90 				   u32 signal_free_time, struct cec_msg *msg)
91 {
92 	struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
93 	unsigned int i;
94 	u32 val;
95 
96 	for (i = 0; i < msg->len; i++) {
97 		if (!(i % 4))
98 			val = msg->msg[i];
99 		if ((i % 4) == 1)
100 			val |= msg->msg[i] << 8;
101 		if ((i % 4) == 2)
102 			val |= msg->msg[i] << 16;
103 		if ((i % 4) == 3)
104 			val |= msg->msg[i] << 24;
105 
106 		if (i == (msg->len - 1) || (i % 4) == 3)
107 			dw_hdmi_qp_write(cec, val, CEC_TX_DATA3_0 + (i / 4) * 4);
108 	}
109 
110 	dw_hdmi_qp_write(cec, msg->len - 1, CEC_TX_CNT);
111 	dw_hdmi_qp_write(cec, CEC_CTRL_START, CEC_TX_CONTROL);
112 
113 	return 0;
114 }
115 
dw_hdmi_qp_cec_hardirq(int irq,void * data)116 static irqreturn_t dw_hdmi_qp_cec_hardirq(int irq, void *data)
117 {
118 	struct cec_adapter *adap = data;
119 	struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
120 	u32 stat = dw_hdmi_qp_read(cec, CEC_INT_STATUS);
121 	irqreturn_t ret = IRQ_HANDLED;
122 
123 	if (stat == 0)
124 		return IRQ_NONE;
125 
126 	dw_hdmi_qp_write(cec, stat, CEC_INT_CLEAR);
127 
128 	if (stat & CEC_STAT_LINE_ERR) {
129 		cec->tx_status = CEC_TX_STATUS_ERROR;
130 		cec->tx_done = true;
131 		ret = IRQ_WAKE_THREAD;
132 	} else if (stat & CEC_STAT_DONE) {
133 		cec->tx_status = CEC_TX_STATUS_OK;
134 		cec->tx_done = true;
135 		ret = IRQ_WAKE_THREAD;
136 	} else if (stat & CEC_STAT_NACK) {
137 		cec->tx_status = CEC_TX_STATUS_NACK;
138 		cec->tx_done = true;
139 		ret = IRQ_WAKE_THREAD;
140 	}
141 
142 	if (stat & CEC_STAT_EOM) {
143 		unsigned int len, i, val;
144 
145 		val = dw_hdmi_qp_read(cec, CEC_RX_CNT);
146 		len = (val & 0xf) + 1;
147 
148 		if (len > sizeof(cec->rx_msg.msg))
149 			len = sizeof(cec->rx_msg.msg);
150 
151 		for (i = 0; i < 4; i++) {
152 			val = dw_hdmi_qp_read(cec, CEC_RX_DATA3_0 + i);
153 			cec->rx_msg.msg[i * 4] = val & 0xff;
154 			cec->rx_msg.msg[i * 4 + 1] = (val >> 8) & 0xff;
155 			cec->rx_msg.msg[i * 4 + 2] = (val >> 16) & 0xff;
156 			cec->rx_msg.msg[i * 4 + 3] = (val >> 24) & 0xff;
157 		}
158 
159 		dw_hdmi_qp_write(cec, 1, CEC_LOCK_CONTROL);
160 
161 		cec->rx_msg.len = len;
162 		cec->rx_done = true;
163 
164 		ret = IRQ_WAKE_THREAD;
165 	}
166 
167 	return ret;
168 }
169 
dw_hdmi_qp_cec_thread(int irq,void * data)170 static irqreturn_t dw_hdmi_qp_cec_thread(int irq, void *data)
171 {
172 	struct cec_adapter *adap = data;
173 	struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
174 
175 	if (cec->tx_done) {
176 		cec->tx_done = false;
177 		cec_transmit_attempt_done(adap, cec->tx_status);
178 	}
179 	if (cec->rx_done) {
180 		cec->rx_done = false;
181 		cec_received_msg(adap, &cec->rx_msg);
182 	}
183 	return IRQ_HANDLED;
184 }
185 
dw_hdmi_qp_cec_enable(struct cec_adapter * adap,bool enable)186 static int dw_hdmi_qp_cec_enable(struct cec_adapter *adap, bool enable)
187 {
188 	struct dw_hdmi_qp_cec *cec = cec_get_drvdata(adap);
189 
190 	if (!enable) {
191 		dw_hdmi_qp_write(cec, 0, CEC_INT_MASK_N);
192 		dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
193 		cec->ops->disable(cec->hdmi);
194 	} else {
195 		unsigned int irqs;
196 
197 		cec->ops->enable(cec->hdmi);
198 
199 		dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
200 		dw_hdmi_qp_write(cec, 1, CEC_LOCK_CONTROL);
201 
202 		dw_hdmi_qp_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
203 
204 		irqs = CEC_STAT_LINE_ERR | CEC_STAT_NACK | CEC_STAT_EOM |
205 		       CEC_STAT_DONE;
206 		dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
207 		dw_hdmi_qp_write(cec, irqs, CEC_INT_MASK_N);
208 	}
209 	return 0;
210 }
211 
212 static const struct cec_adap_ops dw_hdmi_qp_cec_ops = {
213 	.adap_enable = dw_hdmi_qp_cec_enable,
214 	.adap_log_addr = dw_hdmi_qp_cec_log_addr,
215 	.adap_transmit = dw_hdmi_qp_cec_transmit,
216 };
217 
dw_hdmi_qp_cec_del(void * data)218 static void dw_hdmi_qp_cec_del(void *data)
219 {
220 	struct dw_hdmi_qp_cec *cec = data;
221 
222 	cec_delete_adapter(cec->adap);
223 }
224 
dw_hdmi_qp_cec_probe(struct platform_device * pdev)225 static int dw_hdmi_qp_cec_probe(struct platform_device *pdev)
226 {
227 	struct dw_hdmi_qp_cec_data *data = dev_get_platdata(&pdev->dev);
228 	struct dw_hdmi_qp_cec *cec;
229 	int ret;
230 
231 	if (!data) {
232 		dev_err(&pdev->dev, "can't get data\n");
233 		return -ENXIO;
234 	}
235 
236 	/*
237 	 * Our device is just a convenience - we want to link to the real
238 	 * hardware device here, so that userspace can see the association
239 	 * between the HDMI hardware and its associated CEC chardev.
240 	 */
241 	cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
242 	if (!cec)
243 		return -ENOMEM;
244 
245 	cec->ops = data->ops;
246 	cec->hdmi = data->hdmi;
247 	cec->irq = data->irq;
248 
249 	platform_set_drvdata(pdev, cec);
250 
251 	dw_hdmi_qp_write(cec, 0, CEC_TX_CNT);
252 	dw_hdmi_qp_write(cec, ~0, CEC_INT_CLEAR);
253 	dw_hdmi_qp_write(cec, 0, CEC_INT_MASK_N);
254 
255 	cec->adap = cec_allocate_adapter(&dw_hdmi_qp_cec_ops, cec, "dw_hdmi_qp",
256 					 CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
257 					 CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
258 					 CEC_MAX_LOG_ADDRS);
259 	if (IS_ERR(cec->adap)) {
260 		dev_err(&pdev->dev, "cec allocate adapter failed\n");
261 		return PTR_ERR(cec->adap);
262 	}
263 
264 	dw_hdmi_qp_set_cec_adap(cec->hdmi, cec->adap);
265 
266 	/* override the module pointer */
267 	cec->adap->owner = THIS_MODULE;
268 
269 	ret = devm_add_action(&pdev->dev, dw_hdmi_qp_cec_del, cec);
270 	if (ret) {
271 		dev_err(&pdev->dev, "cec add action failed\n");
272 		cec_delete_adapter(cec->adap);
273 		return ret;
274 	}
275 
276 	if (cec->irq < 0) {
277 		ret = cec->irq;
278 		dev_err(&pdev->dev, "cec get irq failed\n");
279 		return ret;
280 	}
281 
282 	ret = devm_request_threaded_irq(&pdev->dev, cec->irq,
283 					dw_hdmi_qp_cec_hardirq,
284 					dw_hdmi_qp_cec_thread, IRQF_SHARED,
285 					"dw-hdmi-qp-cec", cec->adap);
286 	if (ret < 0) {
287 		dev_err(&pdev->dev, "cec request irq thread failed\n");
288 		return ret;
289 	}
290 
291 	cec->notify = cec_notifier_cec_adap_register(pdev->dev.parent,
292 						     NULL, cec->adap);
293 	if (!cec->notify) {
294 		dev_err(&pdev->dev, "cec notifier adap register failed\n");
295 		return -ENOMEM;
296 	}
297 
298 	ret = cec_register_adapter(cec->adap, pdev->dev.parent);
299 	if (ret < 0) {
300 		dev_err(&pdev->dev, "cec adap register failed\n");
301 		cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
302 		return ret;
303 	}
304 
305 	/*
306 	 * CEC documentation says we must not call cec_delete_adapter
307 	 * after a successful call to cec_register_adapter().
308 	 */
309 	devm_remove_action(&pdev->dev, dw_hdmi_qp_cec_del, cec);
310 
311 	return 0;
312 }
313 
dw_hdmi_qp_cec_remove(struct platform_device * pdev)314 static int dw_hdmi_qp_cec_remove(struct platform_device *pdev)
315 {
316 	struct dw_hdmi_qp_cec *cec = platform_get_drvdata(pdev);
317 
318 	cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
319 	cec_unregister_adapter(cec->adap);
320 
321 	return 0;
322 }
323 
324 static struct platform_driver dw_hdmi_qp_cec_driver = {
325 	.probe	= dw_hdmi_qp_cec_probe,
326 	.remove	= dw_hdmi_qp_cec_remove,
327 	.driver = {
328 		.name = "dw-hdmi-qp-cec",
329 	},
330 };
331 module_platform_driver(dw_hdmi_qp_cec_driver);
332 
333 MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
334 MODULE_DESCRIPTION("Synopsys Designware HDMI QP CEC driver");
335 MODULE_LICENSE("GPL");
336 MODULE_ALIAS(PLATFORM_MODULE_PREFIX "dw-hdmi-qp-cec");
337