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