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