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