diff options
Diffstat (limited to 'drivers/video/tegra/dc/nvhdcp.c')
-rw-r--r-- | drivers/video/tegra/dc/nvhdcp.c | 206 |
1 files changed, 124 insertions, 82 deletions
diff --git a/drivers/video/tegra/dc/nvhdcp.c b/drivers/video/tegra/dc/nvhdcp.c index 2bec36a53219..7bbe6741be45 100644 --- a/drivers/video/tegra/dc/nvhdcp.c +++ b/drivers/video/tegra/dc/nvhdcp.c @@ -65,7 +65,7 @@ /* for nvhdcp.state */ -enum { +enum tegra_nvhdcp_state { STATE_OFF, STATE_UNAUTHENTICATED, STATE_LINK_VERIFY, @@ -80,9 +80,9 @@ struct tegra_nvhdcp { struct miscdevice miscdev; char name[12]; unsigned id; - atomic_t plugged; /* true if hotplug detected */ + bool plugged; /* true if hotplug detected */ atomic_t policy; /* set policy */ - atomic_t state; /* STATE_xxx */ + enum tegra_nvhdcp_state state; /* STATE_xxx */ struct i2c_client *client; struct i2c_board_info info; int bus; @@ -97,11 +97,20 @@ struct tegra_nvhdcp { u64 m_prime; u32 num_bksv_list; u64 bksv_list[TEGRA_NVHDCP_MAX_DEVS]; + int fail_count; }; -#define TEGRA_NVHDCP_NUM_AP 1 -static struct tegra_nvhdcp *nvhdcp_dev[TEGRA_NVHDCP_NUM_AP]; -static int nvhdcp_ap; +static inline bool nvhdcp_is_plugged(struct tegra_nvhdcp *nvhdcp) +{ + rmb(); + return nvhdcp->plugged; +} + +static inline bool nvhdcp_set_plugged(struct tegra_nvhdcp *nvhdcp, bool plugged) +{ + return nvhdcp->plugged = plugged; + wmb(); +} static int nvhdcp_i2c_read(struct tegra_nvhdcp *nvhdcp, u8 reg, size_t len, void *data) @@ -311,12 +320,13 @@ static inline int get_receiver_ri(struct tegra_nvhdcp *nvhdcp, u16 *r) static int get_bcaps(struct tegra_nvhdcp *nvhdcp, u8 *b_caps) { - int e, retries = 3; + int e, retries = 4; do { e = nvhdcp_i2c_read8(nvhdcp, 0x40, b_caps); if (!e) return 0; - msleep(100); + if (retries > 1) + msleep(100); } while (--retries); return -EIO; @@ -388,7 +398,7 @@ static void hdcp_ctrl_run(struct tegra_dc_hdmi_data *hdmi, bool v) * sleeps up to 120mS */ static int wait_hdcp_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask, u32 *v) { - int retries = 12; + int retries = 13; u32 ctrl; do { @@ -398,7 +408,8 @@ static int wait_hdcp_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask, u32 *v) *v = ctrl; break; } - msleep(10); + if (retries > 1) + msleep(10); } while (--retries); if (!retries) { nvhdcp_err("ctrl read timeout (mask=0x%x)\n", mask); @@ -411,14 +422,15 @@ static int wait_hdcp_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask, u32 *v) * waits up to 100mS */ static int wait_key_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask) { - int retries = 100; + int retries = 101; u32 ctrl; do { ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_CTRL); if ((ctrl | (mask))) break; - msleep(1); + if (retries > 1) + msleep(1); } while (--retries); if (!retries) { nvhdcp_err("key ctrl read timeout (mask=0x%x)\n", mask); @@ -450,20 +462,22 @@ static int get_s_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet * int e; /* if connection isn't authenticated ... */ - if (atomic_read(&nvhdcp->state) != STATE_LINK_VERIFY) { + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state != STATE_LINK_VERIFY) { memset(pkt, 0, sizeof *pkt); pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED; - return 0; + e = 0; + goto err; } - pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL; /* we will be taking c_n, c_ksv as input */ if (!(pkt->value_flags & TEGRA_NVHDCP_FLAG_CN) || !(pkt->value_flags & TEGRA_NVHDCP_FLAG_CKSV)) { nvhdcp_err("missing value_flags (0x%x)\n", pkt->value_flags); - return -EINVAL; + e = -EINVAL; + goto err; } pkt->value_flags = 0; @@ -485,7 +499,8 @@ static int get_s_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet * if (e) { nvhdcp_err("Sprime read timeout\n"); pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL; - return -EIO; + e = -EIO; + goto err; } msleep(50); @@ -514,7 +529,7 @@ static int get_s_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet * if (verify_ksv(pkt->d_ksv)) { nvhdcp_err("Dksv invalid!\n"); pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL; - return -EIO; /* treat bad Dksv as I/O error */ + e = -EIO; /* treat bad Dksv as I/O error */ } pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV; @@ -523,7 +538,12 @@ static int get_s_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet * pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSV; pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS; + mutex_unlock(&nvhdcp->lock); return 0; + +err: + mutex_unlock(&nvhdcp->lock); + return e; } /* get M prime - READ_M on TMDS0_LINK0 only */ @@ -535,10 +555,12 @@ static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_p pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL; /* if connection isn't authenticated ... */ - if (atomic_read(&nvhdcp->state) != STATE_LINK_VERIFY) { + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state != STATE_LINK_VERIFY) { memset(pkt, 0, sizeof *pkt); pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED; - return 0; + e = 0; + goto err; } pkt->a_ksv = nvhdcp->a_ksv; @@ -556,7 +578,8 @@ static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_p e = wait_hdcp_ctrl(hdmi, MPRIME_VALID, NULL); if (e) { nvhdcp_err("Mprime read timeout\n"); - return -EIO; + e = -EIO; + goto err; } msleep(50); @@ -585,7 +608,8 @@ static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_p pkt->d_ksv = get_dksv(hdmi); if (verify_ksv(pkt->d_ksv)) { nvhdcp_err("Dksv invalid!\n"); - return -EIO; /* treat bad Dksv as I/O error */ + e = -EIO; + goto err; } pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV; @@ -594,7 +618,12 @@ static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_p pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSV; pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS; + mutex_unlock(&nvhdcp->lock); return 0; + +err: + mutex_unlock(&nvhdcp->lock); + return e; } static int load_kfuse(struct tegra_dc_hdmi_data *hdmi) @@ -630,11 +659,12 @@ static int load_kfuse(struct tegra_dc_hdmi_data *hdmi) tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_KEY_SKEY_INDEX); /* wait for SRAM to be cleared */ - retries = 5; + retries = 6; do { tmp = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_DEBUG0); if ((tmp & 1) == 0) break; - mdelay(1); + if (retries > 1) + mdelay(1); } while (--retries); if (!retries) { nvhdcp_err("key SRAM clear timeout\n"); @@ -700,7 +730,7 @@ static int verify_link(struct tegra_nvhdcp *nvhdcp, bool wait_ri) nvhdcp_debug("R0 Ri poll:rx=0x%04x tx=0x%04x\n", rx, tx); - if (!atomic_read(&nvhdcp->plugged)) { + if (!nvhdcp_is_plugged(nvhdcp)) { nvhdcp_err("aborting verify links - lost hdmi connection\n"); return -EIO; } @@ -720,9 +750,9 @@ static int get_repeater_info(struct tegra_nvhdcp *nvhdcp) nvhdcp_vdbg("repeater found:fetching repeater info\n"); /* wait up to 5 seconds for READY on repeater */ - retries = 50; + retries = 51; do { - if (!atomic_read(&nvhdcp->plugged)) { + if (!nvhdcp_is_plugged(nvhdcp)) { nvhdcp_err("disconnect while waiting for repeater\n"); return -EIO; } @@ -732,7 +762,8 @@ static int get_repeater_info(struct tegra_nvhdcp *nvhdcp) nvhdcp_debug("Bcaps READY from repeater\n"); break; } - msleep(100); + if (retries > 1) + msleep(100); } while (--retries); if (!retries) { nvhdcp_err("repeater Bcaps read timeout\n"); @@ -786,30 +817,27 @@ static void nvhdcp_downstream_worker(struct work_struct *work) u8 b_caps; u32 tmp; u32 res; - int st; nvhdcp_vdbg("%s():started thread %s\n", __func__, nvhdcp->name); - st = atomic_read(&nvhdcp->state); - if (st == STATE_OFF) { + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state == STATE_OFF) { nvhdcp_err("nvhdcp failure - giving up\n"); - return; + goto err; } - - atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED); + nvhdcp->state = STATE_UNAUTHENTICATED; /* check plug state to terminate early in case flush_workqueue() */ - if (!atomic_read(&nvhdcp->plugged)) { + if (!nvhdcp_is_plugged(nvhdcp)) { nvhdcp_err("worker started while unplugged!\n"); goto lost_hdmi; } + nvhdcp_vdbg("%s():hpd=%d\n", __func__, nvhdcp->plugged); nvhdcp->a_ksv = 0; nvhdcp->b_ksv = 0; nvhdcp->a_n = 0; - nvhdcp_vdbg("%s():hpd=%d\n", __func__, atomic_read(&nvhdcp->plugged)); - e = get_bcaps(nvhdcp, &b_caps); if (e) { nvhdcp_err("Bcaps read failure\n"); @@ -881,7 +909,7 @@ static void nvhdcp_downstream_worker(struct work_struct *work) nvhdcp_vdbg("wrote Aksv = 0x%010llx\n", nvhdcp->a_ksv); /* bail out if unplugged in the middle of negotiation */ - if (!atomic_read(&nvhdcp->plugged)) + if (!nvhdcp_is_plugged(nvhdcp)) goto lost_hdmi; /* get Bksv from receiver */ @@ -937,42 +965,51 @@ static void nvhdcp_downstream_worker(struct work_struct *work) } } - atomic_set(&nvhdcp->state, STATE_LINK_VERIFY); + nvhdcp->state = STATE_LINK_VERIFY; nvhdcp_info("link verified!\n"); - while (atomic_read(&nvhdcp->state) == STATE_LINK_VERIFY) { - if (!atomic_read(&nvhdcp->plugged)) + while (1) { + if (nvhdcp->state != STATE_LINK_VERIFY) + goto failure; + + if (!nvhdcp_is_plugged(nvhdcp)) goto lost_hdmi; + e = verify_link(nvhdcp, true); if (e) { nvhdcp_err("link verification failed err %d\n", e); goto failure; } + mutex_unlock(&nvhdcp->lock); msleep(1500); + mutex_lock(&nvhdcp->lock); + } failure: - /* TODO: check policy to see if we should start again - * or wait for read_m/read_s to come in (on-demand) - */ - nvhdcp_err("nvhdcp failure - renegotiating in 1.75 seconds\n"); - atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED); - hdcp_ctrl_run(hdmi, 0); - msleep(1750); - queue_work(nvhdcp->downstream_wq, &nvhdcp->work); - return; + nvhdcp->fail_count++; + if(nvhdcp->fail_count > 5) { + nvhdcp_err("nvhdcp failure - too many failures, giving up!\n"); + } else { + nvhdcp_err("nvhdcp failure - renegotiating in 1.75 seconds\n"); + msleep(1750); + queue_work(nvhdcp->downstream_wq, &nvhdcp->work); + } + lost_hdmi: - nvhdcp_err("lost hdmi connection\n"); - atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED); + nvhdcp->state = STATE_UNAUTHENTICATED; hdcp_ctrl_run(hdmi, 0); + +err: + mutex_unlock(&nvhdcp->lock); return; } void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd) { - nvhdcp_info("hdmi hotplug detected (hpd = %d)\n", hpd); + nvhdcp_debug("hdmi hotplug detected (hpd = %d)\n", hpd); - atomic_set(&nvhdcp->plugged, hpd); + nvhdcp_set_plugged(nvhdcp, hpd); if (hpd) { queue_work(nvhdcp->downstream_wq, &nvhdcp->work); @@ -983,36 +1020,45 @@ void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd) static int tegra_nvhdcp_on(struct tegra_nvhdcp *nvhdcp) { - atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED); - if (atomic_read(&nvhdcp->plugged)) + nvhdcp->state = STATE_UNAUTHENTICATED; + if (nvhdcp_is_plugged(nvhdcp)) { + nvhdcp->fail_count = 0; queue_work(nvhdcp->downstream_wq, &nvhdcp->work); + } return 0; } static int tegra_nvhdcp_off(struct tegra_nvhdcp *nvhdcp) { - atomic_set(&nvhdcp->state, STATE_OFF); - atomic_set(&nvhdcp->plugged, 0); /* force early termination */ + mutex_lock(&nvhdcp->lock); + nvhdcp->state = STATE_OFF; + nvhdcp_set_plugged(nvhdcp, false); + mutex_unlock(&nvhdcp->lock); flush_workqueue(nvhdcp->downstream_wq); return 0; } int tegra_nvhdcp_set_policy(struct tegra_nvhdcp *nvhdcp, int pol) { - pol = !!pol; - nvhdcp_info("using \"%s\" policy.\n", - pol ? "always on" : "on demand"); - if (atomic_xchg(&nvhdcp->policy, pol) == TEGRA_NVHDCP_POLICY_ON_DEMAND - && pol == TEGRA_NVHDCP_POLICY_ALWAYS_ON) { - /* policy was off but now it is on, start working */ - tegra_nvhdcp_on(nvhdcp); + if (pol == TEGRA_NVHDCP_POLICY_ALWAYS_ON) { + nvhdcp_info("using \"always on\" policy.\n"); + if (atomic_xchg(&nvhdcp->policy, pol) != pol) { + /* policy changed, start working */ + tegra_nvhdcp_on(nvhdcp); + } + } else { + /* unsupported policy */ + return -EINVAL; } + return 0; } static int tegra_nvhdcp_renegotiate(struct tegra_nvhdcp *nvhdcp) { - atomic_set(&nvhdcp->state, STATE_RENEGOTIATE); + mutex_lock(&nvhdcp->lock); + nvhdcp->state = STATE_RENEGOTIATE; + mutex_unlock(&nvhdcp->lock); tegra_nvhdcp_on(nvhdcp); return 0; } @@ -1084,24 +1130,18 @@ kfree_pkt: return e; } -/* every open indexes a new AP link */ static int nvhdcp_dev_open(struct inode *inode, struct file *filp) { - if (nvhdcp_ap >= TEGRA_NVHDCP_NUM_AP) - return -EMFILE; - - if (!nvhdcp_dev[nvhdcp_ap]) - return -ENODEV; - - filp->private_data = nvhdcp_dev[nvhdcp_ap++]; - + struct miscdevice *miscdev = filp->private_data; + struct tegra_nvhdcp *nvhdcp = + container_of(miscdev, struct tegra_nvhdcp, miscdev); + filp->private_data = nvhdcp; return 0; } static int nvhdcp_dev_release(struct inode *inode, struct file *filp) { filp->private_data = NULL; - --nvhdcp_ap; return 0; } @@ -1113,13 +1153,17 @@ static const struct file_operations nvhdcp_fops = { .release = nvhdcp_dev_release, }; +/* we only support one AP right now, so should only call this once. */ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, int id, int bus) { - struct tegra_nvhdcp *nvhdcp; + static struct tegra_nvhdcp *nvhdcp; /* prevent multiple calls */ struct i2c_adapter *adapter; int e; + if (nvhdcp) + return ERR_PTR(-EMFILE); + nvhdcp = kzalloc(sizeof(*nvhdcp), GFP_KERNEL); if (!nvhdcp) return ERR_PTR(-ENOMEM); @@ -1133,6 +1177,7 @@ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, nvhdcp->bus = bus; nvhdcp->info.addr = 0x74 >> 1; nvhdcp->info.platform_data = nvhdcp; + nvhdcp->fail_count = 0; adapter = i2c_get_adapter(bus); if (!adapter) { @@ -1150,7 +1195,7 @@ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, goto free_nvhdcp; } - atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED); + nvhdcp->state = STATE_UNAUTHENTICATED; nvhdcp->downstream_wq = create_singlethread_workqueue(nvhdcp->name); INIT_WORK(&nvhdcp->work, nvhdcp_downstream_worker); @@ -1165,8 +1210,6 @@ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, nvhdcp_vdbg("%s(): created misc device %s\n", __func__, nvhdcp->name); - nvhdcp_dev[0] = nvhdcp; /* we only support on AP right now */ - return nvhdcp; free_workqueue: destroy_workqueue(nvhdcp->downstream_wq); @@ -1180,8 +1223,7 @@ free_nvhdcp: void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp) { misc_deregister(&nvhdcp->miscdev); - atomic_set(&nvhdcp->plugged, 0); /* force early termination */ - flush_workqueue(nvhdcp->downstream_wq); + tegra_nvhdcp_off(nvhdcp); destroy_workqueue(nvhdcp->downstream_wq); i2c_release_client(nvhdcp->client); kfree(nvhdcp); |