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