xref: /OK3568_Linux_fs/kernel/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_cec.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
4  *
5  * Author: Shunqing Chen <csq@rock-chips.com>
6  */
7 
8 #include <linux/interrupt.h>
9 #include <linux/io.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/sched.h>
13 #include <linux/slab.h>
14 
15 #include <media/cec.h>
16 #include <media/cec-notifier.h>
17 
18 #include "rk_hdmirx.h"
19 #include "rk_hdmirx_cec.h"
20 
hdmirx_cec_write(struct hdmirx_cec * cec,int reg,u32 val)21 static void hdmirx_cec_write(struct hdmirx_cec *cec, int reg, u32 val)
22 {
23 	cec->ops->write(cec->hdmirx, reg, val);
24 }
25 
hdmirx_cec_read(struct hdmirx_cec * cec,int reg)26 static u32 hdmirx_cec_read(struct hdmirx_cec *cec, int reg)
27 {
28 	return cec->ops->read(cec->hdmirx, reg);
29 }
30 
hdmirx_cec_update_bits(struct hdmirx_cec * cec,int reg,u32 mask,u32 data)31 static void hdmirx_cec_update_bits(struct hdmirx_cec *cec, int reg, u32 mask,
32 				   u32 data)
33 {
34 	u32 val = hdmirx_cec_read(cec, reg) & ~mask;
35 
36 	val |= (data & mask);
37 	hdmirx_cec_write(cec, reg, val);
38 }
39 
hdmirx_cec_log_addr(struct cec_adapter * adap,u8 logical_addr)40 static int hdmirx_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
41 {
42 	struct hdmirx_cec *cec = cec_get_drvdata(adap);
43 
44 	if (logical_addr == CEC_LOG_ADDR_INVALID)
45 		cec->addresses = 0;
46 	else
47 		cec->addresses |= BIT(logical_addr) | BIT(15);
48 
49 	hdmirx_cec_write(cec, CEC_ADDR, cec->addresses);
50 
51 	return 0;
52 }
53 
hdmirx_cec_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)54 static int hdmirx_cec_transmit(struct cec_adapter *adap, u8 attempts,
55 			       u32 signal_free_time, struct cec_msg *msg)
56 {
57 	struct hdmirx_cec *cec = cec_get_drvdata(adap);
58 	u32 data[4] = {0};
59 	int i, data_len, msg_len;
60 
61 	msg_len = msg->len;
62 	if (msg->len > 16)
63 		msg_len = 16;
64 	if (msg_len <= 0)
65 		return 0;
66 
67 	hdmirx_cec_write(cec, CEC_TX_COUNT, msg_len - 1);
68 	for (i = 0; i < msg_len; i++)
69 		data[i / 4] |= msg->msg[i] << (i % 4) * 8;
70 
71 	data_len = (msg_len + 3) / 4;
72 	for (i = 0; i < data_len; i++)
73 		hdmirx_cec_write(cec, CEC_TX_DATA3_0 + i * 4, data[i]);
74 
75 	hdmirx_cec_write(cec, CEC_TX_CONTROL, 0x1);
76 
77 	return 0;
78 }
79 
hdmirx_cec_hardirq(int irq,void * data)80 static irqreturn_t hdmirx_cec_hardirq(int irq, void *data)
81 {
82 	struct cec_adapter *adap = data;
83 	struct hdmirx_cec *cec = cec_get_drvdata(adap);
84 	u32 stat = hdmirx_cec_read(cec, CEC_INT_STATUS);
85 	irqreturn_t ret = IRQ_HANDLED;
86 	u32 val;
87 
88 	if (stat == 0)
89 		return IRQ_NONE;
90 
91 	hdmirx_cec_write(cec, CEC_INT_CLEAR, stat);
92 
93 	if (stat & CECTX_LINE_ERR) {
94 		cec->tx_status = CEC_TX_STATUS_ERROR;
95 		cec->tx_done = true;
96 		ret = IRQ_WAKE_THREAD;
97 	} else if (stat & CECTX_DONE) {
98 		cec->tx_status = CEC_TX_STATUS_OK;
99 		cec->tx_done = true;
100 		ret = IRQ_WAKE_THREAD;
101 	} else if (stat & CECTX_NACK) {
102 		cec->tx_status = CEC_TX_STATUS_NACK;
103 		cec->tx_done = true;
104 		ret = IRQ_WAKE_THREAD;
105 	}
106 
107 	if (stat & CECRX_EOM) {
108 		unsigned int len, i;
109 
110 		val = hdmirx_cec_read(cec, CEC_RX_COUNT_STATUS);
111 		/* rxbuffer locked status */
112 		if ((val & 0x80))
113 			return ret;
114 
115 		len = (val & 0xf) + 1;
116 		if (len > sizeof(cec->rx_msg.msg))
117 			len = sizeof(cec->rx_msg.msg);
118 
119 		for (i = 0; i < len; i++) {
120 			if (i % 4 == 0)
121 				val = hdmirx_cec_read(cec, CEC_RX_DATA3_0 + i / 4 * 4);
122 			cec->rx_msg.msg[i] = (val >> ((i % 4) * 8)) & 0xff;
123 		}
124 
125 		cec->rx_msg.len = len;
126 		smp_wmb(); /* receive RX msg */
127 		cec->rx_done = true;
128 		hdmirx_cec_write(cec, CEC_LOCK_CONTROL, 0x1);
129 
130 		ret = IRQ_WAKE_THREAD;
131 	}
132 
133 	return ret;
134 }
135 
hdmirx_cec_thread(int irq,void * data)136 static irqreturn_t hdmirx_cec_thread(int irq, void *data)
137 {
138 	struct cec_adapter *adap = data;
139 	struct hdmirx_cec *cec = cec_get_drvdata(adap);
140 
141 	if (cec->tx_done) {
142 		cec->tx_done = false;
143 		cec_transmit_attempt_done(adap, cec->tx_status);
144 	}
145 	if (cec->rx_done) {
146 		cec->rx_done = false;
147 		smp_rmb(); /* RX msg has been received */
148 		cec_received_msg(adap, &cec->rx_msg);
149 	}
150 
151 	return IRQ_HANDLED;
152 }
153 
hdmirx_cec_enable(struct cec_adapter * adap,bool enable)154 static int hdmirx_cec_enable(struct cec_adapter *adap, bool enable)
155 {
156 	struct hdmirx_cec *cec = cec_get_drvdata(adap);
157 
158 	if (!enable) {
159 		hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
160 		hdmirx_cec_write(cec, CEC_INT_CLEAR, 0);
161 		if (cec->ops->disable)
162 			cec->ops->disable(cec->hdmirx);
163 	} else {
164 		unsigned int irqs;
165 
166 		hdmirx_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
167 		if (cec->ops->enable)
168 			cec->ops->enable(cec->hdmirx);
169 		hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
170 
171 		irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
172 		hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
173 	}
174 
175 	return 0;
176 }
177 
178 static const struct cec_adap_ops hdmirx_cec_ops = {
179 	.adap_enable = hdmirx_cec_enable,
180 	.adap_log_addr = hdmirx_cec_log_addr,
181 	.adap_transmit = hdmirx_cec_transmit,
182 };
183 
hdmirx_cec_del(void * data)184 static void hdmirx_cec_del(void *data)
185 {
186 	struct hdmirx_cec *cec = data;
187 
188 	cec_delete_adapter(cec->adap);
189 }
190 
rk_hdmirx_cec_register(struct hdmirx_cec_data * data)191 struct hdmirx_cec *rk_hdmirx_cec_register(struct hdmirx_cec_data *data)
192 {
193 	struct hdmirx_cec *cec;
194 	int ret;
195 
196 	unsigned int irqs;
197 
198 	if (!data)
199 		return NULL;
200 
201 	/*
202 	 * Our device is just a convenience - we want to link to the real
203 	 * hardware device here, so that userspace can see the association
204 	 * between the HDMI hardware and its associated CEC chardev.
205 	 */
206 	cec = devm_kzalloc(data->dev, sizeof(*cec), GFP_KERNEL);
207 	if (!cec)
208 		return NULL;
209 
210 	cec->dev = data->dev;
211 	cec->irq = data->irq;
212 	cec->ops = data->ops;
213 	cec->hdmirx = data->hdmirx;
214 	cec->edid = (struct edid *)data->edid;
215 
216 	hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
217 	hdmirx_cec_update_bits(cec, CEC_CONFIG, RX_AUTO_DRIVE_ACKNOWLEDGE,
218 			       RX_AUTO_DRIVE_ACKNOWLEDGE);
219 
220 	hdmirx_cec_write(cec, CEC_TX_COUNT, 0);
221 	hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
222 	hdmirx_cec_write(cec, CEC_INT_CLEAR, ~0);
223 
224 	cec->adap = cec_allocate_adapter(&hdmirx_cec_ops, cec, "rk-hdmirx",
225 					 CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
226 					 CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
227 					 CEC_MAX_LOG_ADDRS);
228 	if (IS_ERR(cec->adap)) {
229 		dev_err(cec->dev, "cec adap allocate failed!\n");
230 		return NULL;
231 	}
232 
233 	/* override the module pointer */
234 	cec->adap->owner = THIS_MODULE;
235 
236 	ret = devm_add_action(cec->dev, hdmirx_cec_del, cec);
237 	if (ret) {
238 		cec_delete_adapter(cec->adap);
239 		return NULL;
240 	}
241 
242 	cec->notify = cec_notifier_cec_adap_register(cec->dev,
243 						     NULL, cec->adap);
244 	if (!cec->notify) {
245 		dev_err(cec->dev, "cec notify register failed!\n");
246 		return NULL;
247 	}
248 
249 	ret = cec_register_adapter(cec->adap, cec->dev);
250 	if (ret < 0) {
251 		dev_err(cec->dev, "cec register adapter failed!\n");
252 		cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
253 		return NULL;
254 	}
255 
256 	/* The TV functionality can only map to physical address 0 */
257 	cec_s_phys_addr(cec->adap, 0, false);
258 
259 	ret = devm_request_threaded_irq(cec->dev, cec->irq,
260 					hdmirx_cec_hardirq,
261 					hdmirx_cec_thread, IRQF_ONESHOT,
262 					"rk_hdmirx_cec", cec->adap);
263 	if (ret) {
264 		dev_err(cec->dev, "cec irq request failed!\n");
265 		cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
266 		dev_err(cec->dev, "request cec irq thread failed!\n");
267 		return NULL;
268 	}
269 
270 	irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
271 	hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
272 
273 	/*
274 	 * CEC documentation says we must not call cec_delete_adapter
275 	 * after a successful call to cec_register_adapter().
276 	 */
277 	devm_remove_action(cec->dev, hdmirx_cec_del, cec);
278 
279 	return cec;
280 }
281 
rk_hdmirx_cec_unregister(struct hdmirx_cec * cec)282 void rk_hdmirx_cec_unregister(struct hdmirx_cec *cec)
283 {
284 	if (!cec)
285 		return;
286 
287 	cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
288 	cec_unregister_adapter(cec->adap);
289 }
290