blob: 38884e1f1be4d6e13fdf9ab7480177ce02114b82 [file] [log] [blame]
/*
* Driver for SC100 DVB-S USB2.0 receiver
*
* The hardware design is based on a Montage M88RS6000 tuner/demodulator,
* a Cypress CY7C68013A USB bridge and a TI TPS65233 voltage regulator.
* The M88RS6000 chip integrates a M88RS6000 tuner and a M88DS3103
* demodulator. The demodulator is directly connected to the I2C bus.
* The tuner is connected internally to the M88DS3103 and can be
* accessed through an I2C mux. The CY7C68013A USB bridge is connected
* to the MPEG-TS interface of the M88RS6000 and makes the data on the
* MPEG-TS available to the host through an USB bulk endpoint. The
* TPS65233 has an I2C interface, however in the sc100 design it is
* controlled through pins driven by the M88RS6000 chip.
*
* Copyright (C) 2015 Google Inc.
*
* 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 "dvb_usb.h"
#include "m88ds3103.h"
#include "m88rs6000t.h"
#include "cypress_firmware.h"
static char *mac_addr_str = "90:09:1e:f1:be:33";
module_param_named(mac_addr, mac_addr_str, charp, 0000);
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
struct sc100_state {
struct mutex stream_mutex;
struct i2c_client *i2c_client_tuner;
};
static int sc100_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6])
{
if (!mac_pton(mac_addr_str, mac)) {
return -EINVAL;
}
return 0;
}
static int sc100_download_firmware(struct dvb_usb_device *d,
const struct firmware *fw)
{
pr_debug("Loading dvb-usb-sc100 firmware\n");
return cypress_load_firmware(d->udev, fw, CYPRESS_FX2);
}
static int sc100_identify_state(struct dvb_usb_device *d, const char **name)
{
if (d->udev->descriptor.idProduct == USB_PID_SC100_WARM)
return WARM;
else
return COLD;
}
static int sc100_stream_ctrl(struct dvb_usb_device *d, u8 onoff)
{
struct sc100_state *state = d_to_priv(d);
int ret = 0;
mutex_lock(&state->stream_mutex);
if (onoff) {
/* Configure the TS parameters */
/* TODO: details? */
ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
0xa4, USB_TYPE_VENDOR, 0, 0, 0, 0, 5000);
}
mutex_unlock(&state->stream_mutex);
return ret;
}
static int sc100_streaming_ctrl(struct dvb_frontend *fe, int onoff)
{
struct dvb_usb_device *d = fe_to_d(fe);
return sc100_stream_ctrl(d, (onoff == 0) ? 0 : 1);
}
static const struct m88ds3103_config sc100_m88rs6000_cfg = {
.i2c_addr = 0x69,
.clock = 27000000,
.i2c_wr_max = 33,
.ts_mode = M88DS3103_TS_CI,
.ts_clk = 16000,
.ts_clk_pol = 1,
.agc = 0x99,
.lnb_hv_pol = 0,
.lnb_en_pol = 1,
};
static int sc100_attach(struct dvb_usb_adapter *adap)
{
struct sc100_state *state = adap_to_priv(adap);
struct dvb_usb_device *d = adap_to_d(adap);
int ret = 0;
struct i2c_adapter *sc100_i2c_adap = i2c_get_adapter(0);
/* demod I2C adapter */
struct i2c_adapter *i2c_adapter;
struct i2c_client *client_tuner;
struct i2c_board_info info;
struct m88rs6000t_config m88rs6000t_config;
memset(&info, 0, sizeof(struct i2c_board_info));
/* attach demod */
adap->fe[0] = dvb_attach(m88ds3103_attach,
&sc100_m88rs6000_cfg,
sc100_i2c_adap,
&i2c_adapter);
if (!adap->fe[0]) {
dev_err(&d->udev->dev, "sc100_attach fail.\n");
ret = -ENODEV;
goto fail_attach;
}
/* attach tuner */
m88rs6000t_config.fe = adap->fe[0];
strlcpy(info.type, "m88rs6000t", I2C_NAME_SIZE);
info.addr = 0x21;
info.platform_data = &m88rs6000t_config;
request_module(info.type);
client_tuner = i2c_new_device(i2c_adapter, &info);
if (client_tuner == NULL || client_tuner->dev.driver == NULL) {
dvb_frontend_detach(adap->fe[0]);
ret = -ENODEV;
goto fail_attach;
}
state->i2c_client_tuner = client_tuner;
if (!try_module_get(client_tuner->dev.driver->owner)) {
i2c_unregister_device(client_tuner);
dvb_frontend_detach(adap->fe[0]);
ret = -ENODEV;
goto fail_attach;
}
/* delegate signal strength measurement to tuner */
adap->fe[0]->ops.read_signal_strength =
adap->fe[0]->ops.tuner_ops.get_rf_strength;
return ret;
fail_attach:
return ret;
}
static int sc100_init(struct dvb_usb_device *d)
{
struct sc100_state *state = d_to_priv(d);
mutex_init(&state->stream_mutex);
return 0;
}
static void sc100_exit(struct dvb_usb_device *d)
{
struct sc100_state *state = d_to_priv(d);
struct i2c_client *client;
client = state->i2c_client_tuner;
/* remove I2C tuner */
if (client) {
module_put(client->dev.driver->owner);
i2c_unregister_device(client);
}
}
static struct dvb_usb_device_properties sc100_props = {
.firmware = "dvb-usb-sc100.fw",
.driver_name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.adapter_nr = adapter_nr,
.size_of_priv = sizeof(struct sc100_state),
.frontend_attach = sc100_attach,
.init = sc100_init,
.streaming_ctrl = sc100_streaming_ctrl,
.identify_state = sc100_identify_state,
.exit = sc100_exit,
.read_mac_address = sc100_read_mac_addr,
.download_firmware = sc100_download_firmware,
.num_adapters = 1,
.adapter = {
{
.stream = DVB_USB_STREAM_BULK(
0x82, MAX_NO_URBS_FOR_DATA_STREAM, 16384),
}
}
};
static const struct usb_device_id sc100_id_table[] = {
{ DVB_USB_DEVICE(USB_VID_CYPRESS,
USB_PID_SC100_COLD,
&sc100_props, "Google DVB-S SC100 USB2.0",
NULL) },
{ DVB_USB_DEVICE(USB_VID_CYPRESS,
USB_PID_SC100_WARM,
&sc100_props, "Google DVB-S SC100 USB2.0",
NULL) },
{ }
};
MODULE_DEVICE_TABLE(usb, sc100_id_table);
static struct usb_driver sc100_usb_driver = {
.name = KBUILD_MODNAME,
.id_table = sc100_id_table,
.probe = dvb_usbv2_probe,
.disconnect = dvb_usbv2_disconnect,
.suspend = dvb_usbv2_suspend,
.resume = dvb_usbv2_resume,
.reset_resume = dvb_usbv2_reset_resume,
.no_dynamic_id = 1,
.soft_unbind = 1,
};
module_usb_driver(sc100_usb_driver);
MODULE_AUTHOR("Matthias Kaehlcke <mka@google.com>");
MODULE_DESCRIPTION("Driver for sc100 USB satellite receiver");
MODULE_LICENSE("GPL");