File Index
Files and directories
Sign in
FILE

snps_hdmirx_cec.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.c C
  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
 20static void hdmirx_cec_write(struct hdmirx_cec *cec, int reg, u32 val)
 21{
 22	cec->ops->write(cec->hdmirx, reg, val);
 23}
 24
 25static u32 hdmirx_cec_read(struct hdmirx_cec *cec, int reg)
 26{
 27	return cec->ops->read(cec->hdmirx, reg);
 28}
 29
 30static 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
 39static 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 */
 56static 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
 79static 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
139static 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
157static 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
181static 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
187static void hdmirx_cec_del(void *data)
188{
189	struct hdmirx_cec *cec = data;
190
191	cec_delete_adapter(cec->adap);
192}
193
194struct 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
270void snps_hdmirx_cec_unregister(struct hdmirx_cec *cec)
271{
272	disable_irq(cec->irq);
273
274	cec_unregister_adapter(cec->adap);
275}
~/ Read-only file preview.