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 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 25 static u32 hdmirx_cec_read(struct hdmirx_cec *cec, int reg) 26 { 27 return cec->ops->read(cec->hdmirx, reg); 28 } 29 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 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 */ 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 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 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 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 187 static void hdmirx_cec_del(void *data) 188 { 189 struct hdmirx_cec *cec = data; 190 191 cec_delete_adapter(cec->adap); 192 } 193 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 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