add LGDT3303 tuner and XC3028L demod drivers

This commit is contained in:
jmcneill 2011-07-11 18:00:06 +00:00
parent 730b47d6fe
commit 9c53d3c419
6 changed files with 1110 additions and 2 deletions

View File

@ -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

359
sys/dev/i2c/lg3303.c Normal file
View File

@ -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;
}

48
sys/dev/i2c/lg3303var.h Normal file
View File

@ -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 */

582
sys/dev/i2c/xc3028.c Normal file
View File

@ -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;
}
}

41
sys/dev/i2c/xc3028reg.h Normal file
View File

@ -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 */

70
sys/dev/i2c/xc3028var.h Normal file
View File

@ -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 */