/* $NetBSD: xc3028.c,v 1.9 2018/09/03 16:29:31 riastradh 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.9 2018/09/03 16:29:31 riastradh 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, fwlen); 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=%" PRIx64, 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); 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); 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 = uimin(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); 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, "i2cexec"); 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; } }