From patchwork Fri Oct 19 10:54:38 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Grzeschik X-Patchwork-Id: 10648993 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 467BD112B for ; Fri, 19 Oct 2018 10:55:13 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2F58A28A3F for ; Fri, 19 Oct 2018 10:55:13 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2335E28A8B; Fri, 19 Oct 2018 10:55:13 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EC91828A3F for ; Fri, 19 Oct 2018 10:55:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727460AbeJSTAb (ORCPT ); Fri, 19 Oct 2018 15:00:31 -0400 Received: from metis.ext.pengutronix.de ([85.220.165.71]:44949 "EHLO metis.ext.pengutronix.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727413AbeJSTAb (ORCPT ); Fri, 19 Oct 2018 15:00:31 -0400 Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.89) (envelope-from ) id 1gDSQj-0007Sg-50; Fri, 19 Oct 2018 12:54:57 +0200 Received: from mgr by dude.hi.pengutronix.de with local (Exim 4.91) (envelope-from ) id 1gDSQg-0000Ax-G6; Fri, 19 Oct 2018 12:54:54 +0200 From: Michael Grzeschik To: linux-media@vger.kernel.org Cc: linux-kernel@vger.kernel.org, stoth@kernellabs.com, mchehab@kernel.org, davem@davemloft.net, laurent.pinchart@ideasonboard.com, kernel@pengutronix.de Subject: [PATCH 1/2] media: mst3367: add support for mstar mst3367 HDMI RX Date: Fri, 19 Oct 2018 12:54:38 +0200 Message-Id: <20181019105439.27796-2-m.grzeschik@pengutronix.de> X-Mailer: git-send-email 2.19.0 In-Reply-To: <20181019105439.27796-1-m.grzeschik@pengutronix.de> References: <20181019105439.27796-1-m.grzeschik@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: mgr@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-media@vger.kernel.org Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Steven Toth This patch is based on the work of Steven Toth. He reverse engineered the driver by tracing the windows driver. https://github.com/stoth68000/hdcapm/ Signed-off-by: Steven Toth Signed-off-by: Michael Grzeschik --- MAINTAINERS | 6 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/mst3367.c | 1104 +++++++++++++++++++++++++++++++++++ include/media/i2c/mst3367.h | 29 + 5 files changed, 1150 insertions(+) create mode 100644 drivers/media/i2c/mst3367.c create mode 100644 include/media/i2c/mst3367.h diff --git a/MAINTAINERS b/MAINTAINERS index 556f902b3766..9c69b7f9b2f9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9787,6 +9787,12 @@ L: linux-mtd@lists.infradead.org S: Maintained F: drivers/mtd/devices/docg3* +MT9M032 APTINA SENSOR DRIVER +M: Michael Grzeschik +S: Maintained +F: drivers/media/i2c/mst3367.c +F: include/media/i2c/mst3367.h + MT9M032 APTINA SENSOR DRIVER M: Laurent Pinchart L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 82af97430e5b..3de53a09d88a 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -94,6 +94,16 @@ config VIDEO_MSP3400 To compile this driver as a module, choose M here: the module will be called msp3400. +config VIDEO_MST3367 + tristate "Mstar MST3367 video decoders" + depends on VIDEO_V4L2 && I2C + help + Support for the MStar MST3367 HDMI RX / SOC. It is found on + the usb2hdcapm hdmi framegrabber from startech. + + To compile this driver as a module, choose M here: the + module will be called mst3367. + config VIDEO_CS3308 tristate "Cirrus Logic CS3308 audio ADC" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index a94eb03d10d4..f3e7a35018f8 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 msp3400-objs := msp3400-driver.o msp3400-kthreads.o obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o +obj-$(CONFIG_VIDEO_MST3367) += mst3367.o obj-$(CONFIG_VIDEO_SMIAPP) += smiapp/ obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ diff --git a/drivers/media/i2c/mst3367.c b/drivers/media/i2c/mst3367.c new file mode 100644 index 000000000000..7e2f529d96b3 --- /dev/null +++ b/drivers/media/i2c/mst3367.c @@ -0,0 +1,1104 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the MSTAR 3367 HDMI Receiver + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int debug; +module_param_named(debug, debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level [def: 0]"); + +MODULE_DESCRIPTION("Driver for MST3367 HDMI receiver"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); + +#define BANK0 0x00 +#define BANK1 0x01 +#define BANK2 0x02 +#define BANK3 0x03 +#define DUMP_SHADOWS 0 +#define DUMP_REGISTERS 0 + +/* + * This is how the register map was modified by the windows + * driver during the i2c-trace-driver-init-with-1080-signal.csv + * trace. + * + * BANK0 0 1 2 3 4 5 6 7 8 9 A B C D E F + * ----------------------------------------------- + * 00 : 00 + * 10 : 11 01 + * 20 : + * 30 : + * 40 : 6F + * 50 : 89 20 + * 60 : + * 70 : 90 + * 80 : + * 90 : 15 15 62 10 00 00 00 00 00 00 00 10 00 00 00 00 + * A0 : 00 00 00 10 00 20 00 00 01 20 01 15 95 05 04 + * B0 : 20 E0 08 00 54 0C 00 00 + * C0 : + * D0 : + * E0 : 00 + * F0 : + * + * BANK1 0 1 2 3 4 5 6 7 8 9 A B C D E F + * ----------------------------------------------- + * 00 : 01 02 + * 10 : 30 00 00 00 50 + * 20 : 40 07 + * 30 : 80 00 00 + * 40 : + * 50 : + * 60 : + * 70 : + * 80 : + * 90 : + * A0 : + * B0 : + * C0 : + * D0 : + * E0 : + * F0 : + * + * BANK2 0 1 2 3 4 5 6 7 8 9 A B C D E F + * ----------------------------------------------- + * 00 : 02 61 F5 02 01 00 08 04 03 28 + * 10 : C0 FF FF FC 1A 00 00 00 + * 20 : 00 00 26 A2 00 A1 + * 30 : + * 40 : + * 50 : + * 60 : + * 70 : + * 80 : + * 90 : + * A0 : + * B0 : + * C0 : + * D0 : + * E0 : + * F0 : + * + */ + +struct mst3367_video_standards_s { + struct v4l2_dv_timings timings; + u32 htotal_min; + u32 htotal_max; + u32 vtotal_min; + u32 vtotal_max; + u32 hperiod_min; + u32 hperiod_max; + u32 vperiod_min; + u32 vperiod_max; + u32 interleaved; + u32 encoded_fps; + u32 hdmi_fpsX100; +}; + +struct mst3367_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + + /* Is the mst3367 powered on? */ + bool power_on; + bool haveSource; + + /* controls */ + struct v4l2_ctrl *hotplug_ctrl; + struct v4l2_ctrl *rx_sense_ctrl; + + /* i2c */ + struct i2c_adapter *i2c; + u8 i2c_addr; + u8 current_bank; + + /* Detection */ + const struct mst3367_video_standards_s *detected_standard; + int detected_signal; + struct { + u32 htotal; + u32 vtotal; + u32 hperiod; + u32 vperiod; + u32 detectdelay; + u32 hactive; + u32 interleaved; + } current_timings; + + /* Shadow regs for monitoring writes. */ + u8 regs[4][256]; + u8 regs_updated[4][256]; + + u8 regb1r01_cached; + u8 regb2r48_cached; +}; + +static const struct mst3367_video_standards_s mst3367_video_standards[] = { + {V4L2_DV_BT_CEA_720X480P59_94, 845, 865, 520, 525, 310, 320, 595, 605, + 0, 60, 5994,}, + + // 720p30 - frontend doesn't reliably lock. + {V4L2_DV_BT_CEA_1280X720P30, 2300, 2500, 745, 755, 215, 235, 290, 310, + 0, 30, 3000,}, + {V4L2_DV_BT_CEA_1280X720P50, 2965, 2985, 745, 755, 360, 380, 480, 520, + 0, 50, 5000,}, + {V4L2_DV_BT_CEA_1280X720P60, 2470, 2480, 745, 755, 445, 455, 595, 605, + 0, 60, 6000,}, + + // Tivo + {V4L2_DV_BT_CEA_1280X720P60, 1645, 1655, 745, 755, 445, 455, 595, 605, + 0, 60, 6000,}, + + {V4L2_DV_BT_CEA_1920X1080P24, 4080, 4105, 1120, 1130, 260, 280, 230, + 250, 0, 24, 2400,}, + {V4L2_DV_BT_CEA_1920X1080P25, 3950, 3970, 1120, 1130, 270, 290, 240, + 254, 0, 25, 2500,}, + {V4L2_DV_BT_CEA_1920X1080P30, 2295, 3305, 1120, 1130, 330, 345, 290, + 310, 0, 30, 3000,}, + {V4L2_DV_BT_CEA_1920X1080P50, 3950, 3970, 1120, 1130, 550, 570, 480, + 520, 0, 25, 5000,}, + {V4L2_DV_BT_CEA_1920X1080P60, 3290, 3310, 1120, 1130, 665, 685, 595, + 605, 0, 30, 6000,}, +}; + +static const struct mst3367_video_standards_s *find_video_standard(u32 htotal, + u32 vtotal, + u32 hperiod, + u32 vperiod, + u32 + interleaved) +{ + const struct mst3367_video_standards_s *e, *r = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(mst3367_video_standards); i++) { + e = &mst3367_video_standards[i]; + + if (htotal < e->htotal_min || htotal > e->htotal_max) + continue; + if (vtotal < e->vtotal_min || vtotal > e->vtotal_max) + continue; + if (hperiod < e->hperiod_min || hperiod > e->hperiod_max) + continue; + if (vperiod < e->vperiod_min || vperiod > e->vperiod_max) + continue; + if (interleaved != e->interleaved) + continue; + + r = e; + break; + } + + return r; +} + +static inline struct mst3367_state *get_mst3367_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mst3367_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct mst3367_state, hdl)->sd; +} + +static void mst3367_notify_source_detect(struct v4l2_subdev *sd, int haveSource) +{ + struct v4l2_event ev; + struct mst3367_source_detect msd; + + msd.present = haveSource; + + /* sub-device events get pushed to the bridge via hdcapm_notify(). + * The bridge then forwards those events on to the v4l2_device, + * and eventually they end up in userspace. + */ + v4l2_subdev_notify(sd, MST3367_SOURCE_DETECT, (void *)&msd); + + memset(&ev, 0, sizeof(ev)); + ev.type = V4L2_EVENT_SOURCE_CHANGE; + ev.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION; + /* Input 0 - This event requires that the id matches the input index + * (when used with a video device node) + */ + ev.id = 0; + v4l2_subdev_notify_event(sd, &ev); +} + +/* The MST 3367 has multiple I2C register maps, banks 0-3, if the + * current bank doesn't match the requested bank, switch banks. + */ +static void mst3367_switch_bank(struct v4l2_subdev *sd, u8 bank) +{ + struct mst3367_state *state = get_mst3367_state(sd); + u8 buf[] = { 0x00, bank }; + + struct i2c_msg msg = {.addr = state->i2c_addr >> 1, + .flags = 0, .buf = buf, .len = 2 + }; + + if (state->current_bank != bank) { + state->current_bank = bank; + if (i2c_transfer(state->i2c, &msg, 1) != 1) + v4l2_err(sd, "%s: switch bank error\n", __func__); + } +} + +static u8 mst3367_rd(struct v4l2_subdev *sd, u8 bank, u8 reg) +{ + struct mst3367_state *state = get_mst3367_state(sd); + u8 b0 = reg; + u8 b1 = 0; + + struct i2c_msg msg[] = { + {.addr = state->i2c_addr >> 1, + .flags = 0, .buf = &b0, .len = 1}, + {.addr = state->i2c_addr >> 1, + .flags = I2C_M_RD, .buf = &b1, .len = 1} + }; + + mst3367_switch_bank(sd, bank); + + if (i2c_transfer(state->i2c, msg, 2) != 2) + v4l2_err(sd, "%s: readreg error\n", __func__); + + v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x) = 0x%02x\n", + __func__, bank, reg, b1); + + return b1; +} + +static void mst3367_wr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 val) +{ + struct mst3367_state *state = get_mst3367_state(sd); + u8 buf[] = { reg, val }; + + struct i2c_msg msg = {.addr = state->i2c_addr >> 1, .flags = 0, + .buf = buf, .len = 2 + }; + + v4l2_dbg(2, debug, sd, "%s(bank=%d, reg=0x%02x, value=0x%02x)\n", + __func__, bank, reg, val); + mst3367_switch_bank(sd, bank); + + state->regs[state->current_bank][reg] = val; + state->regs_updated[state->current_bank][reg] = 1; + + if (i2c_transfer(state->i2c, &msg, 1) != 1) + v4l2_err(sd, "%s: writereg error\n", __func__); +} + +static inline void mst3367_set(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask) +{ + u8 val = mst3367_rd(sd, bank, reg); + + val |= mask; + mst3367_wr(sd, bank, reg, val); +} + +static inline void mst3367_clr(struct v4l2_subdev *sd, u8 bank, u8 reg, u8 mask) +{ + u8 val = mst3367_rd(sd, bank, reg); + + val &= ~mask; + mst3367_wr(sd, bank, reg, val); +} + +enum hpt_e { + RX_TMDS_HPD_OFF = 0x00, + RX_TMDS_A_HPD_ON = 0x01, + RX_TMDS_A_LINK_ON = 0x02, + RX_TMDS_B_HPD_ON = 0x10, + RX_TMDS_B_LINK_ON = 0x20, +}; + +static inline void MST3367_TMDS_HOT_PLUG(struct v4l2_subdev *sd, enum hpt_e e) +{ + u8 v = mst3367_rd(sd, BANK0, 0xB7); + + v |= 0x02; + + if (e & RX_TMDS_A_LINK_ON) + v &= ~0x02; + if (e & RX_TMDS_B_LINK_ON) + v &= ~0x02; + + mst3367_wr(sd, BANK0, 0xB7, v); + msleep(20); +} + +static inline void MST3367_HDMI_INIT(struct v4l2_subdev *sd) +{ + /* RxHdmiInit */ + mst3367_clr(sd, BANK2, 0x01, 0xf0); + mst3367_set(sd, BANK2, 0x01, 0x40 | 0x20); + mst3367_set(sd, BANK2, 0x04, 0x01); + mst3367_wr(sd, BANK2, 0x06, 0x08); + mst3367_set(sd, BANK2, 0x09, 0x20); + mst3367_clr(sd, BANK0, 0x54, 0x10); + mst3367_set(sd, BANK0, 0xac, 0x80); + + mst3367_set(sd, BANK0, 0x00, 0x80); + mst3367_set(sd, BANK0, 0xce, 0x80); + mst3367_clr(sd, BANK0, 0xcf, 0x07); + mst3367_set(sd, BANK0, 0xcf, 0x02); + mst3367_clr(sd, BANK0, 0x00, 0x80); +} + +static inline void MST3367_HDCP_RESET(struct v4l2_subdev *sd) +{ + mst3367_wr(sd, BANK0, 0xb8, 0x10); /* HDCP RESET */ + mst3367_wr(sd, BANK0, 0xb8, 0x00); + msleep(20); +} + +static inline void MST3367_HDMI_RESET(struct v4l2_subdev *sd) +{ + mst3367_wr(sd, BANK2, 0x07, 0xf4); + mst3367_wr(sd, BANK2, 0x07, 0x04); + msleep(20); +} + +#if DUMP_SHADOWS +static void dump_shadows(struct v4l2_subdev *sd, int bank) +{ + struct mst3367_state *state = get_mst3367_state(sd); + int i, j; + u8 line[80]; + + v4l2_info(sd, "B%d 0 1 2 3 4 5 6 7 8 9 A B C D E F\n", + bank); + v4l2_info(sd, " -----------------------------------------------\n"); + for (i = 0; i < 256; i += 16) { + sprintf(line, "%02X : ", i); + for (j = 0; j < 16; j++) { + if (state->regs_updated[bank][i + j]) + sprintf(line + strlen(line), "%02X ", + state->regs[bank][i + j]); + else + sprintf(line + strlen(line), " "); + } + sprintf(line + strlen(line), "\n"); + v4l2_info(sd, line); + } +} +#endif + +#if DUMP_REGISTERS +static void dump_registers(struct v4l2_subdev *sd, int bank) +{ + int i, j; + u8 line[80]; + u8 vals[256]; + + for (i = 0; i < sizeof(vals); i++) + vals[i] = mst3367_rd(sd, bank, i); + + v4l2_info(sd, "B%d 0 1 2 3 4 5 6 7 8 9 A B C D E F\n", + bank); + v4l2_info(sd, " -----------------------------------------------\n"); + for (i = 0; i < 256; i += 16) { + sprintf(line, "%02X : ", i); + for (j = 0; j < 16; j++) + sprintf(line + strlen(line), "%02X ", vals[i + j]); + sprintf(line + strlen(line), "\n"); + v4l2_info(sd, line); + } +} +#endif + +static int MST3367_HDMI_MODE_DETECT(struct v4l2_subdev *sd, int *locked) +{ + struct mst3367_state *state = get_mst3367_state(sd); + int ret = -ENOLINK; + u8 r[0xff]; + u16 t; + + *locked = 0; + + /* Do we have a signal detect / lock? */ + if (mst3367_rd(sd, BANK0, 0x55) & 0x3c) { + /* We have a signal, extract timing data. */ + state->current_timings.htotal = (mst3367_rd(sd, + BANK0, 0x6a) << 8 + | mst3367_rd(sd, BANK0, 0x6b)) + & 0xfff; + state->current_timings.vtotal = (mst3367_rd(sd, + BANK0, 0x5b) << 8 + | mst3367_rd(sd, BANK0, 0x5c)) + & 0x7ff; + state->current_timings.hactive = + (mst3367_rd(sd, BANK2, 0x29) << 8 | + mst3367_rd(sd, BANK2, 0x28)) + & 0x1fff; + + r[0x57] = mst3367_rd(sd, BANK0, 0x57) & 0x3f; + r[0x58] = mst3367_rd(sd, BANK0, 0x58); + r[0x59] = mst3367_rd(sd, BANK0, 0x59) & 0x3f; + r[0x5a] = mst3367_rd(sd, BANK0, 0x5a); + r[0x5f] = mst3367_rd(sd, BANK0, 0x5f) & 0x02; + + t = ((r[0x57] << 8) | r[0x58]); + if (t > 0) + state->current_timings.hperiod = + ((1600000) / ((r[0x57] << 8) | (r[0x58] << 0))); + + t = ((r[0x59] << 8) | r[0x5a]); + if (t > 0) + state->current_timings.vperiod = + ((1250000) / ((r[0x59] << 8) | (r[0x5a] << 0))); + + state->current_timings.interleaved = r[0x5f] >> 1; + + state->current_timings.detectdelay = ((r[0x59] << 8) | r[0x5a]); + state->current_timings.detectdelay = + ((state->current_timings.detectdelay + 63) * 2) / 125; + + v4l2_dbg(2, debug, sd, + "%s() htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n", + __func__, + state->current_timings.htotal, + state->current_timings.vtotal, + state->current_timings.hperiod, + state->current_timings.vperiod, + state->current_timings.detectdelay, + state->current_timings.hactive, + state->current_timings.interleaved); + + /* Looking the signal format. If its somet hing we + * support then return lock, else no lock. + */ + state->detected_standard = + find_video_standard(state->current_timings.htotal, + state->current_timings.vtotal, + state->current_timings.hperiod, + state->current_timings.vperiod, + state->current_timings.interleaved); + if (state->detected_standard) { + *locked = 1; + } else { + /* Detected a signal on the wire, but we have no + * standard defined for it. + */ + v4l2_dbg(2, debug, sd, + "%s() No detected standard for htotal = %d, vtotal = %d, hperiod = %d, vperiod = %d, detectdelay = %d, hactive = %d, interleaved = %d\n", + __func__, + state->current_timings.htotal, + state->current_timings.vtotal, + state->current_timings.hperiod, + state->current_timings.vperiod, + state->current_timings.detectdelay, + state->current_timings.hactive, + state->current_timings.interleaved); + } + + ret = 0; + + /* + * printk(KERN_ERR "%s() r01 = 0x%x\n", __func__, r[0x01]); + * HDMI_MD 2 + * HDCP_OP_STS 1 + * HDCP_MD 0 + * 0x0: DVI, without HDCP. + * 001: DVI OESS* + HDCP, without advance cipher. + * 011: DVI EESS** + HDCP, with advance cipher. + * 1x0: HDMI EESS, without HDCP. + * 101: HDMI EESS + HDCP, without advance cipher. + * 111: HDMI + HDCP EESS, with advance cipher. + * *OESS: Original Encryption Status Signaling. + * **EESS: Enhanced Encryption Status Signaling + */ + } + + state->regb1r01_cached = mst3367_rd(sd, BANK1, 0x01); + state->regb2r48_cached = mst3367_rd(sd, BANK2, 0x48); + + if (*locked) { + state->detected_signal = 1; + if (!state->haveSource) { + state->haveSource = 1; + mst3367_notify_source_detect(sd, state->haveSource); + } + } else { + memset(&state->current_timings, 0, + sizeof(state->current_timings)); + state->detected_signal = 0; + if (state->haveSource) { + state->haveSource = 0; + mst3367_notify_source_detect(sd, state->haveSource); + } + } + + return ret; +} + +static inline u32 MST3367_HdmiGetPacketStatus(struct v4l2_subdev *sd) +{ + u32 status = 0; + u8 r0b, r0c, r0e; + + r0b = mst3367_rd(sd, BANK2, 0x0b) & 0xff; + r0c = mst3367_rd(sd, BANK2, 0x0c) & 0x3f; + r0e = mst3367_rd(sd, BANK2, 0x0e) & 0x08; + + status = (r0c << 8) | r0b; + if (r0e & 0x08) + status |= 0x8000; + + v4l2_dbg(1, debug, sd, "%s() status = 0x%08x\n", __func__, status); + + return status; +} + +static inline u32 MST3367_HdmiGetPacketColor(struct v4l2_subdev *sd) +{ + u32 color = 0; + + u8 r48 = mst3367_rd(sd, BANK2, 0x48) & 0x60; + + if (r48 == 0x00) + color = 0; /* RX_INPUT_RGB */ + else if (r48 == 0x20) + color = 1; /* RX_INPUT_YUV422 */ + else if (r48 == 0x40) + color = 2; /* RX_INPUT_YUV444 */ + + return color; +} + +static int mst3367_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + v4l2_dbg(1, debug, sd, "%s: ctrl id: %d, ctrl->val %d\n", __func__, + ctrl->id, ctrl->val); + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops mst3367_ctrl_ops = { + .s_ctrl = mst3367_s_ctrl, +}; + +#ifdef CONFIG_VIDEO_ADV_DEBUG +/* Register bits 15-8 represent bank, bits 7-0 register. */ +static int mst3367_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + reg->val = mst3367_rd(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff); + reg->size = 1; + return 0; +} + +static int mst3367_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + mst3367_wr(sd, (reg->reg >> 8) & 0xff, reg->reg & 0xff, + reg->val & 0xff); + return 0; +} +#endif + +static int mst3367_log_status(struct v4l2_subdev *sd) +{ + struct mst3367_state *state = get_mst3367_state(sd); + + v4l2_info(sd, "source_connected: %s\n", + state->haveSource ? "yes" : "no"); + v4l2_info(sd, "signal_detected: %s\n", + state->detected_signal ? "yes" : "no"); + v4l2_info(sd, "power: %s\n", + state->power_on ? "on" : "off"); + + if (state->detected_signal) { + v4l2_info(sd, "standard: %dx%dx%d%s\n", + state->detected_standard->timings.bt.width, + state->detected_standard->timings.bt.height, + state->detected_standard->hdmi_fpsX100 / 100, + state->detected_standard->timings.bt.interlaced + ? "i" : "p"); + } else { + v4l2_info(sd, "standard: n/a\n"); + } + + v4l2_info(sd, + "htotal: %d (horizontal front porch, sync, back porch + active pixels)\n", + state->current_timings.htotal); + v4l2_info(sd, + "vtotal: %d (vertical front porch, sync, back porch + active pixels)\n", + state->current_timings.vtotal); + v4l2_info(sd, "hperiod: %d (%d.%d KHz)\n", + state->current_timings.hperiod, + state->current_timings.hperiod / 10, + state->current_timings.hperiod % 10); + + v4l2_info(sd, "vperiod: %d (%d.%d Hz)\n", + state->current_timings.vperiod, + state->current_timings.vperiod / 10, + state->current_timings.vperiod % 10); + + v4l2_info(sd, "detectdelay: %d\n", + state->current_timings.detectdelay); + v4l2_info(sd, "hactive: %d\n", + state->current_timings.hactive); + v4l2_info(sd, "scanline: %s\n", + state->current_timings.interleaved + ? "interleaved" : "progressive"); + + v4l2_info(sd, "input colorspace: %s\n", + (state->regb2r48_cached & 0x60) == 0x00 ? "RX_INPUT_RGB" : + (state->regb2r48_cached & 0x60) == 0x20 ? "RX_INPUT_YUV422" : + (state->regb2r48_cached & 0x60) == + 0x40 ? "RX_INPUT_YUV444" : "UNDEFINED"); + + v4l2_info(sd, "1.01: 0x%02x\n", state->regb1r01_cached); + v4l2_info(sd, "1.01.b2: %s\n", + state->regb1r01_cached & 0x04 ? "HDMI" : "DVI"); + v4l2_info(sd, "1.01.b0: %s\n", + state->regb1r01_cached & 0x01 + ? "HDCP active" : "HDCP not present"); + + return 0; +} + +static void mst3367_init_setup(struct v4l2_subdev *sd) +{ + int i; + u8 csctbl[] = { + 0x40, + 0x08, 0x02, 0x03, 0x65, 0x7E, 0x28, /* M11, M12, M13 */ + 0x78, 0xB9, 0x0B, 0x65, 0x79, 0xD6, /* M21, M22, M23 */ + 0x7F, 0x45, 0x01, 0x27, 0x08, 0x02, /* M31, M32, M33 */ + 0x20, 0x00, 0x02, 0x81, 0x20, 0x01, /* A1, A2, A3 */ + 0x15, 0x95, 0x05, 0x20, 0xC0, 0x08 + }; + + v4l2_dbg(1, debug, sd, "%s\n", __func__); + + MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF); + + /* RxGeneralInit */ + mst3367_wr(sd, BANK0, 0x41, 0x6f); + mst3367_wr(sd, BANK0, 0xb8, 0x00); + + /* RxTmdsInit */ + mst3367_wr(sd, BANK1, 0x0f, 0x02); + mst3367_wr(sd, BANK1, 0x16, 0x30); + mst3367_wr(sd, BANK1, 0x17, 0x00); + mst3367_wr(sd, BANK1, 0x18, 0x00); + mst3367_wr(sd, BANK1, 0x19, 0x00); + mst3367_wr(sd, BANK1, 0x1a, 0x50); + mst3367_clr(sd, BANK1, 0x2a, 0x07); + mst3367_set(sd, BANK1, 0x2a, 0x07); + mst3367_wr(sd, BANK2, 0x08, 0x03); + + /* RxHdcpInit */ + /* receive HDCP */ + mst3367_wr(sd, BANK1, 0x24, 0x40); + + mst3367_wr(sd, BANK1, 0x30, 0x80); + mst3367_wr(sd, BANK1, 0x31, 0x00); + mst3367_wr(sd, BANK1, 0x32, 0x00); + + /* RxVideoInit */ + mst3367_wr(sd, BANK0, 0xb0, 0x14); + mst3367_set(sd, BANK0, 0xae, 0x04); + mst3367_wr(sd, BANK0, 0xad, 0x05); /* ENABLE LOW.PASS FILTER */ + mst3367_wr(sd, BANK0, 0xb1, 0xe0); /* From windows i2c trace. */ + mst3367_wr(sd, BANK0, 0xb2, 0x08); /* From windows i2c trace. */ + mst3367_wr(sd, BANK0, 0xb3, 0x00); + mst3367_wr(sd, BANK0, 0xb4, 0x55); + + /* RxAudioInit */ + mst3367_clr(sd, BANK0, 0xb4, 0x03); + mst3367_wr(sd, BANK2, 0x01, 0x61); + mst3367_wr(sd, BANK2, 0x02, 0xf5); + mst3367_set(sd, BANK2, 0x03, 0x02); + mst3367_wr(sd, BANK2, 0x04, 0x01); + mst3367_wr(sd, BANK2, 0x05, 0x00); + mst3367_wr(sd, BANK2, 0x06, 0x08); + mst3367_wr(sd, BANK2, 0x1c, 0x1a); + mst3367_wr(sd, BANK2, 0x1d, 0x00); + mst3367_wr(sd, BANK2, 0x1e, 0x00); + mst3367_wr(sd, BANK2, 0x1f, 0x00); + mst3367_clr(sd, BANK2, 0x25, 0xa2); + mst3367_set(sd, BANK2, 0x25, 0xa2); + + /* unknown */ + mst3367_set(sd, BANK2, 0x02, 0x80); + mst3367_set(sd, BANK2, 0x07, 0x04); + mst3367_wr(sd, BANK2, 0x17, 0xc0); + mst3367_wr(sd, BANK2, 0x19, 0xff); + mst3367_wr(sd, BANK2, 0x1a, 0xff); + mst3367_wr(sd, BANK2, 0x1b, 0xfc); + mst3367_wr(sd, BANK2, 0x20, 0x00); + mst3367_clr(sd, BANK2, 0x21, 0x03); + mst3367_wr(sd, BANK2, 0x22, 0x26); + mst3367_wr(sd, BANK2, 0x27, 0x00); + mst3367_set(sd, BANK2, 0x2e, 0xa1); + + /* unknown */ + mst3367_wr(sd, BANK0, 0xab, 0x15); /* [COLOR.RANGE] 0x15 */ + mst3367_clr(sd, BANK0, 0xac, 0x3f); + mst3367_set(sd, BANK0, 0xac, 0x15); + + /* RxSwitchSource - HDMI */ + MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_HPD_OFF); + MST3367_HDCP_RESET(sd); + MST3367_HDMI_RESET(sd); + mst3367_wr(sd, BANK0, 0x51, 0x89); + MST3367_TMDS_HOT_PLUG(sd, RX_TMDS_A_HPD_ON | RX_TMDS_A_LINK_ON); + mst3367_wr(sd, BANK0, 0xB7, 0x00); + + /* Patches */ + mst3367_wr(sd, BANK0, 0xE2, 0x00); /* DISABLE AUTO POSITION */ + mst3367_wr(sd, BANK0, 0x1e, 0x11); + mst3367_wr(sd, BANK0, 0x1f, 0x01); + mst3367_wr(sd, BANK0, 0x73, 0x90); + mst3367_wr(sd, BANK0, 0xb5, 0x0c); + + /* CSC */ + mst3367_wr(sd, BANK0, 0x90, 0x15); /* Color Range */ + mst3367_wr(sd, BANK0, 0x91, 0x15); + + for (i = 0; i < sizeof(csctbl); i++) + mst3367_wr(sd, BANK0, 0x92 + i, csctbl[i]); + + /* RX_OUTPUT_YUV422 / 08.BITS / EXTERNAL SYNC */ + mst3367_wr(sd, BANK0, 0xB0, 0x20); + + MST3367_HDMI_INIT(sd); +} + +static int mst3367_s_power(struct v4l2_subdev *sd, int on) +{ + struct mst3367_state *state = get_mst3367_state(sd); + + v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off"); + + /* TODO: Turn on/off the TMDS clocks. */ + + state->power_on = on; + if (on) + mst3367_init_setup(sd); + + return true; +} + +static int mst3367_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + v4l2_dbg(0, debug, sd, "%s()\n", __func__); + *handled = true; + return 0; +} + +static int mst3367_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_core_ops mst3367_core_ops = { + .log_status = mst3367_log_status, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = mst3367_g_register, + .s_register = mst3367_s_register, +#endif + .s_power = mst3367_s_power, + .interrupt_service_routine = mst3367_isr, + .subscribe_event = mst3367_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static int mst3367_s_stream(struct v4l2_subdev *sd, int enable) +{ + v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, + (enable ? "en" : "dis")); + + if (!enable) + mst3367_s_power(sd, 0); + + return 0; +} + +static const struct v4l2_dv_timings_cap mst3367_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = {0}, + V4L2_INIT_BT_TIMINGS(0, 1920, 0, 1200, 25000000, 170000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM) +}; + +static int mst3367_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct mst3367_state *state = get_mst3367_state(sd); + + v4l2_dbg(1, debug, sd, "%s:\n", __func__); + + if (!state->detected_signal) + return -ENODATA; + + *timings = state->detected_standard->timings; + + return 0; +} + +static int mst3367_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &mst3367_timings_cap, NULL, + NULL); +} + +static int mst3367_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + if (cap->pad != 0) + return -EINVAL; + + *cap = mst3367_timings_cap; + + return 0; +} + +static int mst3367_video_s_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + v4l2_dbg(1, debug, sd, "%s(input=%d, output=%d, config=0x%x)\n", + __func__, input, output, config); + + return 0; +} + +static int mst3367_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct mst3367_state *state = get_mst3367_state(sd); + int locked; + int ret; +#if DUMP_SHADOWS || DUMP_REGISTERS + static int count; +#endif + + v4l2_dbg(2, debug, sd, "%s()\n", __func__); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + + /* Perform video standard detection. */ + ret = MST3367_HDMI_MODE_DETECT(sd, &locked); + if (ret < 0 || !locked) { + /* No timings could be detected because no signal was found. */ + return ret; + } + + /* We're detected a signal, return formal timing. */ + *timings = state->detected_standard->timings; + + if (debug > 1) { + v4l2_print_dv_timings(sd->name, "timings: ", timings, true); + MST3367_HdmiGetPacketColor(sd); + } +#if DUMP_SHADOWS + if (count++ > 6) { + count = 0; + dump_shadows(sd, 0); + dump_shadows(sd, 1); + dump_shadows(sd, 2); + } +#endif + +#if DUMP_REGISTERS + if (count++ > 10) { + count = 0; + dump_registers(sd, BANK0); + dump_registers(sd, BANK1); + dump_registers(sd, BANK2); + } +#endif + + return 0; /* Success - Signal locked */ +} + +static int mst3367_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct mst3367_state *state = get_mst3367_state(sd); + + if (state->detected_signal) { + /* Clear these failed bits, we have a signal. */ + *status &= ~V4L2_IN_ST_NO_POWER; + *status &= ~V4L2_IN_ST_NO_SIGNAL; + } else { + /* Establish failed bits. */ + *status |= V4L2_IN_ST_NO_POWER; + *status |= V4L2_IN_ST_NO_SIGNAL; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops mst3367_video_ops = { + .s_stream = mst3367_s_stream, + .g_dv_timings = mst3367_g_dv_timings, + .s_routing = mst3367_video_s_routing, + .query_dv_timings = mst3367_query_dv_timings, + .g_input_status = mst3367_g_input_status, +}; + +static const struct v4l2_subdev_pad_ops mst3367_pad_ops = { + .enum_dv_timings = mst3367_enum_dv_timings, + .dv_timings_cap = mst3367_dv_timings_cap, +}; + +static const struct v4l2_subdev_ops mst3367_ops = { + .core = &mst3367_core_ops, + .video = &mst3367_video_ops, + .pad = &mst3367_pad_ops, +}; + +static int mst3367_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mst3367_state *state; + struct v4l2_ctrl_handler *hdl; + struct v4l2_subdev *sd; + int err = -EIO; + + v4l_dbg(1, debug, client, "%s()\n", __func__); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + v4l_err(client, "%s() no dice!\n", __func__); + return -EIO; + } + + v4l_dbg(1, debug, client, "detecting mst3367 client on address 0x%x\n", + client->addr << 1); + + state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->current_bank = 0xff; + state->i2c = client->adapter; + state->i2c_addr = 0x9c; + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &mst3367_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + hdl = &state->hdl; + v4l2_ctrl_handler_init(hdl, 2); + + state->hotplug_ctrl = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0); + state->rx_sense_ctrl = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0); + sd->ctrl_handler = hdl; + if (hdl->error) { + err = hdl->error; + goto err_hdl; + } + + if (mst3367_rd(sd, BANK0, 0x50) != 1) { + v4l2_err(sd, "chip_revision != 1\n"); + err = -EIO; + goto err_hdl; + } + + state->detected_signal = 0; + + mst3367_init_setup(sd); + v4l2_ctrl_handler_setup(&state->hdl); + + v4l2_info(sd, "%s found and initialized @ addr 0x%x (%s)\n", + client->name, client->addr << 1, client->adapter->name); + + if (debug) + v4l2_info(sd, "Debugging is enabled\n"); + + v4l2_info(sd, "driver loaded\n"); + + return 0; + +err_hdl: + v4l2_ctrl_handler_free(&state->hdl); + return err; +} + +static int mst3367_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_dbg(1, debug, sd, "%s()\n", __func__); + + v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name, + client->addr << 1, client->adapter->name); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + v4l_info(client, "driver unloaded\n"); + + return 0; +} + +static struct i2c_device_id mst3367_id[] = { + {"mst3367", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mst3367_id); + +static struct i2c_driver mst3367_driver = { + .driver = { + .name = "mst3367", + }, + .probe = mst3367_probe, + .remove = mst3367_remove, + .id_table = mst3367_id, +}; + +module_i2c_driver(mst3367_driver); diff --git a/include/media/i2c/mst3367.h b/include/media/i2c/mst3367.h new file mode 100644 index 000000000000..bdbe70258df3 --- /dev/null +++ b/include/media/i2c/mst3367.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for the MSTAR 3367 HDMI Receiver + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#ifndef MST3367_H +#define MST3367_H + +/* notify events */ +#define MST3367_SOURCE_DETECT 0 + +struct mst3367_source_detect { + int present; +}; + +#endif /* MST3367_H */ From patchwork Fri Oct 19 10:54:39 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Grzeschik X-Patchwork-Id: 10648991 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 54CB2112B for ; Fri, 19 Oct 2018 10:55:10 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 366F628A3F for ; Fri, 19 Oct 2018 10:55:10 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 2940728A8B; Fri, 19 Oct 2018 10:55:10 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7D57628A3F for ; Fri, 19 Oct 2018 10:55:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726784AbeJSTAg (ORCPT ); Fri, 19 Oct 2018 15:00:36 -0400 Received: from metis.ext.pengutronix.de ([85.220.165.71]:44299 "EHLO metis.ext.pengutronix.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727039AbeJSTAd (ORCPT ); Fri, 19 Oct 2018 15:00:33 -0400 Received: from dude.hi.pengutronix.de ([2001:67c:670:100:1d::7]) by metis.ext.pengutronix.de with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.89) (envelope-from ) id 1gDSQj-0007Sh-50; Fri, 19 Oct 2018 12:54:57 +0200 Received: from mgr by dude.hi.pengutronix.de with local (Exim 4.91) (envelope-from ) id 1gDSQg-0000BM-H4; Fri, 19 Oct 2018 12:54:54 +0200 From: Michael Grzeschik To: linux-media@vger.kernel.org Cc: linux-kernel@vger.kernel.org, stoth@kernellabs.com, mchehab@kernel.org, davem@davemloft.net, laurent.pinchart@ideasonboard.com, kernel@pengutronix.de Subject: [PATCH 2/2] media: hdcapm: add support for usb2hdcapm hdmi2usb framegrabber from startech Date: Fri, 19 Oct 2018 12:54:39 +0200 Message-Id: <20181019105439.27796-3-m.grzeschik@pengutronix.de> X-Mailer: git-send-email 2.19.0 In-Reply-To: <20181019105439.27796-1-m.grzeschik@pengutronix.de> References: <20181019105439.27796-1-m.grzeschik@pengutronix.de> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2001:67c:670:100:1d::7 X-SA-Exim-Mail-From: mgr@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-media@vger.kernel.org Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Steven Toth This patch is based on the work of Steven Toth. He reverse engineered the driver by tracing the windows driver. https://github.com/stoth68000/hdcapm/ Signed-off-by: Steven Toth Signed-off-by: Michael Grzeschik --- MAINTAINERS | 6 + drivers/media/usb/Kconfig | 1 + drivers/media/usb/Makefile | 1 + drivers/media/usb/hdcapm/Kconfig | 11 + drivers/media/usb/hdcapm/Makefile | 3 + drivers/media/usb/hdcapm/hdcapm-buffer.c | 230 ++++++ drivers/media/usb/hdcapm/hdcapm-compressor.c | 782 +++++++++++++++++++ drivers/media/usb/hdcapm/hdcapm-core.c | 743 ++++++++++++++++++ drivers/media/usb/hdcapm/hdcapm-i2c.c | 332 ++++++++ drivers/media/usb/hdcapm/hdcapm-reg.h | 111 +++ drivers/media/usb/hdcapm/hdcapm-video.c | 665 ++++++++++++++++ drivers/media/usb/hdcapm/hdcapm.h | 283 +++++++ 12 files changed, 3168 insertions(+) create mode 100644 drivers/media/usb/hdcapm/Kconfig create mode 100644 drivers/media/usb/hdcapm/Makefile create mode 100644 drivers/media/usb/hdcapm/hdcapm-buffer.c create mode 100644 drivers/media/usb/hdcapm/hdcapm-compressor.c create mode 100644 drivers/media/usb/hdcapm/hdcapm-core.c create mode 100644 drivers/media/usb/hdcapm/hdcapm-i2c.c create mode 100644 drivers/media/usb/hdcapm/hdcapm-reg.h create mode 100644 drivers/media/usb/hdcapm/hdcapm-video.c create mode 100644 drivers/media/usb/hdcapm/hdcapm.h diff --git a/MAINTAINERS b/MAINTAINERS index 9c69b7f9b2f9..20c04092d3c5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6486,6 +6486,12 @@ L: linux-parisc@vger.kernel.org S: Maintained F: sound/parisc/harmony.* +HDCAPM USB HDMI FRAME GRABBER DRIVER +M: Michael Grzeschik +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/usb/hdcapm/ + HDPVR USB VIDEO ENCODER DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig index b24e753c4766..5da5a849bad7 100644 --- a/drivers/media/usb/Kconfig +++ b/drivers/media/usb/Kconfig @@ -41,6 +41,7 @@ if I2C && MEDIA_DIGITAL_TV_SUPPORT comment "Digital TV USB devices" source "drivers/media/usb/dvb-usb/Kconfig" source "drivers/media/usb/dvb-usb-v2/Kconfig" +source "drivers/media/usb/hdcapm/Kconfig" source "drivers/media/usb/ttusb-budget/Kconfig" source "drivers/media/usb/ttusb-dec/Kconfig" source "drivers/media/usb/siano/Kconfig" diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile index 21e46b10caa5..a729842842fe 100644 --- a/drivers/media/usb/Makefile +++ b/drivers/media/usb/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_USB_MSI2500) += msi2500/ obj-$(CONFIG_VIDEO_CPIA2) += cpia2/ obj-$(CONFIG_VIDEO_AU0828) += au0828/ obj-$(CONFIG_VIDEO_HDPVR) += hdpvr/ +obj-$(CONFIG_VIDEO_HDCAPM) += hdcapm/ obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ obj-$(CONFIG_VIDEO_STK1160) += stk1160/ diff --git a/drivers/media/usb/hdcapm/Kconfig b/drivers/media/usb/hdcapm/Kconfig new file mode 100644 index 000000000000..925e88abe68b --- /dev/null +++ b/drivers/media/usb/hdcapm/Kconfig @@ -0,0 +1,11 @@ + +config VIDEO_HDCAPM + tristate "Startech HDCAPM support" + depends on VIDEO_DEV && VIDEO_V4L2 + select VIDEO_MST3367 if MEDIA_SUBDRV_AUTOSELECT + select I2C_ALGOBIT + help + This is a video4linux driver for Startech's HDCAPM USB device. + + To compile this driver as a module, choose M here: the + module will be called hdpvr diff --git a/drivers/media/usb/hdcapm/Makefile b/drivers/media/usb/hdcapm/Makefile new file mode 100644 index 000000000000..a0ccdecbd8f7 --- /dev/null +++ b/drivers/media/usb/hdcapm/Makefile @@ -0,0 +1,3 @@ +hdcapm-objs := hdcapm-core.o hdcapm-video.o hdcapm-compressor.o hdcapm-buffer.o hdcapm-i2c.o + +obj-$(CONFIG_VIDEO_HDCAPM) += hdcapm.o diff --git a/drivers/media/usb/hdcapm/hdcapm-buffer.c b/drivers/media/usb/hdcapm/hdcapm-buffer.c new file mode 100644 index 000000000000..e61de3e29d91 --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-buffer.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include "hdcapm.h" + +struct hdcapm_buffer *hdcapm_buffer_alloc(struct hdcapm_dev *dev, + u32 nr, u32 maxsize) +{ + struct hdcapm_buffer *buf; + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return NULL; + + buf->nr = nr; + buf->dev = dev; + buf->maxsize = maxsize; + buf->ptr = kzalloc(maxsize, GFP_KERNEL); + if (!buf->ptr) { + kfree(buf); + return NULL; + } + + return buf; +} + +void hdcapm_buffer_free(struct hdcapm_buffer *buf) +{ + kfree(buf->ptr); + buf->ptr = NULL; + + usb_free_urb(buf->urb); + buf->urb = NULL; + + kfree(buf); +} + +/* Helper macro for moving all buffers to another list. + * We WILL take the mutex in this func. + */ +void hdcapm_buffers_move_all(struct hdcapm_dev *dev, + struct list_head *to, struct list_head *from) +{ + struct hdcapm_buffer *buf; + + mutex_lock(&dev->dmaqueue_lock); + while (!list_empty(from)) { + buf = list_first_entry(from, struct hdcapm_buffer, list); + if (buf) + list_move_tail(&buf->list, to); + } + mutex_unlock(&dev->dmaqueue_lock); +} + +/* Helper macro to free all buffers from a list. + * We WILL take the mutex in this func. + */ +void hdcapm_buffers_free_all(struct hdcapm_dev *dev, struct list_head *head) +{ + struct hdcapm_buffer *buf; + + mutex_lock(&dev->dmaqueue_lock); + while (!list_empty(head)) { + buf = list_first_entry(head, struct hdcapm_buffer, list); + if (buf) { + list_del(&buf->list); + hdcapm_buffer_free(buf); + } + } + mutex_unlock(&dev->dmaqueue_lock); +} + +/* Helper macros for managing the device lists. + * We WILL take the mutex in this func. + * Return a reference to the top most used buffer, we're going to + * read some or all of it (probably). Don't delete it from the list. + */ +struct hdcapm_buffer *hdcapm_buffer_peek_used(struct hdcapm_dev *dev) +{ + struct hdcapm_buffer *buf = NULL; + + mutex_lock(&dev->dmaqueue_lock); + if (!list_empty(&dev->list_buf_used)) { + buf = list_first_entry(&dev->list_buf_used, + struct hdcapm_buffer, list); + } + mutex_unlock(&dev->dmaqueue_lock); + + v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf); + + return buf; +} + +static struct hdcapm_buffer *hdcapm_buffer_next_used(struct hdcapm_dev *dev) +{ + struct hdcapm_buffer *buf = NULL; + + mutex_lock(&dev->dmaqueue_lock); + if (!list_empty(&dev->list_buf_used)) { + buf = list_first_entry(&dev->list_buf_used, + struct hdcapm_buffer, list); + list_del(&buf->list); + } + mutex_unlock(&dev->dmaqueue_lock); + + v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf); + + return buf; +} + +/* Pull the top buffer from the free list, but don't specifically remove + * it from the list. If no buffer exists, steal one from the used list. + * We WILL take the mutex in this func. Return the buffer at the top of + * the free list, delete the list node. We're probably going to fill it + * and move it to the used list. IF no free buffers exist, steal one + * from the used list and flag an internal data loss statistic. + */ +struct hdcapm_buffer *hdcapm_buffer_next_free(struct hdcapm_dev *dev) +{ + struct hdcapm_buffer *buf = NULL; + + mutex_lock(&dev->dmaqueue_lock); + if (!list_empty(&dev->list_buf_free)) { + buf = list_first_entry(&dev->list_buf_free, + struct hdcapm_buffer, list); + list_del(&buf->list); + } + mutex_unlock(&dev->dmaqueue_lock); + + if (!buf) { + v4l2_err(dev->sd, + "%s() No empty buffers. Increase buffer_count.\n", + __func__); + buf = hdcapm_buffer_next_used(dev); + if (!buf) + v4l2_err(dev->sd, + "%s() No free or empty buffers.\n", __func__); + dev->stats->buffer_overrun++; + } + + v4l2_dbg(3, hdcapm_debug, dev->sd, "%s() returns %p\n", __func__, buf); + + return buf; +} + +static void hdcapm_buffer_add_to_list(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf, + struct list_head *list) +{ + mutex_lock(&dev->dmaqueue_lock); + list_add_tail(&buf->list, list); + mutex_unlock(&dev->dmaqueue_lock); +} + +inline void hdcapm_buffer_add_to_free(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf) +{ + hdcapm_buffer_add_to_list(dev, buf, &dev->list_buf_free); +} + +inline void hdcapm_buffer_add_to_used(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf) +{ + hdcapm_buffer_add_to_list(dev, buf, &dev->list_buf_used); +} + +static inline void hdcapm_buffer_move(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf, + struct list_head *list) +{ + mutex_lock(&dev->dmaqueue_lock); + list_move_tail(&buf->list, list); + mutex_unlock(&dev->dmaqueue_lock); +} + +/* Helper macros for moving a buffer to the free list. + * We WILL take the mutex in this func. + */ +void hdcapm_buffer_move_to_free(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf) +{ + hdcapm_buffer_move(dev, buf, &dev->list_buf_free); +} + +/* Helper macros for moving a buffer to the used list. + * We WILL take the mutex in this func. + */ +void hdcapm_buffer_move_to_used(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf) +{ + hdcapm_buffer_move(dev, buf, &dev->list_buf_used); +} + +/* For debugging. Lock the buffer queue and measure how much data (in bytes) + * and how many items are on the list. + */ +int hdcapm_buffer_used_queue_stats(struct hdcapm_dev *dev, + u64 *bytes, u64 *items) +{ + struct hdcapm_buffer *buf = NULL; + struct list_head *p = NULL, *q = NULL; + + *bytes = 0; + *items = 0; + + mutex_lock(&dev->dmaqueue_lock); + list_for_each_safe(p, q, &dev->list_buf_used) { + buf = list_entry(p, struct hdcapm_buffer, list); + (*bytes) += (buf->actual_size - buf->readpos); + (*items)++; + } + mutex_unlock(&dev->dmaqueue_lock); + + return 0; +} diff --git a/drivers/media/usb/hdcapm/hdcapm-compressor.c b/drivers/media/usb/hdcapm/hdcapm-compressor.c new file mode 100644 index 000000000000..c8795bef4d00 --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-compressor.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include "hdcapm.h" + +#define CMD_ARRAY_SIZE(arr) (sizeof((arr)) / sizeof(u32)) + +static char *cmd_name(u32 id) +{ + switch (id) { + case 0x01: return "Start Compressor"; + case 0x02: return "Stop Compressor"; + case 0x10: return "Configure Compressor Interface"; + default: return "Undefined"; + } +} + +/* Wait up to 500ms for the firmware to be ready, or return a timeout. + * On idle, value 1 is return else < 0 indicates an error. + */ +static int fw_check_idle(struct hdcapm_dev *dev) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(500); + int ret = -ETIMEDOUT; + u32 val; + + while (!time_after(jiffies, timeout)) { + if (hdcapm_read32(dev, REG_FW_CMD_BUSY, &val) != 0) { + ret = -EINVAL; /* Error trying to read register. */ + break; + } + + if (val == 0) { + ret = 1; /* Success - Firmware is idle. */ + break; + } + + usleep_range(10000, 15000); + } + + return ret; +} + +/* Send a command to the firmware. + * + * Firmware commands and arguments are passed to this function for + * transmission to the hardware. + * An array of u32s, with the first u32 being the command type, followed + * by N arguments that are written to ARGS[0-n]. + * Return 0 on success else < 0. + */ +static int execute_cmd(struct hdcapm_dev *dev, const u32 *cmdarr, u32 entries) +{ + int ret; + int i; + + /* Check hardware is ready */ + mutex_lock(&dev->lock); + + if (fw_check_idle(dev) > 0) { + v4l2_dbg(1, hdcapm_debug, dev->sd, + "FIRMWARE CMD = 0x%08x [%s]\n", + *cmdarr, cmd_name(*cmdarr)); + + /* Send a new command to the hardware/firmware. */ + /* Write all args into the FW arg registers */ + for (i = 1; i < entries; i++) + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%2d: 0x%08x\n", i - 1, *(cmdarr + i)); + + for (i = 1; i < entries; i++) + hdcapm_write32(dev, REG_FW_CMD_ARG(i - 1), + *(cmdarr + i)); + + /* Prepare the firmware to execute a command. */ + hdcapm_write32(dev, REG_FW_CMD_BUSY, 1); + + /* Trigger the command execution. */ + hdcapm_write32(dev, REG_FW_CMD_EXECUTE, *cmdarr); + + ret = 0; /* Success */ + } else { + ret = -EINVAL; + } + + mutex_unlock(&dev->lock); + + return ret; +} + +/* Stop encoder */ +static const u32 cmd_02[] = { + 0x00000002, + 0x00000000, +}; + +/* start encoder */ +static const u32 cmd_0a[] = { + 0x0000000a, + 0x00000008, +}; + +/* 27813 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_f1[] = { + 0x000000f1, + 0x80000011, +}; + +/* 27873 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_f2[] = { + 0x000000f2, + 0x01000100, +}; + +/* disable encoder */ +static const u32 cmd_f3[] = { + 0x000000f3, + 0x00000000, +}; + +/* 28548 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_0f[] = { + 0x00000010, + 0x0000000f, + 0x00000000, + 0x00000000, +}; + +/* 28643 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_10[] = { + 0x00000010, + 0x00000010, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +/* 28743 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_12[] = { + 0x00000010, + 0x00000012, + 0x00000000, +}; + +/* 28823 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_13[] = { + 0x00000010, + 0x00000013, + 0x00000050, + 0x00000000, + 0x0000000a, +}; + +/* 29028 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_16[] = { + 0x00000010, + 0x00000016, + 0x00000000, + 0x00000000, +}; + +/* 29123 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_17[] = { + 0x00000010, + 0x00000017, + 0x00000000, +}; + +/* 29203 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_10_02[] = { + 0x00000010, + 0x00000002, + 0xf1f1f1da, + 0xb6f1f1b6, +}; + +/* LGPEncoder/complete-trace.tdc + * EP4 -> 01 00 07 00 B0 06 00 00 + * - (query buffer availablility, read 7 words from address 6b0) + * EP3 <- 40 00 00 00 83 00 00 00 00 1F 3E 00 00 00 + * 00 00 3F 5B 00 00 AA AA AA AA 01 00 00 00 + * EP4 -> 09 00 08 00 00 00 00 00 00 1F 3E 00 3F 5B 00 00 + * The buffer is transferred via EP1 IN, + * note that the ISO13818 DWORDS are byte reversed..... + * then this message is sent to the firmware to acknowledge the buffer was read? + */ +/* 30739 in LGPEncoder/complete-trace.tdc */ +static const u32 cmd_30[] = { + 0x00000030, /* Fixed value */ + 0x00000083, /* Fixed value */ + 0x00005b3f, /* Number of DWORDS we previously read. */ + 0x00010007, /* Fixed value */ + 0x2aaaaaaa, /* Fixed value */ + 0x00000000, /* Fixed value */ + 0x00000000, /* Fixed value */ +}; + +static int hdcapm_compressor_enable_firmware(struct hdcapm_dev *dev, int val) +{ + // 32527 + /* EP4 Host -> 07 01 00 00 00 00 00 00 */ + u8 tx[] = { + 0x07, + val, /* 0 = stop, 1 = start. */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + }; + + v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__); + + /* Flush this to EP4 via a bulk write. */ + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + return 0; /* Success */ +} + +static int firmware_transition(struct hdcapm_dev *dev, int run, + struct v4l2_dv_timings *timings) +{ + struct hdcapm_encoder_parameters *p = &dev->encoder_parameters; + + /* 29298 in LGPEncoder/complete-trace.tdc */ + u32 cfg[12]; + + u32 i_width, i_height, i_fps; + u32 o_width, o_height, o_fps; + u32 min_bitrate_kbps = dev->encoder_parameters.bitrate_bps / 1000; + u32 max_bitrate_kbps = dev->encoder_parameters.bitrate_peak_bps / 1000; + u32 htotal, vtotal; + u32 timing_fpsx100; + + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(%p, %s)\n", __func__, dev, run == 1 ? "START" : "STOP"); + if (run) { + if (!timings) { + v4l2_err(dev->sd, "no timing during firmware transition\n"); + return -EINVAL; + } + + /* Prepare the video/audio compression settings. */ + i_width = timings->bt.width; + i_height = timings->bt.height; + htotal = V4L2_DV_BT_FRAME_WIDTH(&timings->bt); + vtotal = V4L2_DV_BT_FRAME_HEIGHT(&timings->bt); + if (htotal * vtotal) { + timing_fpsx100 = + div_u64((100 * (u64)timings->bt.pixelclock), + (htotal * vtotal)); + } else { + v4l2_err(dev->sd, "no fps calulated\n"); + return -EINVAL; + } + + i_fps = timing_fpsx100 / 100; + + /* If the user has requested a different output + * resolution via set/try fmt, obey. + */ + if (p->output_width) + o_width = p->output_width; + else + o_width = i_width; + + if (p->output_width) + o_height = p->output_height; + else + o_height = i_height; + + o_fps = i_fps; + + /* Scaling. Adjust width, height and output fps. + * Hardware can't handle anything above p30, drop frames + * from p60 to 30, p50 to 25. + */ + + if (timings->bt.width == 1920 && timings->bt.height == 1080 && + !timings->bt.interlaced && i_fps > 30) + o_fps /= 2; + + cfg[0] = 0x00000001; + cfg[1] = 0x21010019 | + (dev->encoder_parameters.h264_level << 12) | + (dev->encoder_parameters.h264_entropy_mode << 26) | + (dev->encoder_parameters.h264_profile << 8); + + cfg[2] = i_height << 16 | i_width; + cfg[3] = o_fps << 23 | i_fps << 16 | 0x0609; + cfg[4] = 0x0050 << 16 | min_bitrate_kbps | + (dev->encoder_parameters.h264_mode << 31); + cfg[5] = max_bitrate_kbps << 16 | min_bitrate_kbps; + cfg[6] = 0x48002000; + cfg[7] = 0xe0010000 | dev->encoder_parameters.gop_size; + cfg[8] = o_height << 16 | o_width; + cfg[9] = 0xc00000d0; + cfg[10] = 0x21121080; + cfg[11] = 0x465001f2; + + hdcapm_compressor_enable_firmware(dev, 1); + + /* From LGP device dump line 27788 */ + execute_cmd(dev, cmd_f1, CMD_ARRAY_SIZE(cmd_f1)); + execute_cmd(dev, cmd_f2, CMD_ARRAY_SIZE(cmd_f2)); + + /* Configure the video / audio compressors. */ + execute_cmd(dev, cmd_10_0f, CMD_ARRAY_SIZE(cmd_10_0f)); + execute_cmd(dev, cmd_10_10, CMD_ARRAY_SIZE(cmd_10_10)); + execute_cmd(dev, cmd_10_12, CMD_ARRAY_SIZE(cmd_10_12)); + execute_cmd(dev, cmd_10_13, CMD_ARRAY_SIZE(cmd_10_13)); + execute_cmd(dev, cmd_10_16, CMD_ARRAY_SIZE(cmd_10_16)); + execute_cmd(dev, cmd_10_17, CMD_ARRAY_SIZE(cmd_10_17)); + execute_cmd(dev, cmd_10_02, CMD_ARRAY_SIZE(cmd_10_02)); + + /* Configure and start encoder. */ + execute_cmd(dev, cfg, CMD_ARRAY_SIZE(cfg)); + execute_cmd(dev, cmd_0a, CMD_ARRAY_SIZE(cmd_0a)); + } else { + /* Stop and disable encoder. */ + execute_cmd(dev, cmd_02, CMD_ARRAY_SIZE(cmd_02)); + execute_cmd(dev, cmd_f3, CMD_ARRAY_SIZE(cmd_f3)); + } + + return 0; +} + +/* Perform a status read of the compressor. If TS data is available then + * query that and push the buffer into a user queue for later processing. + */ +static int usb_read(struct hdcapm_dev *dev) +{ + struct hdcapm_buffer *buf; + u32 val; + u32 arr[7]; + u8 r[4]; + int ret, i; + u32 bytes_to_read; + + /* Query the Compressor regs 0x6b0-0x6c8. + * Determine whether a buffer is ready for transfer. + * Reg 6b0 (0): Status indicator? + * Reg 6b4 (1): F1 always 0x83 + * Reg 6b8 (2): Transport buffer address (on host h/w) + * Reg 6bc (3): + * Reg 6c0 (4): Number of dwords + * Reg 6c4 (5): + * Reg 6c8 (6): + * Line 55674 - LGPEncoder/complete-trace.tdc + */ + + ret = hdcapm_read32_array(dev, REG_06B0, ARRAY_SIZE(arr), &arr[0], 1); + if (ret < 0) { + /* Failure to read from the device. */ + return -EINVAL; + } + + v4l2_dbg(3, hdcapm_debug, dev->sd, + "tsb reply: %08x %08x %08x %08x %08x %08x %08x\n", + arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6]); + + /* Check the reply */ + if (arr[6] == 0) { + /* Buffer not yet ready. */ + dev->stats->codec_ts_not_yet_ready++; + return -ETIMEDOUT; + } + + /* Check this is a TS buffer */ + if ((arr[0] & 0xff) != 0x40) { + /* Unexpected, debug this. */ + v4l2_err(dev->sd, + "tsb reply: %08x %08x %08x %08x %08x %08x %08x (No 0x40?)\n", + arr[0], arr[1], arr[2], arr[3], + arr[4], arr[5], arr[6]); + //WARN_ON(); + } + + /* Check this other fixed value. */ + if ((arr[1] & 0xff) != 0x83) { + /* Unexpected, debug this. */ + v4l2_err(dev->sd, + "tsb reply: %08x %08x %08x %08x %08x %08x %08x (No 0x83?)\n", + arr[0], arr[1], arr[2], arr[3], + arr[4], arr[5], arr[6]); + WARN_ON(1); + } + + bytes_to_read = arr[4] * sizeof(u32); + if (bytes_to_read > 256000) { + /* Unexpected, debug this. */ + v4l2_err(dev->sd, + "tsb reply: %08x %08x %08x %08x %08x %08x %08x (Too many dwords?)\n", + arr[0], arr[1], arr[2], arr[3], + arr[4], arr[5], arr[6]); + WARN_ON(1); + } + + /* We need a buffer to transfer the TS into. */ + buf = hdcapm_buffer_next_free(dev); + if (!buf) + return -EINVAL; + + /* Transfer buffer from the USB device + * (address arr[2]), length arr[4]). + */ + ret = hdcapm_dmaread32(dev, arr[2], (u32 *)buf->ptr, arr[4]); + if (ret < 0) { + /* Throw the buffer back in the free list. */ + hdcapm_buffer_add_to_free(dev, buf); + return -EINVAL; + } + + /* The buffer comes back in DWORD ordering, we need to fixup the + * payload to put the TS packet bytes back into the right order. + */ + for (i = 0; i < bytes_to_read; i += 4) { + r[0] = *(buf->ptr + i + 3); + r[1] = *(buf->ptr + i + 2); + r[2] = *(buf->ptr + i + 1); + r[3] = *(buf->ptr + i + 0); + + *(buf->ptr + i + 0) = r[0]; + *(buf->ptr + i + 1) = r[1]; + *(buf->ptr + i + 2) = r[2]; + *(buf->ptr + i + 3) = r[3]; + } + + dev->stats->codec_bytes_received += bytes_to_read; + dev->stats->codec_buffers_received++; + + /* Put the buffer on the used list, + * the caller will read/dequeue it later. + */ + buf->actual_size = bytes_to_read; + buf->readpos = 0; + hdcapm_buffer_add_to_used(dev, buf); + + /* Signal to any userland waiters, new buffer available. */ + wake_up_interruptible(&dev->wait_read); + + /* Acknowledge the buffer back to the firmware. */ + hdcapm_read32(dev, 0x800, &val); + hdcapm_write32(dev, 0x800, val); + + hdcapm_write32(dev, REG_FW_CMD_ARG(0), 0x83); + hdcapm_write32(dev, REG_FW_CMD_ARG(1), arr[4]); + hdcapm_write32(dev, REG_FW_CMD_ARG(2), 0x2aaaaaaa); + hdcapm_write32(dev, REG_FW_CMD_ARG(3), 0); + hdcapm_write32(dev, REG_FW_CMD_ARG(5), 0); + hdcapm_write32(dev, REG_FW_CMD_BUSY, 1); + hdcapm_write32(dev, REG_FW_CMD_EXECUTE, 0x30); + + hdcapm_write32(dev, 0x6c8, 0); + + return 0; +} + +void hdcapm_compressor_init_gpios(struct hdcapm_dev *dev) +{ + // 38045 - bit toggling, gpios + + /* Available GPIO's 15-0. */ + + /* Configure GPIO's 3-1, 8, 11, 12 as outputs. */ + hdcapm_set32(dev, REG_GPIO_OE, 0x2); + hdcapm_set32(dev, REG_GPIO_OE, 0x4); + hdcapm_set32(dev, REG_GPIO_OE, 0x8); + hdcapm_set32(dev, REG_GPIO_OE, 0x100); + hdcapm_set32(dev, REG_GPIO_OE, 0x800); + hdcapm_set32(dev, REG_GPIO_OE, 0x1000); + /* Reg should end up at 190E */ + + /* Pull all GPIO's high. */ + hdcapm_clr32(dev, REG_GPIO_DATA_WR, 0xFFFFFFFF); + + /* GPIO #2 is the MST3367 reset, active high, */ + + /* TODO: is this register inverted, + * meaning writes high result in low? + */ + hdcapm_set32(dev, REG_GPIO_DATA_WR, 0x00000004); + + mdelay(500); +} + +int hdcapm_compressor_register(struct hdcapm_dev *dev) +{ + const struct firmware *fw = NULL; + const char *fw_video = "v4l-hdcapm-vidfw-01.fw"; + size_t fw_video_len = 453684; + const char *fw_audio = "v4l-hdcapm-audfw-01.fw"; + size_t fw_audio_len = 363832; + u32 val; + u32 *dwords; + u32 addr; + u32 chunk; + u32 cpy; + u32 offset; + int ret; + + hdcapm_compressor_enable_firmware(dev, 0); + +// pl330b_lib_reinit + hdcapm_write32(dev, REG_081C, 0x00004000); + hdcapm_write32(dev, REG_0820, 0x00103FFF); + hdcapm_write32(dev, REG_0824, 0x00000000); + + hdcapm_write32(dev, REG_0828, 0x00104000); + hdcapm_write32(dev, REG_082C, 0x00203FFF); + hdcapm_write32(dev, REG_0830, 0x00100000); + + hdcapm_write32(dev, REG_0834, 0x00204000); + hdcapm_write32(dev, REG_0838, 0x00303FFF); + hdcapm_write32(dev, REG_083C, 0x00200000); + + hdcapm_write32(dev, REG_0840, 0x70003124); + hdcapm_write32(dev, REG_0840, 0x90003124); + + /* Hardware ID? Only every read, never written */ + if (hdcapm_read32(dev, REG_0038, &val) < 0) { + v4l2_err(dev->sd, + "USB read failure, chip id check failed, aborting.\n"); + return -EINVAL; + } + v4l2_dbg(1, hdcapm_debug, dev->sd, + "chiprev? [%08x = %08x]\n", REG_0038, val); + WARN_ON(val != 0x00010020); + +#if ONETIME_FW_LOAD + hdcapm_write32(dev, REG_GPIO_OE, 0x00000000); + hdcapm_write32(dev, REG_GPIO_DATA_WR, 0x00000000); +#endif + + /* Disable audio / video outputs (bits 1/2). */ + hdcapm_write32(dev, REG_0050, 0x00200400); + hdcapm_read32(dev, REG_0050, &val); + + v4l2_dbg(1, hdcapm_debug, dev->sd, "%08x = %08x\n", REG_0050, val); + WARN_ON(val != 0x00200400); + + /* Give the device enough time to boot its initial microcode. */ + msleep(1000); + + hdcapm_compressor_enable_firmware(dev, 0); + + hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000); + hdcapm_write32(dev, REG_081C, 0x00004000); + + hdcapm_write32(dev, REG_0820, 0x00103FFF); + hdcapm_write32(dev, REG_0824, 0x00000000); + hdcapm_write32(dev, REG_0828, 0x00104000); + hdcapm_write32(dev, REG_082C, 0x00203FFF); + hdcapm_write32(dev, REG_0830, 0x00100000); + hdcapm_write32(dev, REG_0834, 0x00204000); + hdcapm_write32(dev, REG_0838, 0x00303FFF); + hdcapm_write32(dev, REG_083C, 0x00200000); + hdcapm_write32(dev, REG_0840, 0x70003124); + hdcapm_write32(dev, REG_0840, 0x90003124); + + hdcapm_write32(dev, REG_0050, 0x00200406); + + hdcapm_write32(dev, REG_0050, 0x00200406); + hdcapm_read32(dev, REG_0050, &val); + + /* Disable audio and video outputs. */ + hdcapm_write32(dev, REG_0050, 0x00200406); + +#if ONETIME_FW_LOAD + hdcapm_write32(dev, REG_GPIO_OE, 0x00000000); + hdcapm_write32(dev, REG_GPIO_DATA_WR, 0x00000000); +#endif + + hdcapm_read32(dev, REG_0000, &val); + hdcapm_write32(dev, REG_0000, 0x03FF0300); + + /* Wipe memory at various addresses */ + dwords = kzalloc(0x2000 * sizeof(u32), GFP_KERNEL); + if (!dwords) + return -ENOMEM; + + if (hdcapm_dmawrite32(dev, 0x0005634E, dwords, 0x2000) < 0) { + v4l2_err(dev->sd, "wipe of addr1 failed\n"); + return -EINVAL; + } + if (hdcapm_dmawrite32(dev, 0x0005834E, dwords, 0x2000) < 0) { + v4l2_err(dev->sd, "wipe of addr2 failed\n"); + return -EINVAL; + } + if (hdcapm_dmawrite32(dev, 0x0005A34E, dwords, 0x1E3B) < 0) { + v4l2_err(dev->sd, "wipe of addr3 failed\n"); + return -EINVAL; + } + kfree(dwords); + + /* Upload the audio firmware. */ + ret = request_firmware(&fw, fw_audio, &dev->udev->dev); + if (ret) { + v4l2_err(dev->sd, + "failed to find firmware file %s, aborting upload.\n", + fw_video); + return -EINVAL; + } + if (fw->size != fw_audio_len) { + v4l2_err(dev->sd, "failed video firmware length check\n"); + release_firmware(fw); + return -EINVAL; + } + v4l2_info(dev->sd, "loading audio firmware size %zu bytes.\n", + fw->size); + + offset = 0; + addr = 0x00040000; + val = fw_audio_len; + chunk = 0x2000 * sizeof(u32); + while (val > 0) { + if (val > chunk) + cpy = chunk; + else + cpy = val; + + hdcapm_dmawrite32(dev, addr, (const u32 *)fw->data + offset, + cpy / sizeof(u32)); + + val -= cpy; + addr += (cpy / sizeof(u32)); + offset += (cpy / sizeof(u32)); + } + release_firmware(fw); + + hdcapm_mem_write32(dev, 0x000BC425, 1); + hdcapm_mem_write32(dev, 0x000BC424, 0); + hdcapm_mem_write32(dev, 0x000BC801, 0); + + hdcapm_write32(dev, REG_0B78, 0x00150000); + hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000); + + /* Upload the video firmware. */ + ret = request_firmware(&fw, fw_video, &dev->udev->dev); + if (ret) { + v4l2_err(dev->sd, + "failed to find firmware file %s, aborting upload.\n", + fw_video); + return -EINVAL; + } + if (fw->size != fw_video_len) { + v4l2_err(dev->sd, + "failed video firmware length check\n"); + release_firmware(fw); + return -EINVAL; + } + v4l2_info(dev->sd, "loading video firmware size %zu bytes.\n", + fw->size); + + /* Load the video firmware */ + offset = 0; + addr = 0x00000000; + val = fw_video_len; + chunk = 0x2000 * sizeof(u32); + while (val > 0) { + if (val > chunk) + cpy = chunk; + else + cpy = val; + + hdcapm_dmawrite32(dev, addr, (const u32 *)fw->data + offset, + cpy / sizeof(u32)); + + val -= cpy; + addr += (cpy / sizeof(u32)); + offset += (cpy / sizeof(u32)); + } + release_firmware(fw); + + hdcapm_compressor_enable_firmware(dev, 1); + hdcapm_write32(dev, REG_FW_CMD_BUSY, 0x00000000); + + hdcapm_mem_read32(dev, 0x00000040, &val); + + hdcapm_mem_read32(dev, 0x00000041, &val); + + hdcapm_mem_read32(dev, 0x000Bc804, &val); + + msleep(100); + +#if ONETIME_FW_LOAD + hdcapm_compressor_init_gpios(dev); +#endif + + v4l2_info(dev->sd, "Registered compressor\n"); + return 0; +} + +void hdcapm_compressor_unregister(struct hdcapm_dev *dev) +{ + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s() Unregistered compressor\n", __func__); +} + +void hdcapm_compressor_run(struct hdcapm_dev *dev) +{ + struct v4l2_dv_timings timings; + int ret; + int val; + + v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__); + + if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0) { + v4l2_err(dev->sd, "%s() subdev call failed\n", __func__); + dev->state = STATE_STOPPED; + return; + } + + /* Make sure all of our buffers are available again. */ + hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used); + +#if !(ONETIME_FW_LOAD) + /* Register the compression codec (it does both audio and video). */ + if (hdcapm_compressor_register(dev) < 0) { + v4l2_err(dev->sd, "failed to register compressor\n"); + return; + } +#endif + + /* Enable audio and video outputs. */ + hdcapm_read32(dev, REG_0050, &val); + val &= ~(1 << 1); + val &= ~(1 << 2); + hdcapm_write32(dev, REG_0050, val); + + ret = firmware_transition(dev, 1, &timings); + + dev->state = STATE_STARTED; + while (dev->state == STATE_STARTED) { + ret = usb_read(dev); + usleep_range(500, 4000); + } + + /* Disable audio and video outputs. */ + hdcapm_read32(dev, REG_0050, &val); + val |= (1 << 1); + val |= (1 << 2); + hdcapm_write32(dev, REG_0050, val); + + /* Give the device enough time to resume its microcode. */ + msleep(1000); + + ret = firmware_transition(dev, 0, NULL); + +#if !(ONETIME_FW_LOAD) + hdcapm_compressor_unregister(dev); + hdcapm_compressor_init_gpios(dev); + + /* Reloading the firmware disturbs the GPIOs and + * causes the MST3367 to go into reset. + * Be kind, tell the HDMI receiver to + * reconfigure itself. + */ + v4l2_subdev_call(dev->sd, core, s_power, 1); +#endif + + dev->state = STATE_STOPPED; + + hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used); +} diff --git a/drivers/media/usb/hdcapm/hdcapm-core.c b/drivers/media/usb/hdcapm/hdcapm-core.c new file mode 100644 index 000000000000..68836d339b7d --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-core.c @@ -0,0 +1,743 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include "hdcapm.h" +#include + +int hdcapm_debug; +module_param_named(debug, hdcapm_debug, int, 0644); +MODULE_PARM_DESC(debug, "debug bitmask: 1) module"); + +int hdcapm_i2c_scan; +module_param_named(i2c_scan, hdcapm_i2c_scan, int, 0644); +MODULE_PARM_DESC(i2c_scan, "Probe i2c bus for devices"); + +unsigned int thread_poll_interval = 500; +module_param(thread_poll_interval, int, 0644); +MODULE_PARM_DESC(thread_poll_interval, + "have the kernel thread poll every N ms (def:500)"); + +unsigned int buffer_count = 128; +module_param(buffer_count, int, 0644); +MODULE_PARM_DESC(buffer_count, "# of buffers the driver should queue"); + +#define XFERBUF_SIZE (65536 * 4) +unsigned int buffer_size = XFERBUF_SIZE; +module_param(buffer_size, int, 0644); +MODULE_PARM_DESC(buffer_size, "size of each buffer in bytes"); + +static DEFINE_MUTEX(devlist); +LIST_HEAD(hdcapm_devlist); +static unsigned int devlist_count; + +/* Copy an on-stack transfer buffer into a device context. Do this + * before we pass it to the USB subsystem, else ARM complains (once) in + * the USB controller about the location of the transfer. TODO: Review + * usage and optimize of the calls so that not all transfers need to be + * on stack. + */ +int hdcapm_core_ep_send(struct hdcapm_dev *dev, int endpoint, + u8 *buf, u32 len, u32 timeout) +{ + int writelength; + + if (len > XFERBUF_SIZE) { + v4l2_err(dev->sd, + "%s() buffer of %d bytes too large for transfer\n", + __func__, len); + return -1; + } + + memcpy(dev->xferbuf, buf, len); + dev->xferbuf_len = len; + + /* Flush this to EP4 via a bulk write. */ + return usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, endpoint), + dev->xferbuf, dev->xferbuf_len, + &writelength, timeout); +} + +/* Copy a transfer buffer from the device context back to an onstack location. + * TODO: Review usage and optimize of the calls so that not all + * transfers need to be on stack. + */ +int hdcapm_core_ep_recv(struct hdcapm_dev *dev, int endpoint, + u8 *buf, u32 len, u32 *actual, u32 timeout) +{ + int ret; + + WARN_ON(len > XFERBUF_SIZE); + + /* Bulk read */ + ret = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, endpoint), + dev->xferbuf, len, &dev->xferbuf_len, timeout); + + memcpy(buf, dev->xferbuf, dev->xferbuf_len); + *actual = dev->xferbuf_len; + + return ret; +} + +int hdcapm_mem_write32(struct hdcapm_dev *dev, u32 addr, u32 val) +{ + /* EP4 Host -> 02 01 04 00 01 C8 0B 00 01 C8 0B 00 00 00 00 00 */ + u8 tx[] = { + 0x02, + 0x01, /* Write */ + 0x04, + 0x00, + addr, /* This is really a fill function? */ + addr >> 8, + addr >> 16, + addr >> 24, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + val, + val >> 8, + val >> 16, + val >> 24, + }; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, val); + + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + return 0; +} + +/* Read a single DWORD from the USB device memory. */ +int hdcapm_mem_read32(struct hdcapm_dev *dev, u32 addr, u32 *val) +{ + int len; + u8 rx[4]; + + /* Read bytes between to addresses + * EP4 Host -> 02 00 04 00 01 C8 0B 00 01 C8 0B 00 + * EP3 Host <- 00 00 00 00 + */ + u8 tx[] = { + 0x02, + 0x00, /* Read */ + 0x04, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + }; + + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + /* Read 4 bytes from EP 3. */ + /* TODO: shouldn;t the buffer length be 4? */ + if (hdcapm_core_ep_recv(dev, + PIPE_EP3, &rx[0], sizeof(rx), &len, 500) < 0) + return -1; + + *val = rx[0] | (rx[1] << 8) | (rx[2] << 16) | (rx[3] << 24); + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, *val); + + return 0; +} + +/* Write a series of DMA DWORDS from the USB device memory. */ +int hdcapm_dmawrite32(struct hdcapm_dev *dev, u32 addr, + const u32 *arr, u32 entries) +{ + int len; + u8 rx; + + /* EP4 Host -> 09 01 08 00 00 00 00 00 4E 63 05 00 00 20 00 00 */ + u8 tx[] = { + 0x09, + 0x01, /* Write */ + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + entries, + entries >> 8, + entries >> 16, + entries >> 24, + }; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, entries); + + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + /* Read 1 byte1 from EP 3. */ + if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx, sizeof(rx), &len, 500) < 0) + return -1; + + if (rx != 0) + return -1; + + /* Flush the buffer to device */ + if (hdcapm_core_ep_send(dev, PIPE_EP2, (u8 *)arr, entries * sizeof(u32), + 5000) < 0) + return -1; + + return 0; +} + +/* Read a series of DMA DWORDS from the USB device memory. */ +int hdcapm_dmaread32(struct hdcapm_dev *dev, u32 addr, u32 *arr, u32 entries) +{ + int len; + u8 rx; + + /* EP4 Host -> 09 00 08 00 00 00 00 00 00 C8 05 00 00 04 00 00 */ + u8 tx[] = { + 0x09, + 0x00, /* Read */ + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + entries, + entries >> 8, + entries >> 16, + entries >> 24, + }; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, entries); + + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + /* Read 1 byte1 from EP 3. */ + if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx, sizeof(rx), &len, 500) < 0) + return -1; + + if (rx != 0) + return -1; + + /* Flush the buffer to device */ + if (hdcapm_core_ep_recv(dev, PIPE_EP1, (u8 *)arr, entries * sizeof(u32), + &len, 5000) < 0) + return -1; + + return 0; +} + +/* Write a DWORD to a USB device register. */ +int hdcapm_write32(struct hdcapm_dev *dev, u32 addr, u32 val) +{ + /* EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00 */ + u8 tx[] = { + 0x01, + 0x01, /* Write */ + 0x01, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + val, + val >> 8, + val >> 16, + val >> 24, + }; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, val); + + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + return 0; +} + +/* Read a DWORD from a USB device register. */ +int hdcapm_read32(struct hdcapm_dev *dev, u32 addr, u32 *val) +{ + int len; + u8 rx[4]; + + /* EP4 Host -> 01 00 01 00 00 05 00 00 */ + u8 tx[] = { + 0x01, + 0x00, /* Read */ + 0x01, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + }; + + /* Flush this to EP4 via a write. */ + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) + return -1; + + /* Read 4 bytes from EP 3. */ + if (hdcapm_core_ep_recv(dev, PIPE_EP3, &rx[0], + sizeof(rx), &len, 500) < 0) + return -1; + + *val = rx[0] | (rx[1] << 8) | (rx[2] << 16) | (rx[3] << 24); + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(0x%08x, 0x%08x)\n", __func__, addr, *val); + + return 0; +} + +/* Read (bulk) a number of DWORDS from device registers and endian + * convert if requested. + */ +int hdcapm_read32_array(struct hdcapm_dev *dev, u32 addr, + u32 wordcount, u32 *arr, int le_to_cpu) +{ + int len, i, j; + int readlenbytes = wordcount * sizeof(u32); + u8 *rx; + + /* EP4 Host -> 01 00 07 00 B0 06 00 00 */ + u8 tx[] = { + 0x01, + 0x00, /* Read */ + wordcount, + 0x00, + addr, + addr >> 8, + addr >> 16, + addr >> 24, + }; + + rx = kzalloc(readlenbytes, GFP_KERNEL); + if (!rx) + return -ENOMEM; + + /* Flush this to EP4 via a write. */ + if (hdcapm_core_ep_send(dev, PIPE_EP4, &tx[0], sizeof(tx), 500) < 0) { + kfree(rx); + return -1; + } + + /* Read 4 bytes from EP 3. */ + if (hdcapm_core_ep_recv(dev, PIPE_EP3, rx, readlenbytes, &len, 500) + < 0) { + kfree(rx); + return -1; + } + + v4l2_dbg(2, hdcapm_debug, dev->sd, "%s(0x%08x) =\n", __func__, addr); + + for (i = 0, j = 0; i < len; i += 4, j++) { + *(arr + j) = rx[i + 0] | (rx[i + 1] << 8) | + (rx[i + 2] << 16) | (rx[i + 3] << 24); + + if (le_to_cpu) + *(arr + j) = le32_to_cpu(*(arr + j)); + } + + kfree(rx); + return 0; +} + +/* Set one or more bits high int a USB device register. */ +void hdcapm_set32(struct hdcapm_dev *dev, u32 addr, u32 mask) +{ + u32 val; + + hdcapm_read32(dev, addr, &val); + val |= mask; + hdcapm_write32(dev, addr, val); +} + +/* Set one or more bits low int a USB device register. */ +void hdcapm_clr32(struct hdcapm_dev *dev, u32 addr, u32 mask) +{ + u32 val; + + hdcapm_read32(dev, addr, &val); + val &= ~mask; + hdcapm_write32(dev, addr, val); +} + +int hdcapm_core_stop_streaming(struct hdcapm_dev *dev) +{ + dev->state = STATE_STOP; + + return 0; +} + +int hdcapm_core_start_streaming(struct hdcapm_dev *dev) +{ + dev->state = STATE_START; + + return 0; +} + +/* Worker thread to poll the HDMI receiver, and run the USB + * transfer mechanism when the encoder starts. + */ +static int hdcapm_thread_function(void *data) +{ + struct hdcapm_dev *dev = data; + struct v4l2_dv_timings timings; + int ret; + + dev->thread_active = 1; + v4l2_dbg(1, hdcapm_debug, dev->sd, "%s() Started\n", __func__); + + set_freezable(); + + while (1) { + msleep_interruptible(thread_poll_interval); + + if (kthread_should_stop()) + break; + + try_to_freeze(); + + if (dev->state == STATE_STOPPED) + ret = v4l2_subdev_call(dev->sd, + video, query_dv_timings, + &timings); + + if (dev->state == STATE_START) + hdcapm_compressor_run(dev); /* blocking */ + } + + dev->thread_active = 0; + return 0; +} + +static void hdcapm_usb_v4l2_release(struct v4l2_device *v4l2_dev) +{ + struct hdcapm_dev *dev = + container_of(v4l2_dev, struct hdcapm_dev, v4l2_dev); + + v4l2_device_unregister_subdev(dev->sd); + + // TODO: Do I need this? + //v4l2_ctrl_handler_free(&dev->v4l2_ctrl_hdl); + + v4l2_device_unregister(&dev->v4l2_dev); +} + +/* sub-device events are pushed with v4l2_subdev_notify() and + * v4l2_subdev_notify_enent(). They eventually make their way here. + * The bridge then forwards those events via v4l2_event_queue() + * to the v4l2_device, and so eventually they end up in userspace. + */ +static void hdcapm_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct hdcapm_dev *dev = container_of(sd->v4l2_dev, + struct hdcapm_dev, v4l2_dev); + struct mst3367_source_detect *mst3367; + + switch (notification) { + case MST3367_SOURCE_DETECT: + mst3367 = (struct mst3367_source_detect *)arg; + break; + case V4L2_DEVICE_NOTIFY_EVENT: + /* + * Userspace can monitor for these with: + * v4l2-ctl -d /dev/video2 --wait-for-event=source_change=0 + */ + v4l2_event_queue(dev->v4l_device, arg); + break; + default: + v4l2_err(sd, "unhandled notification = 0x%x\n", notification); + break; + } +} + +static int hdcapm_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct hdcapm_dev *dev; + struct hdcapm_buffer *buf; + struct usb_device *udev; + struct i2c_board_info mst3367_info; + int ret, i; + + udev = interface_to_usbdev(intf); + + if (intf->altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + dev_dbg(&udev->dev, + "%s() vendor id 0x%x device id 0x%x\n", __func__, + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + /* Ensure the bus speed is 480Mbps. */ + if (udev->speed != USB_SPEED_HIGH) { + dev_err(&intf->dev, + "Device must be connected to a USB 2.0 port (480Mbps).\n"); + return -ENODEV; + } + + dev = devm_kzalloc(&udev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->xferbuf = devm_kzalloc(&udev->dev, XFERBUF_SIZE, GFP_KERNEL); + if (!dev->xferbuf) + return -ENOMEM; + + dev->stats = devm_kzalloc(&udev->dev, sizeof(struct hdcapm_statistics), + GFP_KERNEL); + if (!dev->stats) + return -ENOMEM; + + strlcpy(dev->name, "Startech HDCAPM Encoder", sizeof(dev->name)); + dev->state = STATE_STOPPED; + dev->udev = udev; + + mutex_init(&dev->lock); + mutex_init(&dev->dmaqueue_lock); + INIT_LIST_HEAD(&dev->list_buf_free); + INIT_LIST_HEAD(&dev->list_buf_used); + init_waitqueue_head(&dev->wait_read); + usb_set_intfdata(intf, dev); + + /* Register the I2C buses. */ + if (hdcapm_i2c_register(dev, &dev->i2cbus[0], 0) < 0) { + dev_err(&intf->dev, "failed to register i2cbus 0\n"); + return -EINVAL; + } + + /* We're not using bus#1, it has the eeprom on it. Remove this or leave + * for future developers with future products? + */ + if (hdcapm_i2c_register(dev, &dev->i2cbus[1], 1) < 0) { + dev_err(&intf->dev, "failed to register i2cbus 1\n"); + ret = -EINVAL; + goto fail3; + } +#if ONETIME_FW_LOAD + /* Register the compression codec (it does both audio and video). */ + if (hdcapm_compressor_register(dev) < 0) { + dev_err(&intf->dev, "failed to register compressor\n"); + ret = -EINVAL; + goto fail4; + } +#else + hdcapm_compressor_init_gpios(dev); +#endif + + /* Attach HDMI receiver */ + ret = v4l2_device_register(&intf->dev, &dev->v4l2_dev); + if (ret < 0) { + dev_err(&intf->dev, "v4l2_device_register failed\n"); + ret = -EINVAL; + goto fail5; + } + + dev->v4l2_dev.release = hdcapm_usb_v4l2_release; + dev->v4l2_dev.notify = hdcapm_notify; + + /* Configure a sub-device attachment for the HDMI receiver. */ + memset(&mst3367_info, 0, sizeof(struct i2c_board_info)); + strlcpy(mst3367_info.type, "mst3367", I2C_NAME_SIZE); + + mst3367_info.addr = 0x9c >> 1; + + dev->sd = v4l2_i2c_new_subdev_board(&dev->v4l2_dev, + &dev->i2cbus[0].i2c_adap, + &mst3367_info, NULL); + if (!dev->sd) { + dev_err(&intf->dev, + "failed to find or load a driver for the MST3367\n"); + ret = -EINVAL; + goto fail6; + } + + /* Power on the HDMI receiver, assuming it needs it. */ + v4l2_subdev_call(dev->sd, core, s_power, 1); + + /* We need some buffers to hold user payload. */ + for (i = 0; i < buffer_count; i++) { + buf = hdcapm_buffer_alloc(dev, i, buffer_size); + if (!buf) { + dev_err(&intf->dev, + "failed to allocate a user buffer\n"); + ret = -ENOMEM; + goto fail8; + } + + mutex_lock(&dev->dmaqueue_lock); + list_add_tail(&buf->list, &dev->list_buf_free); + mutex_unlock(&dev->dmaqueue_lock); + } + + /* Formally register the V4L2 interfaces. */ + if (hdcapm_video_register(dev) < 0) { + dev_err(&intf->dev, "failed to register video device\n"); + ret = -EINVAL; + goto fail8; + } + + /* Bring up a kernel thread to manage the HDMI frontend and run + * the data pump. + */ + dev->kthread = kthread_run(hdcapm_thread_function, dev, "hdcapm hdmi"); + if (!dev->kthread) { + dev_err(&intf->dev, "failed to create hdmi kernel thread\n"); + ret = -EINVAL; + goto fail9; + } + + /* Finish the rest of the hardware configuration. */ + mutex_lock(&devlist); + list_add_tail(&dev->devlist, &hdcapm_devlist); + devlist_count++; + mutex_unlock(&devlist); + + dev_info(&intf->dev, "Registered device '%s'\n", dev->name); + + return 0; /* Success */ + +fail9: + hdcapm_video_unregister(dev); +fail8: + /* Put all the buffers back on the free list, then dealloc them. */ + hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used); + hdcapm_buffers_free_all(dev, &dev->list_buf_free); +fail6: + v4l2_device_unregister(&dev->v4l2_dev); +fail5: +#if ONETIME_FW_LOAD + hdcapm_compressor_unregister(dev); +fail4: +#endif + hdcapm_i2c_unregister(dev, &dev->i2cbus[1]); +fail3: + hdcapm_i2c_unregister(dev, &dev->i2cbus[0]); + + return ret; +} + +static void hdcapm_usb_disconnect(struct usb_interface *intf) +{ + struct hdcapm_dev *dev = usb_get_intfdata(intf); + int i; + + v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__); + + if (dev->kthread) { + kthread_stop(dev->kthread); + dev->kthread = NULL; + + i = 0; + while (dev->thread_active) { + msleep(500); + if (i++ > 24) + break; + } + } + + hdcapm_video_unregister(dev); + +#if ONETIME_FW_LOAD + /* Unregister the compression codec. */ + hdcapm_compressor_unregister(dev); +#endif + + /* Unregister any I2C buses. */ + hdcapm_i2c_unregister(dev, &dev->i2cbus[1]); + hdcapm_i2c_unregister(dev, &dev->i2cbus[0]); + + /* Put all the buffers back on the free list, the dealloc them. */ + hdcapm_buffers_move_all(dev, &dev->list_buf_free, &dev->list_buf_used); + hdcapm_buffers_free_all(dev, &dev->list_buf_free); + + mutex_lock(&devlist); + list_del(&dev->devlist); + mutex_unlock(&devlist); +} + +static int hdcapm_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct hdcapm_dev *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + + /* TODO: Power off the HDMI receiver? */ + + pr_info(KBUILD_MODNAME ": USB is suspend\n"); + + return 0; +} + +static int hdcapm_resume(struct usb_interface *intf) +{ + struct hdcapm_dev *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + + /* TODO: Power on the HDMI receiver? */ + + return 0; +} + +struct usb_device_id hdcapm_usb_id_table[] = { + { USB_DEVICE(0x1164, 0x75a7), .driver_info = HDCAPM_CARD_REV1 }, + { /* -- end -- */ }, +}; +MODULE_DEVICE_TABLE(usb, hdcapm_usb_id_table); + +static struct usb_driver hdcapm_usb_driver = { + .name = KBUILD_MODNAME, + .probe = hdcapm_usb_probe, + .disconnect = hdcapm_usb_disconnect, + .id_table = hdcapm_usb_id_table, + .suspend = hdcapm_suspend, + .resume = hdcapm_resume, + .reset_resume = hdcapm_resume, +}; + +module_usb_driver(hdcapm_usb_driver); + +MODULE_DESCRIPTION("Driver for StarTech USB2HDCAPM USB capture product"); +MODULE_AUTHOR("Steven Toth "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.0.1"); diff --git a/drivers/media/usb/hdcapm/hdcapm-i2c.c b/drivers/media/usb/hdcapm/hdcapm-i2c.c new file mode 100644 index 000000000000..6ef74459948b --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-i2c.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include "hdcapm.h" + +#define GPIO_SCL BIT(14) +#define GPIO_SDA BIT(15) + +static unsigned int i2c_udelay = 5; +module_param(i2c_udelay, int, 0644); +MODULE_PARM_DESC(i2c_udelay, + "i2c delay at insmod time, in usecs (should be 5 or higher). Lower value means higher bus speed."); + +/* GPIO bit-banged bus */ +static void hdcapm_bit_setscl(void *data, int state) +{ + struct hdcapm_i2c_bus *bus = data; + struct hdcapm_dev *dev = bus->dev; + + if (state) + hdcapm_clr32(dev, REG_GPIO_OE, GPIO_SCL); + else + hdcapm_set32(dev, REG_GPIO_OE, GPIO_SCL); +} + +static void hdcapm_bit_setsda(void *data, int state) +{ + struct hdcapm_i2c_bus *bus = data; + struct hdcapm_dev *dev = bus->dev; + + if (state) + hdcapm_clr32(dev, REG_GPIO_OE, GPIO_SDA); + else + hdcapm_set32(dev, REG_GPIO_OE, GPIO_SDA); +} + +static int hdcapm_bit_getscl(void *data) +{ + struct hdcapm_i2c_bus *bus = data; + struct hdcapm_dev *dev = bus->dev; + u32 val; + + hdcapm_read32(dev, REG_GPIO_DATA_RD, &val); + + return val & GPIO_SCL ? 1 : 0; +} + +static int hdcapm_bit_getsda(void *data) +{ + struct hdcapm_i2c_bus *bus = data; + struct hdcapm_dev *dev = bus->dev; + u32 val; + + hdcapm_read32(dev, REG_GPIO_DATA_RD, &val); + + return val & GPIO_SDA ? 1 : 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_algo_bit_data hdcapm_i2c1_algo_template = { + .setsda = hdcapm_bit_setsda, + .setscl = hdcapm_bit_setscl, + .getsda = hdcapm_bit_getsda, + .getscl = hdcapm_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +/* Internal I2C Bus */ +static int i2c_writeread(struct i2c_adapter *i2c_adap, + const struct i2c_msg *msg, int joined_rlen) +{ +/* + * EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00 + * - usbwrite(REG_504, 0x55) + * EP4 Host -> 01 01 01 00 00 05 00 00 09 27 00 80 + * - usbwrite(REG_500, 0x80002709); + * EP4 Host -> 01 00 01 00 00 05 00 00 + * EP3 Host <- 09 27 00 00 + * - 2709 = usbread(REG_0500); + * EP4 Host -> 01 00 01 00 0c 05 00 00 + * EP3 Host <- 03 00 00 00 + * - 03 = usbread(REG_050c); + */ + struct hdcapm_i2c_bus *bus = i2c_adap->algo_data; + struct hdcapm_dev *dev = bus->dev; + struct i2c_msg *nextmsg = (struct i2c_msg *)(msg + 1); + u32 val; + int ret; + int safety = 32; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(addr=0x%x, reg=0x%x, len=%d)\n", __func__, + msg->addr, msg->buf[0], msg->len); + + ret = hdcapm_write32(dev, REG_I2C_W_BUF, msg->buf[0]); + + /* Write one and read one byte? */ + val = (1 << 31); + val |= 9; + val |= (msg->addr << 7); + ret = hdcapm_write32(dev, REG_I2C_XACT, val); + + /* I2C engine busy? */ + val = (1 << 31); + while (val & 0x80000000) { + /* Check bit31 has cleared? */ + ret = hdcapm_read32(dev, REG_I2C_XACT, &val); + if (safety-- == 0) + break; + } + if (safety == 0) { + v4l2_err(dev->sd, ": stuck i2c bit, aborting.\n"); + return 0; + } + + /* Read i2c result */ + ret = hdcapm_read32(dev, REG_I2C_R_BUF, &val); + nextmsg->buf[0] = val & 0x000000ff; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(addr=0x%x, reg = 0x%x) = 0x%02x\n", __func__, + msg->addr, msg->buf[0], nextmsg->buf[0]); + return 1; +} + +static int i2c_write(struct i2c_adapter *i2c_adap, const struct i2c_msg *msg, + int joined) +{ + struct hdcapm_i2c_bus *bus = i2c_adap->algo_data; + struct hdcapm_dev *dev = bus->dev; + u32 val; + int ret, i; + + /* Position each data byte into the u32, for a single strobe + * into the write buffer. + */ + val = 0; + for (i = msg->len; i > 0; i--) { + val <<= 8; + val |= msg->buf[i - 1]; + } + + ret = hdcapm_write32(dev, REG_I2C_W_BUF, val); + + /* Write N bytes, no read */ + val = (1 << 31); + val |= msg->len; + val |= (msg->addr << 7); + ret = hdcapm_write32(dev, REG_I2C_XACT, val); + + return 1; +} + +static int i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct hdcapm_i2c_bus *bus = i2c_adap->algo_data; + struct hdcapm_dev *dev = bus->dev; + int ret = 0; + int i; + + v4l2_dbg(2, hdcapm_debug, dev->sd, + "%s(num = %d)\n", __func__, num); + + for (i = 0; i < num; i++) { + v4l2_dbg(4, hdcapm_debug, dev->sd, + "%s(num = %d) addr = 0x%02x len = 0x%x\n", + __func__, num, msgs[i].addr, msgs[i].len); + if (msgs[i].flags & I2C_M_RD) { + /* do nothing */ + } else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + /* write then read from same address */ + ret = i2c_writeread(i2c_adap, &msgs[i], + msgs[i + 1].len); + if (ret < 0) + goto error; + i++; + + } else { + /* Write */ + ret = i2c_write(i2c_adap, &msgs[i], 0); + } + if (ret < 0) + goto error; + } + return num; + +error: + return ret; +} + +static u32 hdcapm0_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static const struct i2c_algorithm hdcapm_i2c0_algo_template = { + .master_xfer = i2c_xfer, + .functionality = hdcapm0_functionality, +}; + +static const struct i2c_adapter hdcapm_i2c0_adap_template = { + .name = "hdcapm internal", + .owner = THIS_MODULE, + .algo = &hdcapm_i2c0_algo_template, +}; + +static struct i2c_client hdcapm_i2c0_client_template = { + .name = "hdcapm internal", +}; + +static int i2c_readreg8(struct hdcapm_i2c_bus *bus, u8 addr, u8 reg, u8 *val) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + + struct i2c_msg msg[] = { + { .addr = addr, .flags = 0, .buf = b0, .len = 1 }, + { .addr = addr, .flags = I2C_M_RD, .buf = b1, .len = 1 } }; + + ret = i2c_transfer(&bus->i2c_adap, msg, 2); + if (ret != 2) + return 0; + + *val = b1[0]; + + return 2; +} + +static char *i2c_devs[128] = { + [0x66 >> 1] = "MST3367?", + [0x88 >> 1] = "MST3367?", + [0x94 >> 1] = "MST3367?", + [0x9c >> 1] = "MST3367?", + [0xa2 >> 1] = "EEPROM", +}; + +static void do_i2c_scan(struct hdcapm_i2c_bus *bus) +{ + struct hdcapm_dev *dev = bus->dev; + int a, ret; + u8 val; + + for (a = 0; a < 128; a++) { + ret = i2c_readreg8(bus, a, 0x00, &val); + if (ret == 2) { + v4l2_info(dev->sd, + "%s: i2c scan: found device @ 0x%x [%s]\n", + __func__, a << 1, + i2c_devs[a] ? i2c_devs[a] : "???"); + } + } +} + +int hdcapm_i2c_register(struct hdcapm_dev *dev, + struct hdcapm_i2c_bus *bus, int nr) +{ + bus->nr = nr; + bus->dev = dev; + + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s() registering I2C Bus#%d\n", __func__, bus->nr); + + if (bus->nr == 0) { + bus->i2c_adap = hdcapm_i2c0_adap_template; + bus->i2c_client = hdcapm_i2c0_client_template; + + bus->i2c_adap.dev.parent = &dev->udev->dev; + strlcpy(bus->i2c_adap.name, KBUILD_MODNAME, + sizeof(bus->i2c_adap.name)); + + bus->i2c_adap.algo_data = bus; + i2c_set_adapdata(&bus->i2c_adap, bus); + i2c_add_adapter(&bus->i2c_adap); + + bus->i2c_client.adapter = &bus->i2c_adap; + + } else if (bus->nr == 1) { + bus->i2c_algo = hdcapm_i2c1_algo_template; + + bus->i2c_adap.dev.parent = &dev->udev->dev; + strlcpy(bus->i2c_adap.name, KBUILD_MODNAME, + sizeof(bus->i2c_adap.name)); + bus->i2c_adap.owner = THIS_MODULE; + bus->i2c_algo.udelay = i2c_udelay; + bus->i2c_algo.data = bus; + i2c_set_adapdata(&bus->i2c_adap, bus); + bus->i2c_adap.algo_data = &bus->i2c_algo; + bus->i2c_client.adapter = &bus->i2c_adap; + strlcpy(bus->i2c_client.name, "hdcapm gpio", I2C_NAME_SIZE); + + hdcapm_bit_setscl(bus, 1); + hdcapm_bit_setsda(bus, 1); + + i2c_bit_add_bus(&bus->i2c_adap); + + } else { + WARN_ON(1); + } + + if (hdcapm_i2c_scan && bus->nr == 1) + do_i2c_scan(bus); + + return 0; +} + +void hdcapm_i2c_unregister(struct hdcapm_dev *dev, struct hdcapm_i2c_bus *bus) +{ + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s() unregistering I2C Bus#%d\n", __func__, bus->nr); + + i2c_del_adapter(&bus->i2c_adap); +} diff --git a/drivers/media/usb/hdcapm/hdcapm-reg.h b/drivers/media/usb/hdcapm/hdcapm-reg.h new file mode 100644 index 000000000000..8f8487840ac5 --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-reg.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +/* + * idle-no-hdmi-connected.tdc -- Nothing else of consequence in the + * file. It's worth noting that when you run the graphedit tool under + * windows, open up the Analog Capture property page, switch to the + * Driver Properties view, and enable "PRINT DEBUG" option, debug view + * shows the following driver activity: + * "MST3367_HDMI_MODE_DETECT(0x55 = 0x03)". + * + * Record 4: + * + * EP4 Host -> 01 01 01 00 04 05 00 00 55 00 00 00 + * - usbwrite(REG_504, 0x55) + * EP4 Host -> 01 01 01 00 00 05 00 00 09 27 00 80 + * - usbwrite(REG_500, 0x80002709); + * EP4 Host -> 01 00 01 00 00 05 00 00 + * EP3 Host <- 09 27 00 00 + * - 2709 = usbread(REG_0500); + * EP4 Host -> 01 00 01 00 0c 05 00 00 + * EP3 Host <- 03 00 00 00 + * - 03 = usbread(REG_050c); + * + * In light of the debug view findings, I conclude: + * Writes to 504 establist a I2C write to device 0x55. + * Writes to 500 are...... what? + * reads for 50c are reads from the i2c bus answer. + * + * 80 00 27 09 = + * 1000 0000 | 0000 0000 | 0010 0111 | 0000 1001 + * + * 001 = 1 rx/tx length? + * 001 = 1 + * 01001110 = 0x4e device address or register of MST3367? + * 10011100 = 0x9c device address or register of MST3367? + * .... tv schematic suggests this is likely correct. + * + */ + +/* Bit 13: Cleared during initialization, stall h/w + */ +#define REG_0000 0x000 + +/* Register is read but never written to. + * Hardware version / chip id? + */ +#define REG_0038 0x038 + +/* Bit 0,3-7: Unknown + * 1: Low when audio output is required, high when disabled. + * 2: Low when video output is required, high when disabled. + */ +#define REG_0050 0x050 + +#define REG_I2C_XACT 0x500 +#define REG_I2C_W_BUF 0x504 +#define REG_I2C_R_BUF 0x50c + +/* driver-install.csv shows toggling of register between values: + * Bits 15.. .. 0 + * 19 0E -- 0001 1001 0000 1110 + * 59 0E -- 0101 1001 0000 1110 + * 99 0E -- 1001 1001 0000 1110 + * D9 0E -- 1101 1001 0000 1110 + * Suggesting bits 15/14 are a bitbanged I2C bus. + * We'll assume 15: SDA, 14: SCL + */ +#define REG_GPIO_OE 0x610 +#define REG_GPIO_DATA_WR 0x614 +#define REG_GPIO_DATA_RD 0x618 + +#define REG_06B0 0x6b0 + +#define REG_FW_CMD_BUSY 0x6cc + +/* Valid args are 0 - 10 */ +#define REG_FW_CMD_ARG(n) (0x6f8 - ((n) * 4)) + +/* A command 'type' or identifier is written to this register, + * after the type specifics args have already been written. + */ +#define REG_FW_CMD_EXECUTE 0x6fc + +#define REG_081C 0x81c +#define REG_0820 0x820 +#define REG_0824 0x824 +#define REG_0828 0x828 +#define REG_082C 0x82c +#define REG_0830 0x830 +#define REG_0834 0x834 +#define REG_0838 0x838 +#define REG_083C 0x83c +#define REG_0840 0x840 + +#define REG_0B78 0xb78 diff --git a/drivers/media/usb/hdcapm/hdcapm-video.c b/drivers/media/usb/hdcapm/hdcapm-video.c new file mode 100644 index 000000000000..47d19361f54a --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm-video.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#include "hdcapm.h" + +#define ENCODER_MIN_BITRATE 2000000 +#define ENCODER_MAX_BITRATE 20000000 +#define ENCODER_DEF_BITRATE ENCODER_MAX_BITRATE + +#define ENCODER_MIN_GOP_SIZE 1 +#define ENCODER_MAX_GOP_SIZE 60 +#define ENCODER_DEF_GOP_SIZE ENCODER_MAX_GOP_SIZE + +static int s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hdcapm_dev *dev = container_of(ctrl->handler, + struct hdcapm_dev, ctrl_handler); + struct hdcapm_encoder_parameters *p = &dev->encoder_parameters; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_MPEG_AUDIO_MUTE: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_AUDIO_MUTE) = %d\n", + __func__, ctrl->val); + p->audio_mute = ctrl->val; + break; + case V4L2_CID_BRIGHTNESS: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_BRIGHTNESS) = %d\n", __func__, ctrl->val); + p->brightness = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_BITRATE) = %d\n", + __func__, ctrl->val); + p->bitrate_bps = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_BITRATE_PEAK) = %d\n", + __func__, ctrl->val); + p->bitrate_peak_bps = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR: + p->h264_mode = 1; + break; + case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR: + p->h264_mode = 0; + break; + default: + v4l2_err(dev->sd, + "failed to handle ctrl->id 0x%x, value = %d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_BITRATE_MODE) = %d\n", + __func__, ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_GOP_SIZE) = %d\n", + __func__, ctrl->val); + p->gop_size = ctrl->val; + + /* If we're in VBR mode GOP 1 looks bad, + * force a change to CBR. + */ + if (p->gop_size == 1 && p->h264_mode == 1) { + v4l2_info(dev->sd, + "GOP size 1 produces poor quality, switching from VBR to CBR\n"); + p->h264_mode = 0; + } + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + if (ctrl->val > V4L2_MPEG_VIDEO_H264_LEVEL_5_1) { + v4l2_err(dev->sd, + "failed to handle ctrl->id 0x%x, value = %d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + p->h264_level = ctrl->val; + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_H264_LEVEL) = %d\n", + __func__, ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC: + p->h264_entropy_mode = 1; + break; + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC: + p->h264_entropy_mode = 0; + break; + default: + v4l2_err(dev->sd, + "failed to handle ctrl->id 0x%x, value = %d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE) = %d\n", + __func__, ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + p->h264_profile = 0; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + p->h264_profile = 1; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + p->h264_profile = 2; + break; + default: + v4l2_err(dev->sd, + "failed to handle ctrl->id 0x%x, value = %d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_VIDEO_H264_PROFILE) = %d\n", + __func__, ctrl->val); + break; + case V4L2_CID_MPEG_STREAM_TYPE: + v4l2_dbg(1, hdcapm_debug, dev->sd, + "%s(V4L2_CID_MPEG_STREAM_TYPE) = %d\n", + __func__, ctrl->val); + break; + default: + v4l2_err(dev->sd, + "failed to handle ctrl->id 0x%x, value = %d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ctrl_ops = { + .s_ctrl = s_ctrl, +}; + +static int vidioc_enum_input(struct file *file, void *priv_fh, + struct v4l2_input *i) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + if (i->index > 0) + return -EINVAL; + + snprintf(i->name, sizeof(i->name), "HDMI / DVI"); + i->type = V4L2_INPUT_TYPE_CAMERA; + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + + return v4l2_subdev_call(dev->sd, video, g_input_status, &i->status); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + strcpy(cap->driver, KBUILD_MODNAME); + strlcpy(cap->card, dev->name, sizeof(cap->card)); + usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info)); + + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + struct hdcapm_statistics *s = dev->stats; + u64 q_used_bytes, q_used_items; + struct hdcapm_encoder_parameters *p = &dev->encoder_parameters; + + v4l2_info(dev->sd, "device_state: %s\n", + dev->state == STATE_START ? "START" : + dev->state == STATE_STARTED ? "STARTED" : + dev->state == STATE_STOP ? "STOP" : + dev->state == STATE_STOPPED ? "STOPPED" : "UNDEFINED"); + + v4l2_info(dev->sd, "device_context: 0x%p\n", dev); + v4l2_info(dev->sd, "codec_buffers_received: %llu\n", + s->codec_buffers_received); + v4l2_info(dev->sd, "codec_bytes_received: %llu\n", + s->codec_bytes_received); + v4l2_info(dev->sd, "codec_ts_not_yet_ready: %llu\n", + s->codec_ts_not_yet_ready); + v4l2_info(dev->sd, "buffer_overrun: %llu\n", s->buffer_overrun); + + if (p->output_width && p->output_height) + v4l2_info(dev->sd, "video_scaler_output: %dx%d\n", + p->output_width, p->output_height); + else + v4l2_info(dev->sd, "video_scaler_output: [native 1:1]\n"); + + if (hdcapm_buffer_used_queue_stats(dev, + &q_used_bytes, &q_used_items) == 0) { + v4l2_info(dev->sd, "q_used_bytes: %llu\n", + q_used_bytes); + v4l2_info(dev->sd, "q_used_items: %llu\n", + q_used_items); + } + + return v4l2_subdev_call(dev->sd, core, log_status); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + return v4l2_subdev_call(dev->sd, video, g_dv_timings, timings); +} + +static int vidioc_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + timings->pad = 0; + timings->reserved[0] = 0; + timings->reserved[1] = 0; + + return v4l2_subdev_call(dev->sd, pad, enum_dv_timings, timings); +} + +static int vidioc_dv_timings_cap(struct file *file, void *priv, + struct v4l2_dv_timings_cap *cap) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + cap->pad = 0; + + return v4l2_subdev_call(dev->sd, pad, dv_timings_cap, cap); +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + strlcpy(f->description, "MPEG", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MPEG; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + struct hdcapm_encoder_parameters *p = &dev->encoder_parameters; + struct v4l2_dv_timings timings; + + if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0) + return -EINVAL; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 312; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + if (p->output_width) + f->fmt.pix.width = p->output_width; + else + f->fmt.pix.width = timings.bt.width; + + if (p->output_height) + f->fmt.pix.height = p->output_height; + else + f->fmt.pix.height = timings.bt.width; + + f->fmt.pix.height = timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + struct hdcapm_encoder_parameters *p = &dev->encoder_parameters; + struct v4l2_dv_timings timings; + + if (v4l2_subdev_call(dev->sd, video, g_dv_timings, &timings) < 0) + return -EINVAL; + + /* Its not clear to me if the input resolution changes, if we're + * required to preserve the users requested width and height, or + * default it back to 1:1 with the input signal. + */ + p->output_width = f->fmt.pix.width; + p->output_height = f->fmt.pix.height; + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 312; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.width = timings.bt.width; + f->fmt.pix.height = timings.bt.height; + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_s_fmt_vid_cap(file, priv, f); +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 16, NULL); + default: + v4l2_warn(fh->vdev->v4l2_dev, + "event sub->type = 0x%x (UNKNOWN)\n", sub->type); + } + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static int vidioc_query_dv_timings(struct file *file, + void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + return v4l2_subdev_call(dev->sd, video, query_dv_timings, timings); +} + +static const struct v4l2_ioctl_ops mpeg_ioctl_ops = { + .vidioc_enum_input = vidioc_enum_input, + .vidioc_querycap = vidioc_querycap, + .vidioc_log_status = vidioc_log_status, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_g_dv_timings = vidioc_g_dv_timings, + .vidioc_query_dv_timings = vidioc_query_dv_timings, + .vidioc_enum_dv_timings = vidioc_enum_dv_timings, + .vidioc_dv_timings_cap = vidioc_dv_timings_cap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int fops_open(struct file *file) +{ + struct hdcapm_dev *dev; + struct hdcapm_fh *fh; + + dev = (struct hdcapm_dev *)video_get_drvdata(video_devdata(file)); + if (!dev) + return -ENODEV; + + v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) + return -ENOMEM; + + fh->dev = dev; + v4l2_fh_init(&fh->fh, video_devdata(file)); + file->private_data = &fh->fh; + v4l2_fh_add(&fh->fh); + + return 0; +} + +static int fops_release(struct file *file) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + + v4l2_dbg(2, hdcapm_debug, dev->sd, "%s()\n", __func__); + + /* Shut device down on last close */ + if ((atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) && + (atomic_dec_return(&dev->v4l_reader_count) == 0)) + /* stop mpeg capture then cancel buffers */ + hdcapm_core_stop_streaming(dev); + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + kfree(fh); + + return 0; +} + +static ssize_t fops_read(struct file *file, char __user *buffer, + size_t count, loff_t *pos) +{ + struct hdcapm_fh *fh = file->private_data; + struct hdcapm_dev *dev = fh->dev; + struct hdcapm_buffer *ubuf = NULL; + int ret = 0; + int rem, cnt; + u8 *p; + + if (*pos) { + v4l2_err(dev->sd, "%s() ESPIPE\n", __func__); + return -ESPIPE; + } + + if ((atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) && + (atomic_inc_return(&dev->v4l_reader_count) == 1)) + hdcapm_core_start_streaming(dev); + + /* blocking wait for buffer */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(dev->wait_read, + hdcapm_buffer_peek_used(dev))) { + v4l2_err(dev->sd, + "%s() ERESTARTSYS\n", __func__); + //return -ERESTARTSYS; + return -EINVAL; + } + } + + /* Pull the first buffer from the used list */ + ubuf = hdcapm_buffer_peek_used(dev); + + while ((count > 0) && ubuf) { + /* set remaining bytes to copy */ + rem = ubuf->actual_size - ubuf->readpos; + cnt = rem > count ? count : rem; + + p = ubuf->ptr + ubuf->readpos; + + v4l2_dbg(3, hdcapm_debug, dev->sd, + "%s() nr=%d count=%d cnt=%d rem=%d buf=%p buf->readpos=%d\n", + __func__, ubuf->nr, (int)count, + cnt, rem, ubuf, ubuf->readpos); + + if (copy_to_user(buffer, p, cnt)) { + v4l2_err(dev->sd, + "%s() copy_to_user failed\n", __func__); + if (!ret) { + v4l2_err(dev->sd, "%s() EFAULT\n", __func__); + ret = -EFAULT; + } + goto err; + } + + ubuf->readpos += cnt; + count -= cnt; + buffer += cnt; + ret += cnt; + + if (ubuf->readpos > ubuf->actual_size) + v4l2_err(dev->sd, "read() pos > actual, huh?\n"); + + if (ubuf->readpos == ubuf->actual_size) { + /* finished with current buffer, take next buffer */ + + /* Requeue the buffer on the free list */ + ubuf->readpos = 0; + + hdcapm_buffer_move_to_free(dev, ubuf); + + /* Dequeue next */ + if ((file->f_flags & O_NONBLOCK) == 0) { + if (wait_event_interruptible(dev->wait_read, + hdcapm_buffer_peek_used(dev))) + break; + } + ubuf = hdcapm_buffer_peek_used(dev); + } + } +err: + if (!ret && !ubuf) + ret = -EAGAIN; + + return ret; +} + +static unsigned int fops_poll(struct file *file, poll_table *wait) +{ + unsigned long req_events = poll_requested_events(wait); + struct hdcapm_fh *fh = (struct hdcapm_fh *)file->private_data; + struct hdcapm_dev *dev = fh->dev; + unsigned int mask = v4l2_ctrl_poll(file, wait); + + if (!(req_events & (POLLIN | POLLRDNORM))) + return mask; + + if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) { + if (atomic_inc_return(&dev->v4l_reader_count) == 1) + hdcapm_core_start_streaming(dev); + } + + /* Pull the first buffer from the used list */ + if (!list_empty(&dev->list_buf_used)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct v4l2_file_operations mpeg_fops = { + .owner = THIS_MODULE, + .open = fops_open, + .release = fops_release, + .read = fops_read, + .poll = fops_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static struct video_device mpeg_template = { + .name = "hdcapm", + .fops = &mpeg_fops, + .ioctl_ops = &mpeg_ioctl_ops, + .minor = -1, +}; + +int hdcapm_video_register(struct hdcapm_dev *dev) +{ + struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; + int ret; + + v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__); + + /* Any video controls. */ + + dev->v4l_device = video_device_alloc(); + if (!dev->v4l_device) + return -EINVAL; + + /* Configure the V4L2 device properties */ + *dev->v4l_device = mpeg_template; + snprintf(dev->v4l_device->name, sizeof(dev->v4l_device->name), + "%s %s (%s)", dev->name, "mpeg", dev->name); + dev->v4l_device->v4l2_dev = &dev->v4l2_dev; + dev->v4l_device->release = video_device_release; + + v4l2_ctrl_handler_init(hdl, 14); + dev->v4l_device->ctrl_handler = hdl; + + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_BRIGHTNESS, 0, 255, 1, 127); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE, + ENCODER_MIN_GOP_SIZE, + ENCODER_MAX_GOP_SIZE, 1, ENCODER_DEF_GOP_SIZE); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE, + ENCODER_MIN_BITRATE, + ENCODER_MAX_BITRATE, 100000, ENCODER_DEF_BITRATE); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + ENCODER_MIN_BITRATE, + ENCODER_MAX_BITRATE, 100000, ENCODER_DEF_BITRATE); + + v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + + v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + + v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + 0, V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC); + + v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + + v4l2_ctrl_new_std_menu(hdl, &ctrl_ops, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_MPEG_STREAM_TYPE_MPEG2_TS, + ~(1 << V4L2_MPEG_STREAM_TYPE_MPEG2_TS), + V4L2_MPEG_STREAM_TYPE_MPEG2_TS); + + /* Establish all default control values. */ + v4l2_ctrl_handler_setup(hdl); + + video_set_drvdata(dev->v4l_device, dev); + ret = video_register_device(dev->v4l_device, VFL_TYPE_GRABBER, -1); + if (ret < 0) + goto fail1; + + v4l2_info(dev->sd, + "registered device video%d [mpeg]\n", dev->v4l_device->num); + + ret = 0; /* Success */ + +fail1: + return ret; +} + +void hdcapm_video_unregister(struct hdcapm_dev *dev) +{ + v4l2_dbg(1, hdcapm_debug, dev->sd, "%s()\n", __func__); + + if (dev->v4l_device) { + if (dev->v4l_device->minor != -1) + video_unregister_device(dev->v4l_device); + else + video_device_release(dev->v4l_device); + + dev->v4l_device = NULL; + } + v4l2_ctrl_handler_free(&dev->ctrl_handler); +} diff --git a/drivers/media/usb/hdcapm/hdcapm.h b/drivers/media/usb/hdcapm/hdcapm.h new file mode 100644 index 000000000000..d2a0e820c3eb --- /dev/null +++ b/drivers/media/usb/hdcapm/hdcapm.h @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for the Startech USB2HDCAPM USB capture device + * + * Copyright (c) 2017 Steven Toth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + */ + +#ifndef _HDCAPM_H +#define _HDCAPM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hdcapm-reg.h" + +extern int hdcapm_i2c_scan; +extern int hdcapm_debug; + +#define HDCAPM_CARD_REV1 1 + +#define PIPE_EP1 0x01 +#define PIPE_EP2 0x02 +#define PIPE_EP3 0x83 +#define PIPE_EP4 0x04 + +/* The scheduler on ARM/RDU2 uses a different quanta, probably 20ms. + * on X86 its 10. This skews our hard timing when polling the + * codec memory levels (and downloading payload). + * SOme discussion was had re hires timers for ARM and how they may + * be more accurate. + * They're not, they just trade CPU cycles for more accurate timing. + * Enable this to evaluate ARM hires timers and look for the histogram + * results in the --log-status output, they show how accurate the + * kernel is for certain requested sleep intervals. + */ +#define TIMER_EVAL 0 + +/* The driver started development by loading the firmware once + * during startup, unlike the windows driver that loads the + * firmware before every capture session. (ONETIME = 1). + * During later development, we found that if we don't load the + * firmware before ever capture, we lose audio in the second + * and subsequent capture. + * With ONETIME = 0 we load the firm whenever a capture starts. + */ +#define ONETIME_FW_LOAD 0 + +extern struct usb_device_id hdcapm_usb_id_table[]; + +struct hdcapm_dev; +struct hdcapm_statistics; + +enum transition_state_e { + STATE_UNDEFINED = 0, + STATE_START, /* V4L2 read() or poll() advanced to _START state. */ + + STATE_STARTED, /* kernel thread notices _START state, starts + * the firmware and moves state to _STARTED. + */ + STATE_STOP, /* V4L2 close advances from _STARTED to _STOP. */ + + STATE_STOPPED, /* kernel thread notices _STOPPING, stops + * firmware and moves to STOPPED state. + */ +}; + +struct hdcapm_encoder_parameters { + /* TODO: Mostly all todo items. */ + u32 audio_mute; + u32 brightness; + u32 bitrate_bps; + u32 bitrate_peak_bps; + u32 bitrate_mode; + u32 gop_size; + + u32 h264_profile; /* H264 profile BASELINE etc */ + u32 h264_level; /* H264 profile 4.1 etc */ + u32 h264_entropy_mode; /* CABAC = 1 / CAVLC = 0 */ + u32 h264_mode; /* VBR = 1, CBR = 0 */ + + /* Typically these map 1:1 to the detected timing + * resolution, but these could be modified bu + * s_fmt to invoke the hardware video scaler. + */ + u32 output_width; + u32 output_height; +}; + +struct hdcapm_fh { + struct v4l2_fh fh; + struct hdcapm_dev *dev; + atomic_t v4l_reading; +}; + +struct hdcapm_i2c_bus { + struct hdcapm_dev *dev; + int nr; + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + struct i2c_algo_bit_data i2c_algo; +}; + +struct hdcapm_dev { + struct list_head devlist; + + char name[32]; + + enum transition_state_e state; + int thread_active; + struct task_struct *kthread; + + struct hdcapm_statistics *stats; + + /* Held by the follow driver features. + * 1. During probe and disconnect. + * 2. When writing commands to the firmware. + */ + struct mutex lock; + + struct usb_device *udev; + + /* We need to xfer USB buffers off the stack, put them here. */ + u8 *xferbuf; + u32 xferbuf_len; + + /* I2C. + * Bus0 - MST3367. + * Bus1 - Sonix chip. + */ + struct hdcapm_i2c_bus i2cbus[2]; + //struct i2c_client *i2c_client_hdmi; + struct v4l2_subdev *sd; + + /* V4L2 */ + struct v4l2_device v4l2_dev; + struct video_device *v4l_device; + struct v4l2_ctrl_handler ctrl_handler; + atomic_t v4l_reader_count; + struct hdcapm_encoder_parameters encoder_parameters; +#if TIMER_EVAL + struct timer_list ktimer; + struct hrtimer hrtimer; +#endif + + /* User buffering */ + struct mutex dmaqueue_lock; + struct list_head list_buf_free; + struct list_head list_buf_used; + wait_queue_head_t wait_read; +}; + +struct hdcapm_buffer { + struct list_head list; + + int nr; + struct hdcapm_dev *dev; + struct urb *urb; + + u8 *ptr; + u32 maxsize; + u32 actual_size; + u32 readpos; +}; + +struct hdcapm_statistics { + /* Number of times the driver stole a used buffer to satisfy a + * free buffer streaming request. + */ + u64 buffer_overrun; + + /* The amount of data we've received from the firmware + * (video/audio codec data). + */ + u64 codec_bytes_received; + + /* The number of buffers we're received full of codec data + * (video/audio codec data). + */ + u64 codec_buffers_received; + + /* Any time we call the codec to check for a TS buffer, and it + * replies that it doesn't yet have one. + */ + u64 codec_ts_not_yet_ready; +}; + +/* -core.c */ +int hdcapm_write32(struct hdcapm_dev *dev, u32 addr, u32 val); +int hdcapm_read32(struct hdcapm_dev *dev, u32 addr, u32 *val); + +/* Read N DWORDS from the firmware and optionally convert the LE + * firmware dwords to platform CPU DWORDS. + */ +int hdcapm_read32_array(struct hdcapm_dev *dev, u32 addr, u32 wordcount, + u32 *arr, int le_to_cpu); + +void hdcapm_set32(struct hdcapm_dev *dev, u32 addr, u32 mask); +void hdcapm_clr32(struct hdcapm_dev *dev, u32 addr, u32 mask); + +int hdcapm_dmawrite32(struct hdcapm_dev *dev, u32 addr, const u32 *arr, + u32 entries); +int hdcapm_dmaread32(struct hdcapm_dev *dev, u32 addr, u32 *arr, u32 entries); +int hdcapm_mem_write32(struct hdcapm_dev *dev, u32 addr, u32 val); +int hdcapm_mem_read32(struct hdcapm_dev *dev, u32 addr, u32 *val); + +int hdcapm_core_ep_send(struct hdcapm_dev *dev, int endpoint, u8 *buf, + u32 len, u32 timeout); + +int hdcapm_core_ep_recv(struct hdcapm_dev *dev, int endpoint, u8 *buf, + u32 len, u32 *actual, u32 timeout); + +int hdcapm_core_stop_streaming(struct hdcapm_dev *dev); +int hdcapm_core_start_streaming(struct hdcapm_dev *dev); + +/* -i2c.c */ +int hdcapm_i2c_register(struct hdcapm_dev *dev, + struct hdcapm_i2c_bus *bus, int nr); +void hdcapm_i2c_unregister(struct hdcapm_dev *dev, + struct hdcapm_i2c_bus *bus); + +/* -buffer.c */ +struct hdcapm_buffer *hdcapm_buffer_alloc(struct hdcapm_dev *dev, + u32 nr, u32 maxsize); + +void hdcapm_buffer_free(struct hdcapm_buffer *buf); +void hdcapm_buffers_move_all(struct hdcapm_dev *dev, struct list_head *to, + struct list_head *from); +void hdcapm_buffers_free_all(struct hdcapm_dev *dev, struct list_head *head); +struct hdcapm_buffer *hdcapm_buffer_next_free(struct hdcapm_dev *dev); +struct hdcapm_buffer *hdcapm_buffer_peek_used(struct hdcapm_dev *dev); +void hdcapm_buffer_move_to_free(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf); +void hdcapm_buffer_move_to_used(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf); +void hdcapm_buffer_add_to_free(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf); +void hdcapm_buffer_add_to_used(struct hdcapm_dev *dev, + struct hdcapm_buffer *buf); +int hdcapm_buffer_used_queue_stats(struct hdcapm_dev *dev, + u64 *bytes, u64 *items); + +/* -compressor.c */ +int hdcapm_compressor_register(struct hdcapm_dev *dev); +void hdcapm_compressor_unregister(struct hdcapm_dev *dev); +void hdcapm_compressor_run(struct hdcapm_dev *dev); +void hdcapm_compressor_init_gpios(struct hdcapm_dev *dev); + +/* -video.c */ +int hdcapm_video_register(struct hdcapm_dev *dev); +void hdcapm_video_unregister(struct hdcapm_dev *dev); + +#endif /* _HDCAPM_H */