/* * Intel CE6230 DVB USB driver * * Copyright (C) 2009 Antti Palosaari * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "ce6230.h" DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); static int ce6230_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) { int ret; unsigned int pipe; u8 request; u8 requesttype; u16 value; u16 index; u8 *buf; request = req->cmd; value = req->value; index = req->index; switch (req->cmd) { case I2C_READ: case DEMOD_READ: case REG_READ: requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); break; case I2C_WRITE: case DEMOD_WRITE: case REG_WRITE: requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); break; default: pr_debug("%s: unknown command=%02x\n", __func__, req->cmd); ret = -EINVAL; goto error; } buf = kmalloc(req->data_len, GFP_KERNEL); if (!buf) { ret = -ENOMEM; goto error; } if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { /* write */ memcpy(buf, req->data, req->data_len); pipe = usb_sndctrlpipe(d->udev, 0); } else { /* read */ pipe = usb_rcvctrlpipe(d->udev, 0); } msleep(1); /* avoid I2C errors */ ret = usb_control_msg(d->udev, pipe, request, requesttype, value, index, buf, req->data_len, CE6230_USB_TIMEOUT); ce6230_debug_dump(request, requesttype, value, index, buf, req->data_len); if (ret < 0) pr_err("%s: usb_control_msg() failed=%d\n", KBUILD_MODNAME, ret); else ret = 0; /* read request, copy returned data to return buf */ if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) memcpy(req->data, buf, req->data_len); kfree(buf); error: return ret; } /* I2C */ static struct zl10353_config ce6230_zl10353_config; static int ce6230_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); int ret = 0, i = 0; struct usb_req req; if (num > 2) return -EOPNOTSUPP; memset(&req, 0, sizeof(req)); if (mutex_lock_interruptible(&d->i2c_mutex) < 0) return -EAGAIN; while (i < num) { if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { if (msg[i].addr == ce6230_zl10353_config.demod_address) { req.cmd = DEMOD_READ; req.value = msg[i].addr >> 1; req.index = msg[i].buf[0]; req.data_len = msg[i+1].len; req.data = &msg[i+1].buf[0]; ret = ce6230_ctrl_msg(d, &req); } else { pr_err("%s: I2C read not implemented\n", KBUILD_MODNAME); ret = -EOPNOTSUPP; } i += 2; } else { if (msg[i].addr == ce6230_zl10353_config.demod_address) { req.cmd = DEMOD_WRITE; req.value = msg[i].addr >> 1; req.index = msg[i].buf[0]; req.data_len = msg[i].len-1; req.data = &msg[i].buf[1]; ret = ce6230_ctrl_msg(d, &req); } else { req.cmd = I2C_WRITE; req.value = 0x2000 + (msg[i].addr >> 1); req.index = 0x0000; req.data_len = msg[i].len; req.data = &msg[i].buf[0]; ret = ce6230_ctrl_msg(d, &req); } i += 1; } if (ret) break; } mutex_unlock(&d->i2c_mutex); return ret ? ret : i; } static u32 ce6230_i2c_functionality(struct i2c_adapter *adapter) { return I2C_FUNC_I2C; } static struct i2c_algorithm ce6230_i2c_algorithm = { .master_xfer = ce6230_i2c_master_xfer, .functionality = ce6230_i2c_functionality, }; /* Callbacks for DVB USB */ static struct zl10353_config ce6230_zl10353_config = { .demod_address = 0x1e, .adc_clock = 450000, .if2 = 45700, .no_tuner = 1, .parallel_ts = 1, .clock_ctl_1 = 0x34, .pll_0 = 0x0e, }; static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap) { pr_debug("%s:\n", __func__); adap->fe[0] = dvb_attach(zl10353_attach, &ce6230_zl10353_config, &adap->dev->i2c_adap); if (adap->fe[0] == NULL) return -ENODEV; return 0; } static struct mxl5005s_config ce6230_mxl5003s_config = { .i2c_address = 0xc6, .if_freq = IF_FREQ_4570000HZ, .xtal_freq = CRYSTAL_FREQ_16000000HZ, .agc_mode = MXL_SINGLE_AGC, .tracking_filter = MXL_TF_DEFAULT, .rssi_enable = MXL_RSSI_ENABLE, .cap_select = MXL_CAP_SEL_ENABLE, .div_out = MXL_DIV_OUT_4, .clock_out = MXL_CLOCK_OUT_DISABLE, .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, .top = MXL5005S_TOP_25P2, .mod_mode = MXL_DIGITAL_MODE, .if_mode = MXL_ZERO_IF, .AgcMasterByte = 0x00, }; static int ce6230_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) { int ret; pr_debug("%s:\n", __func__); ret = dvb_attach(mxl5005s_attach, adap->fe[0], &adap->dev->i2c_adap, &ce6230_mxl5003s_config) == NULL ? -ENODEV : 0; return ret; } static int ce6230_power_ctrl(struct dvb_usb_device *d, int onoff) { int ret; pr_debug("%s: onoff=%d\n", __func__, onoff); /* InterfaceNumber 1 / AlternateSetting 0 idle InterfaceNumber 1 / AlternateSetting 1 streaming */ ret = usb_set_interface(d->udev, 1, onoff); if (ret) pr_err("%s: usb_set_interface() failed=%d\n", KBUILD_MODNAME, ret); return ret; } /* DVB USB Driver stuff */ static struct dvb_usb_device_properties ce6230_props = { .driver_name = KBUILD_MODNAME, .owner = THIS_MODULE, .adapter_nr = adapter_nr, .bInterfaceNumber = 1, .i2c_algo = &ce6230_i2c_algorithm, .power_ctrl = ce6230_power_ctrl, .frontend_attach = ce6230_zl10353_frontend_attach, .tuner_attach = ce6230_mxl5003s_tuner_attach, .num_adapters = 1, .adapter = { { .stream = { .type = USB_BULK, .count = 6, .endpoint = 0x82, .u = { .bulk = { .buffersize = (16*512), } } }, } }, }; static const struct usb_device_id ce6230_id_table[] = { { DVB_USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500, &ce6230_props, "Intel CE9500 reference design", NULL) }, { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310, &ce6230_props, "AVerMedia A310 USB 2.0 DVB-T tuner", NULL) }, { } }; MODULE_DEVICE_TABLE(usb, ce6230_id_table); static struct usb_driver ce6230_usb_driver = { .name = KBUILD_MODNAME, .id_table = ce6230_id_table, .probe = dvb_usbv2_probe, .disconnect = dvb_usbv2_disconnect, .suspend = dvb_usbv2_suspend, .resume = dvb_usbv2_resume, .no_dynamic_id = 1, .soft_unbind = 1, }; module_usb_driver(ce6230_usb_driver); MODULE_AUTHOR("Antti Palosaari "); MODULE_DESCRIPTION("Intel CE6230 driver"); MODULE_LICENSE("GPL");