From 9c53d3c4191d0f69adacfb6015952696c4fb8272 Mon Sep 17 00:00:00 2001 From: jmcneill Date: Mon, 11 Jul 2011 18:00:06 +0000 Subject: [PATCH] add LGDT3303 tuner and XC3028L demod drivers --- sys/dev/i2c/files.i2c | 12 +- sys/dev/i2c/lg3303.c | 359 +++++++++++++++++++++++++ sys/dev/i2c/lg3303var.h | 48 ++++ sys/dev/i2c/xc3028.c | 582 ++++++++++++++++++++++++++++++++++++++++ sys/dev/i2c/xc3028reg.h | 41 +++ sys/dev/i2c/xc3028var.h | 70 +++++ 6 files changed, 1110 insertions(+), 2 deletions(-) create mode 100644 sys/dev/i2c/lg3303.c create mode 100644 sys/dev/i2c/lg3303var.h create mode 100644 sys/dev/i2c/xc3028.c create mode 100644 sys/dev/i2c/xc3028reg.h create mode 100644 sys/dev/i2c/xc3028var.h diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c index 151b5803d781..8bd20c29332c 100644 --- a/sys/dev/i2c/files.i2c +++ b/sys/dev/i2c/files.i2c @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.36 2011/07/11 00:30:23 jakllsch Exp $ +# $NetBSD: files.i2c,v 1.37 2011/07/11 18:00:06 jmcneill Exp $ defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -17,8 +17,16 @@ file dev/i2c/i2c_bitbang.c i2c_bitbang define au8522: i2cexec file dev/i2c/au8522.c au8522 +# LG DT3303 decoder +define lg3303: i2cexec +file dev/i2c/lg3303.c lg3303 + +# Xceive XC3028 tuner +define xc3028: i2cexec, firmload +file dev/i2c/xc3028.c xc3028 + # Xceive XC5000 tuner -define xc5k: i2cexec +define xc5k: i2cexec, firmload file dev/i2c/xc5k.c xc5k # Generic PLL-based tuners diff --git a/sys/dev/i2c/lg3303.c b/sys/dev/i2c/lg3303.c new file mode 100644 index 000000000000..f888ef5daa47 --- /dev/null +++ b/sys/dev/i2c/lg3303.c @@ -0,0 +1,359 @@ +/* $NetBSD: lg3303.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $ */ + +/*- + * Copyright 2007 Jason Harmening + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__KERNEL_RCSID(0, "$NetBSD: lg3303.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $"); + +#include +#include +#include + +#include +#include +#include + +#define REG_TOP_CONTROL 0x00 +#define REG_IRQ_MASK 0x01 +#define REG_IRQ_STATUS 0x02 +#define REG_VSB_CARRIER_FREQ0 0x16 +#define REG_VSB_CARRIER_FREQ1 0x17 +#define REG_VSB_CARRIER_FREQ2 0x18 +#define REG_VSB_CARRIER_FREQ3 0x19 +#define REG_CARRIER_MSEQAM1 0x1a +#define REG_CARRIER_MSEQAM2 0x1b +#define REG_CARRIER_LOCK 0x1c +#define REG_TIMING_RECOVERY 0x1d +#define REG_AGC_DELAY0 0x2a +#define REG_AGC_DELAY1 0x2b +#define REG_AGC_DELAY2 0x2c +#define REG_AGC_RF_BANDWIDTH0 0x2d +#define REG_AGC_RF_BANDWIDTH1 0x2e +#define REG_AGC_RF_BANDWIDTH2 0x2f +#define REG_AGC_LOOP_BANDWIDTH0 0x30 +#define REG_AGC_LOOP_BANDWIDTH1 0x31 +#define REG_AGC_FUNC_CTRL1 0x32 +#define REG_AGC_FUNC_CTRL2 0x33 +#define REG_AGC_FUNC_CTRL3 0x34 +#define REG_AGC_RFIF_ACC0 0x39 +#define REG_AGC_RFIF_ACC1 0x3a +#define REG_AGC_RFIF_ACC2 0x3b +#define REG_AGC_STATUS 0x3f +#define REG_SYNC_STATUS_VSB 0x43 +#define REG_DEMUX_CONTROL 0x66 +#define REG_EQPH_ERR0 0x6e +#define REG_EQ_ERR1 0x6f +#define REG_EQ_ERR2 0x70 +#define REG_PH_ERR1 0x71 +#define REG_PH_ERR2 0x72 +#define REG_PACKET_ERR_COUNTER1 0x8b +#define REG_PACKET_ERR_COUNTER2 0x8c + +#define LG3303_DEFAULT_DELAY 250000 + +static int lg3303_reset(struct lg3303 *); +static int lg3303_init(struct lg3303 *); + +struct lg3303 * +lg3303_open(device_t parent, i2c_tag_t i2c, i2c_addr_t addr) +{ + struct lg3303 *lg; + + lg = kmem_alloc(sizeof(*lg), KM_SLEEP); + if (lg == NULL) + return NULL; + lg->parent = parent; + lg->i2c = i2c; + lg->i2c_addr = addr; + lg->current_modulation = -1; + + if (lg3303_init(lg) != 0) { + kmem_free(lg, sizeof(*lg)); + return NULL; + } + + device_printf(lg->parent, "lg3303: found @ 0x%02x\n", addr); + + return lg; +} + +void +lg3303_close(struct lg3303 *lg) +{ + kmem_free(lg, sizeof(*lg)); +} + +static int +lg3303_write(struct lg3303 *lg, uint8_t *buf, size_t len) +{ + unsigned int i; + uint8_t *p = buf; + int error; + + for (i = 0; i < len - 1; i += 2) { + error = iic_exec(lg->i2c, I2C_OP_WRITE_WITH_STOP, lg->i2c_addr, + p, 2, NULL, 0, 0); + if (error) + return error; + p += 2; + } + + return 0; +} + +static int +lg3303_read(struct lg3303 *lg, uint8_t reg, uint8_t *buf, size_t len) +{ + int error; + + error = iic_exec(lg->i2c, I2C_OP_WRITE, lg->i2c_addr, + ®, sizeof(reg), NULL, 0, 0); + if (error) + return error; + return iic_exec(lg->i2c, I2C_OP_READ, lg->i2c_addr, + NULL, 0, buf, len, 0); +} + +static int +lg3303_reset(struct lg3303 *lg) +{ + uint8_t buffer[] = {REG_IRQ_STATUS, 0x00}; + int error = lg3303_write(lg, buffer, 2); + if (error == 0) { + buffer[1] = 0x01; + error = lg3303_write(lg, buffer, 2); + } + return error; +} + +static int +lg3303_init(struct lg3303 *lg) +{ + //static uint8_t init_data[] = {0x4c, 0x14, 0x87, 0xf3}; + static uint8_t init_data[] = {0x4c, 0x14}; + size_t len; + int error; + +#if notyet + if (clock_polarity == DVB_IFC_POS_POL) + len = 4; + else +#endif + len = 2; + + error = lg3303_write(lg, init_data, len); + if (error == 0) + lg3303_reset(lg); + + return error; +} + +int +lg3303_set_modulation(struct lg3303 *lg, fe_modulation_t modulation) +{ + int error; + static uint8_t vsb_data[] = { + 0x04, 0x00, + 0x0d, 0x40, + 0x0e, 0x87, + 0x0f, 0x8e, + 0x10, 0x01, + 0x47, 0x8b + }; + static uint8_t qam_data[] = { + 0x04, 0x00, + 0x0d, 0x00, + 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, + 0x51, 0x63, + 0x47, 0x66, + 0x48, 0x66, + 0x4d, 0x1a, + 0x49, 0x08, + 0x4a, 0x9b + }; + + error = lg3303_reset(lg); + if (error) + return error; + + if (lg->current_modulation != modulation) { + uint8_t top_ctrl[] = {REG_TOP_CONTROL, 0x00}; +#if 0 + if (m_input == DVB_INPUT_SERIAL) + top_ctrl[1] = 0x40; +#endif + + switch (modulation) { + case VSB_8: + top_ctrl[1] |= 0x03; + error = lg3303_write(lg, vsb_data, sizeof(vsb_data)); + if (error) + return error; + break; + case QAM_256: + top_ctrl[1] |= 0x01; + /* FALLTHROUGH */ + case QAM_64: + error = lg3303_write(lg, qam_data, sizeof(qam_data)); + if (error) + return error; + break; + default: + device_printf(lg->parent, + "lg3303: unsupported modulation type (%d)\n", + modulation); + return EINVAL; + } + error = lg3303_write(lg, top_ctrl, sizeof(top_ctrl)); + if (error) + return error; + lg->current_modulation = modulation; + lg3303_reset(lg); + } + + return error; +} + +fe_status_t +lg3303_get_dtv_status(struct lg3303 *lg) +{ + uint8_t reg = 0, value = 0x00; + fe_status_t festatus = 0; + int error = 0; + + error = lg3303_read(lg, 0x58, &value, sizeof(value)); + if (error) + return 0; + + if (value & 0x01) + festatus |= FE_HAS_SIGNAL; + + error = lg3303_read(lg, REG_CARRIER_LOCK, &value, sizeof(value)); + if (error) + return 0; + + switch (lg->current_modulation) { + case VSB_8: + if (value & 0x80) + festatus |= FE_HAS_CARRIER; + reg = 0x38; + break; + case QAM_64: + case QAM_256: + if ((value & 0x07) == 0x07) + festatus |= FE_HAS_CARRIER; + reg = 0x8a; + break; + default: + device_printf(lg->parent, + "lg3303: unsupported modulation type (%d)\n", + lg->current_modulation); + return EINVAL; + } + + error = lg3303_read(lg, reg, &value, sizeof(value)); + if (!error && (value & 0x01)) + festatus |= FE_HAS_LOCK; + + if (festatus & FE_HAS_LOCK) + festatus |= (FE_HAS_SYNC | FE_HAS_VITERBI); + + return festatus; +} + +#if notyet +int lg3303::get_signal(dvb_signal &signal) +{ + int error = check_for_lock(signal.locked); + uint32_t noise, snr_const; + uint8_t buffer[5]; + uint8_t reg; + if (error || !signal.locked) + { + return error; + } + signal.ber = 0; + switch(m_modulation) + { + case DVB_MOD_VSB_8: + reg = REG_EQPH_ERR0; + if ((error = m_device.transact(®, sizeof(reg), buffer, sizeof(buffer)))) + { + LIBTUNERERR << "LG3303: Unable to retrieve 8-VSB noise value" << endl; + return error; + } + noise = ((buffer[0] & 7) << 16) | (buffer[3] << 8) | buffer[4]; + snr_const = 25600; + break; + case DVB_MOD_QAM_64: + case DVB_MOD_QAM_256: + reg = REG_CARRIER_MSEQAM1; + if ((error = m_device.transact(®, sizeof(reg), buffer, 2))) + { + LIBTUNERERR << "LG3303: Unable to retrieve QAM noise value" << endl; + return error; + } + noise = (buffer[0] << 8) | buffer[1]; + if (m_modulation == DVB_MOD_QAM_64) + { + snr_const = 688128; + } + else + { + snr_const = 696320; + } + break; + default: + LIBTUNERERR << "LG3303: Unsupported modulation type" << endl; + return EINVAL; + } + signal.snr = 10.0 * log10((double)snr_const / noise); + signal.strength = (signal.snr / 35) * 100; + reg = REG_PACKET_ERR_COUNTER1; + if ((error = m_device.transact(®, sizeof(reg), buffer, 2))) + { + LIBTUNERERR << "LG3303: Unable to retrieve packet error count" << endl; + return error; + } + signal.uncorrected_blocks = (buffer[0] << 8) | buffer[1]; + return 0; +} +#endif + + +MODULE(MODULE_CLASS_DRIVER, lg3303, NULL); + +static int +lg3303_modcmd(modcmd_t cmd, void *opaque) +{ + if (cmd == MODULE_CMD_INIT || cmd == MODULE_CMD_FINI) + return 0; + return ENOTTY; +} diff --git a/sys/dev/i2c/lg3303var.h b/sys/dev/i2c/lg3303var.h new file mode 100644 index 000000000000..b8ba5a28d78b --- /dev/null +++ b/sys/dev/i2c/lg3303var.h @@ -0,0 +1,48 @@ +/* $NetBSD: lg3303var.h,v 1.1 2011/07/11 18:00:06 jmcneill Exp $ */ + +/*- + * Copyright (c) 2011 Jared D. McNeill + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _LG3303VAR_H +#define _LG3303VAR_H + +#include +#include + +struct lg3303 { + device_t parent; + i2c_tag_t i2c; + i2c_addr_t i2c_addr; + + fe_modulation_t current_modulation; +}; + +struct lg3303 * lg3303_open(device_t, i2c_tag_t, i2c_addr_t); +void lg3303_close(struct lg3303 *); +int lg3303_set_modulation(struct lg3303 *, fe_modulation_t); +fe_status_t lg3303_get_dtv_status(struct lg3303 *); + +#endif /* !_LG3303VAR_H */ diff --git a/sys/dev/i2c/xc3028.c b/sys/dev/i2c/xc3028.c new file mode 100644 index 000000000000..2b9e094fb5f9 --- /dev/null +++ b/sys/dev/i2c/xc3028.c @@ -0,0 +1,582 @@ +/* $NetBSD: xc3028.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $ */ + +/*- + * Copyright (c) 2011 Jared D. McNeill + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Xceive XC3028L + */ + +#include +__KERNEL_RCSID(0, "$NetBSD: xc3028.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#define XC3028_FIRMWARE_DRVNAME "xc3028" + +#define XC3028_FREQ_MIN 1000000 +#define XC3028_FREQ_MAX 1023000000 + +#define XC3028_FW_BASE (1 << 0) +#define XC3028_FW_D2633 (1 << 4) +#define XC3028_FW_DTV6 (1 << 5) +#define XC3028_FW_QAM (1 << 6) +#define XC3028_FW_ATSC (1 << 16) +#define XC3028_FW_LG60 (1 << 18) +#define XC3028_FW_F6MHZ (1 << 27) +#define XC3028_FW_SCODE (1 << 29) +#define XC3028_FW_HAS_IF (1 << 30) + +#define XC3028_FW_DEFAULT (XC3028_FW_ATSC|XC3028_FW_D2633|XC3028_FW_DTV6) + +static kmutex_t xc3028_firmware_lock; + +static int xc3028_reset(struct xc3028 *); +static int xc3028_read_2(struct xc3028 *, uint16_t, uint16_t *); +static int xc3028_write_buffer(struct xc3028 *, const uint8_t *, size_t); +static int xc3028_firmware_open(struct xc3028 *); +static int xc3028_firmware_parse(struct xc3028 *, const uint8_t *, size_t); +static int xc3028_firmware_upload(struct xc3028 *, struct xc3028_fw *); +static int xc3028_scode_upload(struct xc3028 *, struct xc3028_fw *); +static void xc3028_dump_fw(struct xc3028 *, struct xc3028_fw *, + const char *); + +static const char * +xc3028_name(struct xc3028 *xc) +{ + if (xc->type == XC3028L) + return "xc3028l"; + else + return "xc3028"; +} + +static const char * +xc3028_firmware_name(struct xc3028 *xc) +{ + if (xc->type == XC3028L) + return "xc3028L-v36.fw"; + else + return "xc3028-v27.fw"; +} + +static int +xc3028_reset(struct xc3028 *xc) +{ + int error = 0; + + if (xc->reset) + error = xc->reset(xc->reset_priv); + + return error; +} + +static struct xc3028_fw * +xc3028_get_basefw(struct xc3028 *xc) +{ + struct xc3028_fw *fw; + unsigned int i; + + for (i = 0; i < xc->nfw; i++) { + fw = &xc->fw[i]; + if (fw->type == XC3028_FW_BASE) + return fw; + } + + return NULL; +} + +static struct xc3028_fw * +xc3028_get_stdfw(struct xc3028 *xc) +{ + struct xc3028_fw *fw; + unsigned int i; + + for (i = 0; i < xc->nfw; i++) { + fw = &xc->fw[i]; + if (fw->type == (XC3028_FW_D2633|XC3028_FW_DTV6|XC3028_FW_ATSC)) + return fw; + } + + return NULL; +} + +static struct xc3028_fw * +xc3028_get_scode(struct xc3028 *xc) +{ + struct xc3028_fw *fw; + unsigned int i; + + for (i = 0; i < xc->nfw; i++) { + fw = &xc->fw[i]; + if (fw->type == + (XC3028_FW_DTV6|XC3028_FW_QAM|XC3028_FW_ATSC|XC3028_FW_LG60| + XC3028_FW_F6MHZ|XC3028_FW_SCODE|XC3028_FW_HAS_IF) && + fw->int_freq == 6200) + return fw; + } + + return NULL; +} + +static int +xc3028_firmware_open(struct xc3028 *xc) +{ + firmware_handle_t fwh; + struct xc3028_fw *basefw, *stdfw, *scode; + uint8_t *fw = NULL; + uint16_t xcversion = 0; + size_t fwlen; + int error; + + mutex_enter(&xc3028_firmware_lock); + + error = firmware_open(XC3028_FIRMWARE_DRVNAME, + xc3028_firmware_name(xc), &fwh); + if (error) + goto done; + fwlen = firmware_get_size(fwh); + fw = firmware_malloc(fwlen); + if (fw == NULL) { + firmware_close(fwh); + error = ENOMEM; + goto done; + } + error = firmware_read(fwh, 0, fw, fwlen); + firmware_close(fwh); + if (error) + goto done; + + device_printf(xc->parent, "%s: loading firmware '%s/%s'\n", + xc3028_name(xc), XC3028_FIRMWARE_DRVNAME, xc3028_firmware_name(xc)); + error = xc3028_firmware_parse(xc, fw, fwlen); + if (!error) { + basefw = xc3028_get_basefw(xc); + stdfw = xc3028_get_stdfw(xc); + scode = xc3028_get_scode(xc); + if (basefw && stdfw) { + xc3028_reset(xc); + xc3028_dump_fw(xc, basefw, "base"); + error = xc3028_firmware_upload(xc, basefw); + if (error) + return error; + xc3028_dump_fw(xc, stdfw, "std"); + error = xc3028_firmware_upload(xc, stdfw); + if (error) + return error; + if (scode) { + xc3028_dump_fw(xc, scode, "scode"); + error = xc3028_scode_upload(xc, scode); + if (error) + return error; + } + } else + error = ENODEV; + } + if (!error) { + xc3028_read_2(xc, XC3028_REG_VERSION, &xcversion); + + device_printf(xc->parent, "%s: hw %d.%d, fw %d.%d\n", + xc3028_name(xc), + (xcversion >> 12) & 0xf, (xcversion >> 8) & 0xf, + (xcversion >> 4) & 0xf, (xcversion >> 0) & 0xf); + } + +done: + if (fw) + firmware_free(fw, 0); + mutex_exit(&xc3028_firmware_lock); + + if (error) + aprint_error_dev(xc->parent, + "%s: couldn't open firmware '%s/%s' (error=%d)\n", + xc3028_name(xc), XC3028_FIRMWARE_DRVNAME, + xc3028_firmware_name(xc), error); + + return error; +} + +static const char *xc3028_fw_types[] = { + "BASE", + "F8MHZ", + "MTS", + "D2620", + "D2633", + "DTV6", + "QAM", + "DTV7", + "DTV78", + "DTV8", + "FM", + "INPUT1", + "LCD", + "NOGD", + "INIT1", + "MONO", + "ATSC", + "IF", + "LG60", + "ATI638", + "OREN538", + "OREN36", + "TOYOTA388", + "TOYOTA794", + "DIBCOM52", + "ZARLINK456", + "CHINA", + "F6MHZ", + "INPUT2", + "SCODE", + "HAS_IF", +}; + +static void +xc3028_dump_fw(struct xc3028 *xc, struct xc3028_fw *xcfw, const char *type) +{ + unsigned int i; + + device_printf(xc->parent, "%s: %s:", xc3028_name(xc), type); + if (xcfw == NULL) { + printf(" \n"); + return; + } + for (i = 0; i < __arraycount(xc3028_fw_types); i++) { + if (xcfw->type & (1 << i)) + printf(" %s", xc3028_fw_types[i]); + } + if (xcfw->type & (1 << 30)) + printf("_%d", xcfw->int_freq); + if (xcfw->id) + printf(" id=%llx", xcfw->id); + printf(" size=%u\n", xcfw->data_size); +} + +static int +xc3028_firmware_parse(struct xc3028 *xc, const uint8_t *fw, size_t fwlen) +{ + const uint8_t *p = fw, *endp = p + fwlen; + char fwname[32 + 1]; + uint16_t fwver, narr; + unsigned int index; + struct xc3028_fw *xcfw; + + if (fwlen < 36) + return EINVAL; + + /* first 32 bytes are the firmware name string */ + memset(fwname, 0, sizeof(fwname)); + memcpy(fwname, p, sizeof(fwname) - 1); + p += (sizeof(fwname) - 1); + + fwver = le16dec(p); + p += sizeof(fwver); + narr = le16dec(p); + p += sizeof(narr); + + aprint_debug_dev(xc->parent, "%s: fw type %s, ver %d.%d, %d images\n", + xc3028_name(xc), fwname, fwver >> 8, fwver & 0xff, narr); + + xc->fw = kmem_zalloc(sizeof(*xc->fw) * narr, KM_SLEEP); + if (xc->fw == NULL) + return ENOMEM; + xc->nfw = narr; + + for (index = 0; index < xc->nfw && p < endp; index++) { + xcfw = &xc->fw[index]; + + if (endp - p < 16) + goto corrupt; + + xcfw->type = le32dec(p); + p += sizeof(xcfw->type); + + xcfw->id = le64dec(p); + p += sizeof(xcfw->id); + + if (xcfw->type & XC3028_FW_HAS_IF) { + xcfw->int_freq = le16dec(p); + p += sizeof(xcfw->int_freq); + if ((uint32_t)(endp - p) < sizeof(xcfw->data_size)) + goto corrupt; + } + + xcfw->data_size = le32dec(p); + p += sizeof(xcfw->data_size); + + if (xcfw->data_size == 0 || + xcfw->data_size > (uint32_t)(endp - p)) + goto corrupt; + xcfw->data = kmem_alloc(xcfw->data_size, KM_SLEEP); + if (xcfw->data == NULL) + goto corrupt; + memcpy(xcfw->data, p, xcfw->data_size); + p += xcfw->data_size; + } + + return 0; + +corrupt: + aprint_error_dev(xc->parent, "%s: fw image corrupt\n", xc3028_name(xc)); + for (index = 0; index < xc->nfw; index++) { + if (xc->fw[index].data) + kmem_free(xc->fw[index].data, xc->fw[index].data_size); + } + kmem_free(xc->fw, sizeof(*xc->fw) * xc->nfw); + xc->nfw = 0; + + return ENXIO; +} + +static int +xc3028_firmware_upload(struct xc3028 *xc, struct xc3028_fw *xcfw) +{ + const uint8_t *fw = xcfw->data, *p; + uint32_t fwlen = xcfw->data_size; + uint8_t cmd[64]; + unsigned int i; + uint16_t len, rem; + size_t wrlen; + int error; + + for (i = 0; i < fwlen - 2;) { + len = le16dec(&fw[i]); + i += 2; + if (len == 0xffff) + break; + + /* reset command */ + if (len == 0x0000) { + error = xc3028_reset(xc); + if (error) + return error; + continue; + } + /* reset clk command */ + if (len == 0xff00) { + continue; + } + /* delay command */ + if (len & 0x8000) { + delay((len & 0x7fff) * 1000); + continue; + } + + if (i + len > fwlen) { + printf("weird len, i=%u len=%u fwlen=%u'\n", i, len, fwlen); + return EINVAL; + } + + cmd[0] = fw[i]; + p = &fw[i + 1]; + rem = len - 1; + while (rem > 0) { + wrlen = min(rem, __arraycount(cmd) - 1); + memcpy(&cmd[1], p, wrlen); + error = xc3028_write_buffer(xc, cmd, wrlen + 1); + if (error) + return error; + p += wrlen; + rem -= wrlen; + } + i += len; + } + + return 0; +} + +static int +xc3028_scode_upload(struct xc3028 *xc, struct xc3028_fw *xcfw) +{ + static uint8_t scode_init[] = { 0xa0, 0x00, 0x00, 0x00 }; + static uint8_t scode_fini[] = { 0x00, 0x8c }; + int error; + + if (xcfw->data_size < 12) + return EINVAL; + error = xc3028_write_buffer(xc, scode_init, sizeof(scode_init)); + if (error) + return error; + error = xc3028_write_buffer(xc, xcfw->data, 12); + if (error) + return error; + error = xc3028_write_buffer(xc, scode_fini, sizeof(scode_fini)); + if (error) + return error; + + return 0; +} + +static int +xc3028_read_2(struct xc3028 *xc, uint16_t reg, uint16_t *val) +{ + uint8_t cmd[2], resp[2]; + int error; + + cmd[0] = reg >> 8; + cmd[1] = reg & 0xff; + error = iic_exec(xc->i2c, I2C_OP_WRITE, xc->i2c_addr, + cmd, sizeof(cmd), NULL, 0, 0); + if (error) + return error; + resp[0] = resp[1] = 0; + error = iic_exec(xc->i2c, I2C_OP_READ, xc->i2c_addr, + NULL, 0, resp, sizeof(resp), 0); + if (error) + return error; + + *val = (resp[0] << 8) | resp[1]; + + return 0; +} + +static int +xc3028_write_buffer(struct xc3028 *xc, const uint8_t *data, size_t datalen) +{ + return iic_exec(xc->i2c, I2C_OP_WRITE_WITH_STOP, xc->i2c_addr, + data, datalen, NULL, 0, 0); +} + +#if notyet +static int +xc3028_write_2(struct xc3028 *xc, uint16_t reg, uint16_t val) +{ + uint8_t data[4]; + + data[0] = reg >> 8; + data[1] = reg & 0xff; + data[2] = val >> 8; + data[3] = val & 0xff; + + return xc3028_write_buffer(xc, data, sizeof(data)); +} +#endif + +struct xc3028 * +xc3028_open(device_t parent, i2c_tag_t i2c, i2c_addr_t addr, + xc3028_reset_cb reset, void *reset_priv, enum xc3028_type type) +{ + struct xc3028 *xc; + + xc = kmem_alloc(sizeof(*xc), KM_SLEEP); + if (xc == NULL) + return NULL; + xc->parent = parent; + xc->i2c = i2c; + xc->i2c_addr = addr; + xc->reset = reset; + xc->reset_priv = reset_priv; + xc->type = type; + + if (xc3028_firmware_open(xc)) { + aprint_error_dev(parent, "%s: fw open failed\n", + xc3028_name(xc)); + goto failed; + } + + return xc; + +failed: + kmem_free(xc, sizeof(*xc)); + return NULL; +} + +void +xc3028_close(struct xc3028 *xc) +{ + unsigned int index; + + if (xc->fw) { + for (index = 0; index < xc->nfw; index++) { + if (xc->fw[index].data) + kmem_free(xc->fw[index].data, + xc->fw[index].data_size); + } + kmem_free(xc->fw, sizeof(*xc->fw) * xc->nfw); + } + kmem_free(xc, sizeof(*xc)); +} + +int +xc3028_tune_dtv(struct xc3028 *xc, const struct dvb_frontend_parameters *params) +{ + static uint8_t freq_init[] = { 0x80, 0x02, 0x00, 0x00 }; + uint8_t freq_buf[4]; + uint32_t div, offset = 0; + int error; + + if (params->u.vsb.modulation == VSB_8) { + offset = 1750000; + } else { + return EINVAL; + } + + div = (params->frequency - offset + 15625 / 2) / 15625; + + error = xc3028_write_buffer(xc, freq_init, sizeof(freq_init)); + if (error) + return error; + delay(10000); + + freq_buf[0] = (div >> 24) & 0xff; + freq_buf[1] = (div >> 16) & 0xff; + freq_buf[2] = (div >> 8) & 0xff; + freq_buf[3] = (div >> 0) & 0xff; + error = xc3028_write_buffer(xc, freq_buf, sizeof(freq_buf)); + if (error) + return error; + delay(100000); + + return 0; +} + +MODULE(MODULE_CLASS_DRIVER, xc3028, NULL); + +static int +xc3028_modcmd(modcmd_t cmd, void *opaque) +{ + switch (cmd) { + case MODULE_CMD_INIT: + mutex_init(&xc3028_firmware_lock, MUTEX_DEFAULT, IPL_NONE); + return 0; + case MODULE_CMD_FINI: + mutex_destroy(&xc3028_firmware_lock); + return 0; + default: + return ENOTTY; + } +} diff --git a/sys/dev/i2c/xc3028reg.h b/sys/dev/i2c/xc3028reg.h new file mode 100644 index 000000000000..f1729e29f3f1 --- /dev/null +++ b/sys/dev/i2c/xc3028reg.h @@ -0,0 +1,41 @@ +/* $NetBSD: xc3028reg.h,v 1.1 2011/07/11 18:00:06 jmcneill Exp $ */ + +/*- + * Copyright (c) 2011 Jared D. McNeill + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XC3028REG_H +#define _XC3028REG_H + +#define XC3028_REG_FREQ_ERROR 0x0001 +#define XC3028_REG_LOCK 0x0002 +#define XC3028_REG_VERSION 0x0004 +#define XC3028_REG_PRODUCT_ID 0x0008 +#define XC3028_REG_HSYNC_FREQ 0x0010 +#define XC3028_REG_FRAME_LINES 0x0020 +#define XC3028_REG_QUALITY 0x0040 +#define XC3028_REG_ADC_ENVELOPE 0x0100 + +#endif /* !_XC3028REG_H */ diff --git a/sys/dev/i2c/xc3028var.h b/sys/dev/i2c/xc3028var.h new file mode 100644 index 000000000000..b18cb0b4b890 --- /dev/null +++ b/sys/dev/i2c/xc3028var.h @@ -0,0 +1,70 @@ +/* $NetBSD: xc3028var.h,v 1.1 2011/07/11 18:00:06 jmcneill Exp $ */ + +/*- + * Copyright (c) 2011 Jared D. McNeill + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XC3028VAR_H +#define _XC3028VAR_H + +#include +#include + +typedef int (*xc3028_reset_cb)(void *); + +enum xc3028_type { + XC3028, + XC3028L, +}; + +struct xc3028_fw { + uint32_t type; + uint64_t id; + uint16_t int_freq; + uint8_t *data; + uint32_t data_size; +}; + +struct xc3028 { + device_t parent; + i2c_tag_t i2c; + i2c_addr_t i2c_addr; + + xc3028_reset_cb reset; + void *reset_priv; + + enum xc3028_type type; + + struct xc3028_fw *fw; + unsigned int nfw; +}; + +struct xc3028 * xc3028_open(device_t, i2c_tag_t, i2c_addr_t, + xc3028_reset_cb, void *, enum xc3028_type); +void xc3028_close(struct xc3028 *); +int xc3028_tune_dtv(struct xc3028 *, + const struct dvb_frontend_parameters *); + +#endif /* !_XC3028VAR_H */