blob: c6d2be420ef0f91cd0f75a1a8580b9c6a5b52c93 [file] [log] [blame]
/* DVB USB framework compliant Linux driver for the
* Google DVB-S SC100 tuner.
*
* Copyright (C) 2015 Ke Dong (kedong@google.com)
*
* 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, version 2.
*
* see Documentation/dvb/README.dvb-usb for more information
*/
#include "sc100.h"
#include "dvbsky_m88rs6000.h"
#include "mach/comcerto-2000.h"
#define SC100_READ_MSG 0
#define SC100_WRITE_MSG 1
#define SC100_MAC_ADDR_INDEX 1
#define err_str "did not find the firmware file. (%s) " \
"Please see linux/Documentation/dvb/ for more details " \
"on firmware-problems."
/* debug */
static int dvb_usb_sc100_debug;
module_param_named(debug, dvb_usb_sc100_debug, int, 0644);
MODULE_PARM_DESC(debug, "set debugging level (1=info)." DVB_USB_DEBUG_STATUS);
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
/* Control register for LNP settings
* B7: I2C Control
* 1 - enabled, 0 - disabled
* B6: Reserved
* B5: Tone Gate
* 1 - on, 0 - off
* B4: Tone Mode
* 1 - internal, 0 - external
* B3: LNP output voltage
* 1 - enabled, 0 - disabled
* B2-1: Output voltage selection
* 0 - 13V, 4 - 18V
*/
#define LNB_I2C_ADDRESS (0x60)
#define LNB_CONTROL_REGISTER_1 (0x00)
#define LNB_CONTROL_REGISTER_1_I2C_CONTROL_MASK (0x80)
#define LNB_CONTROL_REGISTER_1_I2C_CONTROL_SHIFT (0x07)
#define LNB_CONTROL_REGISTER_1_I2C_CONTROL_ENABLED (0x01)
#define LNB_CONTROL_REGISTER_1_I2C_CONTROL_DISABLED (0x00)
#define LNB_CONTROL_REGISTER_1_TONE_GATE_MASK (0x20)
#define LNB_CONTROL_REGISTER_1_TONE_GATE_SHIFT (0x05)
#define LNB_CONTROL_REGISTER_1_TONE_GATE_ON (0x01)
#define LNB_CONTROL_REGISTER_1_TONE_GATE_OFF (0x00)
#define LNB_CONTROL_REGISTER_1_TONE_MODE_MASK (0x10)
#define LNB_CONTROL_REGISTER_1_TONE_MODE_SHIFT (0x4)
#define LNB_CONTROL_REGISTER_1_TONE_MODE_INTERNAL (0x01)
#define LNB_CONTROL_REGISTER_1_TONE_MODE_EXTERNAL (0x00)
#define LNB_CONTROL_REGISTER_1_LNB_OUTPUT_VOLTAGE_MASK (0x08)
#define LNB_CONTROL_REGISTER_1_LNB_OUTPUT_VOLTAGE_SHIFT (0x03)
#define LNB_CONTROL_REGISTER_1_LNB_OUTPUT_VOLTAGE_ON (0x01)
#define LNB_CONTROL_REGISTER_1_LNB_OUTPUT_VOLTAGE_OFF 0x00
#define LNB_CONTROL_REGISTER_1_LNB_VOLTAGE_SELECTION_MASK 0x07
#define LNB_CONTROL_REGISTER_1_LNB_VOLTAGE_SELECTION_SHIFT 0x00
#define LNB_CONTROL_REGISTER_1_LNB_VOLTAGE_SELECTION_13V 0x00
#define LNB_CONTROL_REGISTER_1_LNB_VOLTAGE_SELECTION_18V 0x04
#define LNB_FIELD_SET(data, field, value) \
{ \
data &= ~(LNB_CONTROL_REGISTER_1_ ## field ## _MASK); \
data |= LNB_CONTROL_REGISTER_1_ ## field ## _ ## value \
<< (LNB_CONTROL_REGISTER_1_ ## field ## _ ## SHIFT); \
}
struct i2c_adapter *sc100_i2c_adap;
struct usb_device *sc100_udev;
static int sc100_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
{
return comcerto_mac_addr_get(mac, SC100_MAC_ADDR_INDEX);
};
static int sc100_writereg(u8 reg, u8 data)
{
u8 buf[] = { reg, data };
struct i2c_msg msg = { .addr = LNB_I2C_ADDRESS,
.flags = 0, .buf = buf, .len = 2 };
int ret;
deb_info("%s: [W] R:0x%02x, V:0x%02x", __func__, reg, data);
ret = i2c_transfer(sc100_i2c_adap, &msg, 1);
if (ret != 1) {
err("%s: [W] R:0x%02x, V:0x%02x, E:%d", __func__, reg, data, ret);
return -EREMOTEIO;
}
return 0;
}
static u8 sc100_readreg(u8 reg)
{
int ret;
u8 b0[] = { reg };
u8 b1[] = { 0 };
struct i2c_msg msg[] = {
{ .addr = LNB_I2C_ADDRESS, .flags = 0, .buf = b0, .len = 1 },
{ .addr = LNB_I2C_ADDRESS, .flags = I2C_M_RD, .buf = b1, .len = 1 }
};
ret = i2c_transfer(sc100_i2c_adap, msg, 2);
if (ret != 2) {
err("%s: [R] R:0x%x, E:%d", __func__, reg, ret);
return ret;
}
deb_info("%s: [R] R:0x%02x, V:0x%02x", __func__, reg, b1[0]);
return b1[0];
}
static int sc100_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone)
{
u8 lnb_reg = sc100_readreg(LNB_CONTROL_REGISTER_1);
switch (tone) {
case SEC_TONE_ON:
info("%s: LNB TONE=ON\n", __func__);
LNB_FIELD_SET(lnb_reg, I2C_CONTROL, ENABLED);
LNB_FIELD_SET(lnb_reg, TONE_GATE, ON);
LNB_FIELD_SET(lnb_reg, TONE_MODE, INTERNAL);
break;
case SEC_TONE_OFF:
info("%s: LNB TONE=OFF\n", __func__);
LNB_FIELD_SET(lnb_reg, I2C_CONTROL, ENABLED);
LNB_FIELD_SET(lnb_reg, TONE_GATE, OFF);
break;
default:
return -EINVAL;
}
return sc100_writereg(LNB_CONTROL_REGISTER_1, lnb_reg);
}
static int sc100_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
{
u8 lnb_reg = sc100_readreg(LNB_CONTROL_REGISTER_1);
switch (voltage) {
case SEC_VOLTAGE_18:
info("%s: LNB VOLTAGE=18V\n", __func__);
LNB_FIELD_SET(lnb_reg, I2C_CONTROL, ENABLED);
LNB_FIELD_SET(lnb_reg, LNB_OUTPUT_VOLTAGE, ON);
LNB_FIELD_SET(lnb_reg, LNB_VOLTAGE_SELECTION, 18V);
break;
case SEC_VOLTAGE_13:
info("%s: LNB VOLTAGE=13V\n", __func__);
LNB_FIELD_SET(lnb_reg, I2C_CONTROL, ENABLED);
LNB_FIELD_SET(lnb_reg, LNB_OUTPUT_VOLTAGE, ON);
LNB_FIELD_SET(lnb_reg, LNB_VOLTAGE_SELECTION, 13V);
break;
default:
info("%s: LNB VOLTAGE=OFF\n", __func__);
LNB_FIELD_SET(lnb_reg, I2C_CONTROL, ENABLED);
LNB_FIELD_SET(lnb_reg, LNB_OUTPUT_VOLTAGE, OFF);
break;
}
return sc100_writereg(LNB_CONTROL_REGISTER_1, lnb_reg);
}
static int sc100_set_ts_params(struct dvb_frontend* fe, int is_punctured)
{
return usb_control_msg(sc100_udev, usb_sndctrlpipe(sc100_udev,0),
0xa4, USB_TYPE_VENDOR, 0, 0, 0, 0, 5000);
}
static struct dvbsky_m88rs6000_config sc100_config = {
.demod_address = 0x69,
.ci_mode = 2,
.pin_ctrl = 0x80,
.ts_mode = 0,
.tuner_readstops = 1,
.set_ts_params = sc100_set_ts_params,
};
static struct dvb_usb_device_properties sc100_properties;
static int sc100_frontend_attach(struct dvb_usb_adapter *d)
{
/* Initializes i2c adapter when the frontend is attached. */
sc100_i2c_adap = i2c_get_adapter(0);
d->fe_adap[0].fe = dvb_attach(dvbsky_m88rs6000_attach, &sc100_config,
sc100_i2c_adap);
if (d->fe_adap[0].fe != NULL) {
d->fe_adap[0].fe->ops.set_tone = sc100_set_tone;
d->fe_adap[0].fe->ops.set_voltage = sc100_set_voltage;
info("Attached SC100!\n");
return 0;
}
return -EIO;
}
static struct usb_device_id sc100_table[] = {
{USB_DEVICE(USB_VID_CYPRESS, USB_PID_SC100_COLD)},
{USB_DEVICE(USB_VID_CYPRESS, USB_PID_SC100_WARM)},
{ }
};
MODULE_DEVICE_TABLE(usb, sc100_table);
static struct dvb_usb_device_properties sc100_properties = {
.usb_ctrl = CYPRESS_FX2,
.firmware = "dvb-usb-sc100.fw",
/* parameter for the MPEG2-data transfer */
.num_adapters = 1,
.read_mac_address = sc100_read_mac_address,
.adapter = {
{
.num_frontends = 1,
.fe = {{
.frontend_attach = sc100_frontend_attach,
.stream = {
.type = USB_BULK,
.count = 8,
.endpoint = 0x82,
.u = {
.bulk = {
.buffersize = 4096,
}
}
},
}},
}
},
.num_device_descs = 1,
.devices = {
{ .name = "Google DVB-S SC100 USB2.0",
.cold_ids = { &sc100_table[0], NULL },
.warm_ids = { &sc100_table[1], NULL },
},
{NULL},
}
};
static int sc100_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
sc100_udev = interface_to_usbdev(intf);
if (0 == dvb_usb_device_init(intf, &sc100_properties,
THIS_MODULE, NULL, adapter_nr))
return 0;
return -ENODEV;
}
static struct usb_driver sc100_driver = {
.name = "sc100",
.probe = sc100_probe,
.disconnect = dvb_usb_device_exit,
.id_table = sc100_table,
};
static int __init sc100_module_init(void)
{
int ret = usb_register(&sc100_driver);
if (ret)
err("usb_register failed. Error number %d", ret);
return ret;
}
static void __exit sc100_module_exit(void)
{
usb_deregister(&sc100_driver);
}
module_init(sc100_module_init);
module_exit(sc100_module_exit);
MODULE_AUTHOR("Ke Dong (c) kedong@google.com");
MODULE_DESCRIPTION("Driver for Google SC100 device");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");