| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/mm.h> |
| #include <linux/major.h> |
| #include <linux/platform_device.h> |
| #include <linux/mutex.h> |
| #include <linux/cdev.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include <linux/of_gpio.h> |
| #include <linux/amlogic/media/frame_provider/tvin/tvin.h> |
| |
| /* Local include */ |
| #include "hdmi_rx_repeater.h" |
| #include "hdmi_rx_drv.h" |
| #include "hdmi_rx_hw.h" |
| #include "hdmi_rx_wrapper.h" |
| #include "hdmi_rx_edid.h" |
| /*edid original data from device*/ |
| static unsigned char receive_edid[MAX_RECEIVE_EDID]; |
| int receive_edid_len = MAX_RECEIVE_EDID; |
| MODULE_PARM_DESC(receive_edid, "\n receive_edid\n"); |
| module_param_array(receive_edid, byte, &receive_edid_len, 0664); |
| int tx_hpd_event; |
| int edid_len; |
| MODULE_PARM_DESC(edid_len, "\n edid_len\n"); |
| module_param(edid_len, int, 0664); |
| bool new_edid; |
| /*original bksv from device*/ |
| unsigned char receive_hdcp[MAX_KSV_LIST_SIZE]; |
| int hdcp_array_len = MAX_KSV_LIST_SIZE; |
| MODULE_PARM_DESC(receive_hdcp, "\n receive_hdcp\n"); |
| module_param_array(receive_hdcp, byte, &hdcp_array_len, 0664); |
| int hdcp_len; |
| int hdcp_repeat_depth; |
| bool new_hdcp; |
| bool start_auth_14; |
| MODULE_PARM_DESC(start_auth_14, "\n start_auth_14\n"); |
| module_param(start_auth_14, bool, 0664); |
| |
| bool repeat_plug; |
| MODULE_PARM_DESC(repeat_plug, "\n repeat_plug\n"); |
| module_param(repeat_plug, bool, 0664); |
| |
| int up_phy_addr;/*d c b a 4bit*/ |
| MODULE_PARM_DESC(up_phy_addr, "\n up_phy_addr\n"); |
| module_param(up_phy_addr, int, 0664); |
| int hdcp22_firm_switch_timeout; |
| |
| unsigned char *rx_get_dw_hdcp_addr(void) |
| { |
| return receive_hdcp; |
| } |
| |
| void rx_start_repeater_auth(void) |
| { |
| rx.hdcp.state = REPEATER_STATE_START; |
| start_auth_14 = 1; |
| rx.hdcp.delay = 0; |
| hdcp_len = 0; |
| hdcp_repeat_depth = 0; |
| rx.hdcp.dev_exceed = 0; |
| rx.hdcp.cascade_exceed = 0; |
| rx.hdcp.depth = 0; |
| rx.hdcp.count = 0; |
| memset(&receive_hdcp, 0, sizeof(receive_hdcp)); |
| } |
| |
| void rx_check_repeat(void) |
| { |
| struct hdcp14_topo_s *topo_data = (struct hdcp14_topo_s *)receive_hdcp; |
| |
| if (!hdmirx_repeat_support()) |
| return; |
| |
| if (rx.hdcp.repeat != repeat_plug) { |
| /*pull down hpd if downstream plug low*/ |
| /* rx_set_cur_hpd(0, 3); */ |
| rx_send_hpd_pulse(); |
| rx_pr("firm_change:%d,repeat_plug:%d,repeat:%d\n", |
| rx.firm_change, repeat_plug, rx.hdcp.repeat); |
| rx_set_repeat_signal(repeat_plug); |
| if (!repeat_plug) { |
| edid_len = 0; |
| rx.hdcp.dev_exceed = 0; |
| rx.hdcp.cascade_exceed = 0; |
| memset(&receive_hdcp, 0, sizeof(receive_hdcp)); |
| memset(&receive_edid, 0, sizeof(receive_edid)); |
| up_phy_addr = 0; |
| /*new_edid = true;*/ |
| /* rx_set_cur_hpd(1, 3); */ |
| /*rx.firm_change = 0;*/ |
| rx_pr("1firm_change:%d,repeat_plug:%d,repeat:%d\n", |
| rx.firm_change, repeat_plug, rx.hdcp.repeat); |
| } |
| } |
| |
| /*this is addition for the downstream edid too late*/ |
| if (new_edid && rx.hdcp.repeat && !rx.firm_change) { |
| /*check downstream plug when new plug occur*/ |
| /*check receive change*/ |
| /*it's contained in hwconfig*/ |
| hdmi_rx_top_edid_update(); |
| hdcp22_firm_switch_timeout = 0; |
| new_edid = false; |
| rx_send_hpd_pulse(); |
| } |
| |
| if (repeat_plug) { |
| switch (rx.hdcp.state) { |
| case REPEATER_STATE_START: |
| rx_pr("[RX] receive aksv\n"); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, |
| KSVLIST_TIMEOUT, 0); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, |
| KSVLIST_LOSTAUTH, 0); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, |
| KSVLIST_READY, 0); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_CASCADE_EXCEEDED, 0); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_DEVS_EXCEEDED, 0); |
| rx.hdcp.state = REPEATER_STATE_WAIT_KSV; |
| break; |
| |
| case REPEATER_STATE_WAIT_KSV: |
| if (!rx.cur_5v_sts) { |
| rx.hdcp.state = REPEATER_STATE_IDLE; |
| break; |
| } |
| if (hdmirx_rd_bits_dwc(DWC_HDCP_RPT_CTRL, WAITING_KSV)) { |
| rx.hdcp.delay++; |
| if (rx.hdcp.delay == 1) |
| rx_pr("[RX] receive ksv wait signal\n"); |
| if (rx.hdcp.delay < KSV_LIST_WR_MAX) { |
| break; |
| } else if (rx.hdcp.delay >= KSV_LIST_WAIT_DELAY) { |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, |
| KSVLIST_TIMEOUT, 1); |
| rx.hdcp.state = REPEATER_STATE_IDLE; |
| rx_pr("[RX] receive ksv wait timeout\n"); |
| } |
| if (rx_set_repeat_aksv(topo_data->ksv_list, |
| topo_data->device_count, |
| topo_data->depth, |
| topo_data->max_devs_exceeded, |
| topo_data->max_cascade_exceeded)) { |
| rx.hdcp.state = REPEATER_STATE_IDLE; |
| } |
| } |
| /*if support hdcp2.2 jump to wait_ack else to idle*/ |
| break; |
| |
| case REPEATER_STATE_WAIT_ACK: |
| /*if receive ack jump to idle else send auth_req*/ |
| break; |
| |
| case REPEATER_STATE_IDLE: |
| |
| break; |
| |
| default: |
| break; |
| } |
| } |
| /*hdr change from app*/ |
| /*if (new_hdr_lum) {*/ |
| /*hdmi_rx_ctrl_edid_update();*/ |
| /*new_hdr_lum = false;*/ |
| /*rx_send_hpd_pulse();*/ |
| /*}*/ |
| } |
| |
| void rx_reload_firm_reset(int reset) |
| { |
| if (reset) |
| hdmirx_load_firm_reset(reset); |
| else |
| rx_firm_reset_end(); |
| } |
| |
| void rx_firm_reset_end(void) |
| { |
| rx_pr("%s new_edid:%d\n", __func__, new_edid); |
| if (new_edid) { |
| new_edid = 0; |
| hdmi_rx_top_edid_update(); |
| } |
| rx.firm_change = 0; |
| } |
| |
| unsigned char *rx_get_dw_edid_addr(void) |
| { |
| return receive_edid; |
| } |
| |
| int rx_set_receiver_edid(const char *data, int len) |
| { |
| if (!data || !len) |
| return false; |
| |
| if (len > MAX_RECEIVE_EDID || len < 3) { |
| memset(receive_edid, 0, sizeof(receive_edid)); |
| edid_len = 0; |
| } else { |
| memcpy(receive_edid, data, len); |
| edid_len = len; |
| } |
| new_edid = true; |
| return true; |
| } |
| |
| void rx_hdcp14_resume(void) |
| { |
| hdcp22_kill_esm = 0; |
| /* extcon_set_state_sync(rx.rx_excton_rx22, EXTCON_DISP_HDMI, 0); */ |
| rx_hdcp22_send_uevent(0); |
| hdmirx_wr_dwc(DWC_HDCP22_CONTROL, 0x1000); |
| /* extcon_set_state_sync(rx.rx_excton_rx22, EXTCON_DISP_HDMI, 1); */ |
| rx_hdcp22_send_uevent(1); |
| hpd_to_esm = 1; |
| rx_pr("hdcp14 on\n"); |
| } |
| |
| void rx_set_repeater_support(bool enable) |
| { |
| downstream_repeat_support = enable; |
| repeat_plug = enable; |
| rx_pr("****************=%d\n", downstream_repeat_support); |
| } |
| EXPORT_SYMBOL(rx_set_repeater_support); |
| |
| bool rx_poll_dwc(u16 addr, u32 exp_data, |
| u32 mask, u32 max_try) |
| { |
| u32 rd_data; |
| u32 cnt = 0; |
| u8 done = 0; |
| |
| rd_data = hdmirx_rd_dwc(addr); |
| while (((cnt < max_try) || (max_try == 0)) && (done != 1)) { |
| if ((rd_data & mask) == (exp_data & mask)) { |
| done = 1; |
| } else { |
| cnt++; |
| rd_data = hdmirx_rd_dwc(addr); |
| } |
| } |
| if (log_level & VIDEO_LOG) |
| rx_pr("poll dwc cnt :%d\n", cnt); |
| if (done == 0) { |
| /* if(log_level & ERR_LOG) */ |
| rx_pr("poll dwc%x time-out!\n", addr); |
| return false; |
| } |
| return true; |
| } |
| |
| bool rx_set_repeat_aksv(unsigned char *data, int len, int depth, |
| bool dev_exceed, bool cascade_exceed) |
| { |
| int i; |
| bool ksvlist_ready = 0; |
| |
| if (data == 0 || ((depth == 0 || len == 0) && |
| !dev_exceed && !cascade_exceed)) |
| return false; |
| rx_pr("set ksv list len:%d,depth:%d, exceed count:%d,cascade:%d\n", |
| len, depth, dev_exceed, cascade_exceed); |
| /*set repeat depth*/ |
| if (depth <= MAX_REPEAT_DEPTH && !cascade_exceed) { |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_CASCADE_EXCEEDED, 0); |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, DEPTH, depth); |
| rx.hdcp.depth = depth; |
| } else { |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_CASCADE_EXCEEDED, 1); |
| rx.hdcp.depth = 0; |
| } |
| /*set repeat count*/ |
| if (len <= HDCP14_KSV_MAX_COUNT && !dev_exceed) { |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_DEVS_EXCEEDED, 0); |
| rx.hdcp.count = len; |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, DEVICE_COUNT, |
| rx.hdcp.count); |
| } else { |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, |
| MAX_DEVS_EXCEEDED, 1); |
| rx.hdcp.count = 0; |
| } |
| /*set repeat status*/ |
| /* if (rx.hdcp.count > 0) { |
| * rx.hdcp.repeat = true; |
| * hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, REPEATER, 1); |
| *} else { |
| * rx.hdcp.repeat = false; |
| * hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, REPEATER, 0); |
| *} |
| */ |
| ksvlist_ready = ((rx.hdcp.count > 0) && (rx.hdcp.depth > 0)); |
| rx_pr("[RX]write ksv list count:%d\n", rx.hdcp.count); |
| /*write ksv list to fifo*/ |
| for (i = 0; i < rx.hdcp.count; i++) { |
| if (rx_poll_dwc(DWC_HDCP_RPT_CTRL, ~KSV_HOLD, KSV_HOLD, |
| KSV_LIST_WR_TH)) { |
| /*check fifo can be written*/ |
| hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFOCTRL, i); |
| hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFO1, |
| *(data + i * MAX_KSV_SIZE + 4)); |
| hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFO0, |
| *((u32 *)(data + i * MAX_KSV_SIZE))); |
| if (log_level & VIDEO_LOG) |
| rx_pr("[RX]write ksv list index:%d, ksv hi:%#x, low:%#x\n", |
| i, *(data + i * MAX_KSV_SIZE + 4), |
| *((u32 *)(data + i * MAX_KSV_SIZE))); |
| } else { |
| return false; |
| } |
| } |
| /* Wait for ksv_hold=0*/ |
| rx_poll_dwc(DWC_HDCP_RPT_CTRL, ~KSV_HOLD, KSV_HOLD, KSV_LIST_WR_TH); |
| /*set ksv list ready*/ |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, KSVLIST_READY, ksvlist_ready); |
| /* Wait for HW completion of V value*/ |
| if (ksvlist_ready) |
| rx_poll_dwc(DWC_HDCP_RPT_CTRL, FIFO_READY, |
| FIFO_READY, KSV_V_WR_TH); |
| rx_pr("[RX]write Ready signal! ready:%u\n", |
| (unsigned int)ksvlist_ready); |
| |
| return true; |
| } |
| |
| void rx_set_repeat_signal(bool repeat) |
| { |
| rx.hdcp.repeat = repeat; |
| hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, REPEATER, repeat); |
| } |
| |
| bool rx_set_receive_hdcp(unsigned char *data, int len, int depth, |
| bool cas_exceed, bool devs_exceed) |
| { |
| return true; |
| } |
| EXPORT_SYMBOL(rx_set_receive_hdcp); |
| |
| void rx_repeat_hpd_state(bool plug) |
| { |
| } |
| EXPORT_SYMBOL(rx_repeat_hpd_state); |
| |