From patchwork Mon May 4 17:33:02 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kamil Debski X-Patchwork-Id: 6328731 Return-Path: X-Original-To: patchwork-linux-samsung-soc@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id DBE5DBEEE5 for ; Mon, 4 May 2015 17:35:05 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id BA01B2008F for ; Mon, 4 May 2015 17:35:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 63208200C6 for ; Mon, 4 May 2015 17:35:03 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751384AbbEDRek (ORCPT ); Mon, 4 May 2015 13:34:40 -0400 Received: from mailout3.samsung.com ([203.254.224.33]:23751 "EHLO mailout3.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751617AbbEDRei (ORCPT ); Mon, 4 May 2015 13:34:38 -0400 Received: from epcpsbgm2.samsung.com (epcpsbgm2 [203.254.230.27]) by mailout3.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTP id <0NNU005AQ65OKB50@mailout3.samsung.com>; Tue, 05 May 2015 02:34:36 +0900 (KST) X-AuditID: cbfee61b-f79536d000000f1f-cc-5547adacd7c6 Received: from epmmp1.local.host ( [203.254.227.16]) by epcpsbgm2.samsung.com (EPCPMTA) with SMTP id 44.5F.03871.CADA7455; Tue, 5 May 2015 02:34:36 +0900 (KST) Received: from AMDC1342.digital.local ([106.116.147.39]) by mmp1.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTPA id <0NNU0018W6370NB0@mmp1.samsung.com>; Tue, 05 May 2015 02:34:36 +0900 (KST) From: Kamil Debski To: dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org Cc: m.szyprowski@samsung.com, k.debski@samsung.com, mchehab@osg.samsung.com, hverkuil@xs4all.nl, kyungmin.park@samsung.com, thomas@tommie-lie.de, sean@mess.org, dmitry.torokhov@gmail.com, linux-input@vger.kernel.org, linux-samsung-soc@vger.kernel.org, lars@opdenkamp.eu, Hans Verkuil Subject: [PATCH v6 09/11] cec: adv7604: add cec support. Date: Mon, 04 May 2015 19:33:02 +0200 Message-id: <1430760785-1169-10-git-send-email-k.debski@samsung.com> X-Mailer: git-send-email 1.7.9.5 In-reply-to: <1430760785-1169-1-git-send-email-k.debski@samsung.com> References: <1430760785-1169-1-git-send-email-k.debski@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFjrNLMWRmVeSWpSXmKPExsVy+t9jAd01a91DDX694bI4vOgFo8WVr+/Z LD5MnMlocWryMyaLH68vsFmcbXrDbrF+6TVWi5ufvrFa9GzYymox4/w+Jou1R+6yW6x+VmGx 8Ol3FouFy9rZHPg8pvzeyOqxc9Zddo/73ceZPJa8sfbY8O4dq8eWfqBQ35ZVjB6PLs1h9vi8 Sc7j1NfP7AFcUVw2Kak5mWWpRfp2CVwZ20/OYC+Y6Vhx8mZeA+MP4y5GTg4JAROJlxMmMELY YhIX7q1n62Lk4hASWMQocWPveRYI5xejxMYnq5i6GDk42AQ0JVbd8wBpEBFwkJi28QcTSA2z wA4mida9l5lBEsICFhIrJ11gBbFZBFQlTk1+zg7SyyvgIrFttReIKSGgIDFnkg1IBSdQdNWf dhYQW0jAWeLLlv/MExh5FzAyrGIUTS1ILihOSs810itOzC0uzUvXS87P3cQIDuJn0jsYVzVY HGIU4GBU4uE9scotVIg1say4MvcQowQHs5II7/Ml7qFCvCmJlVWpRfnxRaU5qcWHGKU5WJTE eefoyoUKCaQnlqRmp6YWpBbBZJk4OKUaGEOVPqw+7T7nj5LrTc8GC2ceKSZOtqOH/OtucxaI TT9kp9EhrP2w7Y5SUKrFZD5jn/7E2ESeTfM+mH7dOl01+P2t2MCvx+w0p/hP/Lt907fnzVuE hSdWR0aqr21Zt/FowKXO1wmTf3OfWSAX/yL46ME/mpMci4vZDHjrGtdZuBf+7VM4HpEjpMRS nJFoqMVcVJwIADQDeBNeAgAA Sender: linux-samsung-soc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-samsung-soc@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Hans Verkuil Add CEC support to the adv7604 driver. Signed-off-by: Hans Verkuil [k.debski@samsung.com: Merged changes from CEC Updates commit by Hans Verkuil] [k.debski@samsung.com: add missing methods cec/io_write_and_or] [k.debski@samsung.com: change adv7604 to adv76xx in added functions] Signed-off-by: Kamil Debski --- drivers/media/i2c/adv7604.c | 207 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 60ffcf0..4921276 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,8 @@ MODULE_LICENSE("GPL"); #define ADV76XX_OP_SWAP_CB_CR (1 << 0) +#define ADV76XX_MAX_ADDRS (3) + enum adv76xx_type { ADV7604, ADV7611, @@ -159,6 +162,10 @@ struct adv76xx_state { u16 spa_port_a[2]; struct v4l2_fract aspect_ratio; u32 rgb_quantization_range; + u8 cec_addr[ADV76XX_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct workqueue_struct *work_queues; struct delayed_work delayed_work_enable_hotplug; bool restart_stdi_once; @@ -424,7 +431,15 @@ static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state, ADV76XX_PAGE_IO, reg, val); } -static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val) +static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return io_write(sd, reg, (io_read(sd, reg) & mask) | val); +} + + +static inline int io_write_clr_set(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) { return io_write(sd, reg, (io_read(sd, reg) & ~mask) | val); } @@ -457,6 +472,12 @@ static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) return adv_smbus_write_byte_data(state, ADV76XX_PAGE_CEC, reg, val); } +static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, + u8 val) +{ + return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val); +} + static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg) { struct adv76xx_state *state = to_state(sd); @@ -1865,6 +1886,183 @@ static int adv76xx_set_format(struct v4l2_subdev *sd, return 0; } +static void adv76xx_cec_tx_raw_status(struct v4l2_subdev *sd, u8 tx_raw_status) +{ + if ((cec_read(sd, 0x11) & 0x01) == 0) { + v4l2_dbg(1, debug, sd, "%s: tx raw: tx disabled\n", __func__); + return; + } + + if (tx_raw_status & 0x02) { + v4l2_dbg(1, debug, sd, "%s: tx raw: arbitration lost\n", + __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & 0x04) { + v4l2_dbg(1, debug, sd, "%s: tx raw: retry failed\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_RETRY_TIMEOUT); + return; + } + if (tx_raw_status & 0x01) { + v4l2_dbg(1, debug, sd, "%s: tx raw: ready ok\n", __func__); + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_TX_DONE, + (void *)CEC_TX_STATUS_OK); + return; + } +} + +static void adv76xx_cec_isr(struct v4l2_subdev *sd, bool *handled) +{ + struct cec_msg msg; + u8 cec_irq; + + /* cec controller */ + cec_irq = io_read(sd, 0x4d) & 0x0f; + if (!cec_irq) + return; + + v4l2_dbg(1, debug, sd, "%s: cec: irq 0x%x\n", __func__, cec_irq); + adv76xx_cec_tx_raw_status(sd, cec_irq); + if (cec_irq & 0x08) { + msg.len = cec_read(sd, 0x25) & 0x1f; + if (msg.len > 16) + msg.len = 16; + + if (msg.len) { + u8 i; + + for (i = 0; i < msg.len; i++) + msg.msg[i] = cec_read(sd, i + 0x15); + cec_write(sd, 0x26, 0x01); /* re-enable rx */ + v4l2_subdev_notify(sd, V4L2_SUBDEV_CEC_RX_MSG, &msg); + } + } + + /* note: the bit order is swapped between 0x4d and 0x4e */ + cec_irq = ((cec_irq & 0x08) >> 3) | ((cec_irq & 0x04) >> 1) | + ((cec_irq & 0x02) << 1) | ((cec_irq & 0x01) << 3); + io_write(sd, 0x4e, cec_irq); + + if (handled) + *handled = true; +} + +static int adv76xx_cec_enable(struct v4l2_subdev *sd, bool enable) +{ + struct adv76xx_state *state = to_state(sd); + + if (!state->cec_enabled_adap && enable) { + cec_write_and_or(sd, 0x2a, 0xfe, 0x01); /* power up cec */ + cec_write(sd, 0x2c, 0x01); /* cec soft reset */ + cec_write_and_or(sd, 0x11, 0xfe, 0); /* initially disable tx */ + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready */ + io_write_and_or(sd, 0x50, 0xf0, 0x0f); + cec_write(sd, 0x26, 0x01); /* enable rx */ + } else if (state->cec_enabled_adap && !enable) { + /* disable cec interrupts */ + io_write_and_or(sd, 0x50, 0xf0, 0x00); + /* disable address mask 1-3 */ + cec_write_and_or(sd, 0x27, 0x8f, 0x70); + /* power down cec section */ + cec_write_and_or(sd, 0x2a, 0xfe, 0x00); + state->cec_valid_addrs = 0; + } + state->cec_enabled_adap = enable; + return 0; +} + +static int adv76xx_cec_log_addr(struct v4l2_subdev *sd, u8 addr) +{ + struct adv76xx_state *state = to_state(sd); + unsigned i, free_idx = ADV76XX_MAX_ADDRS; + + if (!state->cec_enabled_adap) + return -EIO; + + for (i = 0; i < ADV76XX_MAX_ADDRS; i++) { + bool is_valid = state->cec_valid_addrs & (1 << i); + + if (free_idx == ADV76XX_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && state->cec_addr[i] == addr) + return 0; + } + if (i == ADV76XX_MAX_ADDRS) { + i = free_idx; + if (i == ADV76XX_MAX_ADDRS) + return -ENXIO; + } + state->cec_addr[i] = addr; + state->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + cec_write_and_or(sd, 0x27, 0xef, 0x10); + /* set address for mask 0 */ + cec_write_and_or(sd, 0x28, 0xf0, addr); + break; + case 1: + /* enable address mask 1 */ + cec_write_and_or(sd, 0x27, 0xdf, 0x20); + /* set address for mask 1 */ + cec_write_and_or(sd, 0x28, 0x0f, addr << 4); + break; + case 2: + /* enable address mask 2 */ + cec_write_and_or(sd, 0x27, 0xbf, 0x40); + /* set address for mask 1 */ + cec_write_and_or(sd, 0x29, 0xf0, addr); + break; + } + return 0; +} + +static int adv76xx_cec_transmit(struct v4l2_subdev *sd, struct cec_msg *msg) +{ + u8 len = msg->len; + unsigned i; + + if (len == 1) + /* allow for one retry for polling */ + cec_write_and_or(sd, 0x12, 0xf8, 1); + else + /* allow for three retries */ + cec_write_and_or(sd, 0x12, 0xf8, 3); + + if (len > 16) { + v4l2_err(sd, "%s: len exceeded 16 (%d)\n", __func__, len); + return -EINVAL; + } + + /* write data */ + for (i = 0; i < len; i++) + cec_write(sd, i, msg->msg[i]); + + /* set length (data + header) */ + cec_write(sd, 0x10, len); + /* start transmit, enable tx */ + cec_write(sd, 0x11, 0x01); + /* For some reason sometimes the + * transmit won't start. + * Doing it twice seems to help ? + */ + cec_write(sd, 0x11, 0x01); + return 0; +} + +static void adv76xx_cec_transmit_timed_out(struct v4l2_subdev *sd) +{ + cec_write_and_or(sd, 0x11, 0xfe, 0); /* disable tx */ +} + static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) { struct adv76xx_state *state = to_state(sd); @@ -1910,6 +2108,9 @@ static int adv76xx_isr(struct v4l2_subdev *sd, u32 status, bool *handled) *handled = true; } + /* cec */ + adv76xx_cec_isr(sd, handled); + /* tx 5v detect */ tx_5v = io_read(sd, 0x70) & info->cable_det_mask; if (tx_5v) { @@ -2304,6 +2505,10 @@ static const struct v4l2_subdev_video_ops adv76xx_video_ops = { .s_dv_timings = adv76xx_s_dv_timings, .g_dv_timings = adv76xx_g_dv_timings, .query_dv_timings = adv76xx_query_dv_timings, + .cec_enable = adv76xx_cec_enable, + .cec_log_addr = adv76xx_cec_log_addr, + .cec_transmit = adv76xx_cec_transmit, + .cec_transmit_timed_out = adv76xx_cec_transmit_timed_out, }; static const struct v4l2_subdev_pad_ops adv76xx_pad_ops = {