summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorjmcneill <jmcneill@NetBSD.org>2011-07-11 18:00:06 +0000
committerjmcneill <jmcneill@NetBSD.org>2011-07-11 18:00:06 +0000
commit67aa3b0bd2ef654890b01fa77ecc28779fe7eaa8 (patch)
tree3a3332ca8d8acacf16160e7d87cb4a5ee206b799 /sys
parenta0c3923aa77d08b5bf0605628210b492c792b87e (diff)
add LGDT3303 tuner and XC3028L demod drivers
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/i2c/files.i2c12
-rw-r--r--sys/dev/i2c/lg3303.c359
-rw-r--r--sys/dev/i2c/lg3303var.h48
-rw-r--r--sys/dev/i2c/xc3028.c582
-rw-r--r--sys/dev/i2c/xc3028reg.h41
-rw-r--r--sys/dev/i2c/xc3028var.h70
6 files changed, 1110 insertions, 2 deletions
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index 151b5803d78..8bd20c29332 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 00000000000..f888ef5daa4
--- /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 <sys/param.h>
+__KERNEL_RCSID(0, "$NetBSD: lg3303.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $");
+
+#include <sys/types.h>
+#include <sys/kmem.h>
+#include <sys/module.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/lg3303var.h>
+#include <dev/dtv/dtvif.h>
+
+#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,
+ &reg, 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(&reg, 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(&reg, 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(&reg, 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 00000000000..b8ba5a28d78
--- /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 <jmcneill@invisible.ca>
+ * 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 <dev/i2c/i2cvar.h>
+#include <dev/dtv/dtvio.h>
+
+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 00000000000..2b9e094fb5f
--- /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 <jmcneill@invisible.ca>
+ * 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 <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: xc3028.c,v 1.1 2011/07/11 18:00:06 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/kmem.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+
+#include <dev/firmload.h>
+#include <dev/i2c/i2cvar.h>
+
+#include <dev/i2c/xc3028reg.h>
+#include <dev/i2c/xc3028var.h>
+
+#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(" <none>\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 00000000000..f1729e29f3f
--- /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 <jmcneill@invisible.ca>
+ * 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 00000000000..b18cb0b4b89
--- /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 <jmcneill@invisible.ca>
+ * 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 <dev/i2c/i2cvar.h>
+#include <dev/dtv/dtvio.h>
+
+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 */