Files
px4_drv/driver/px4.c
nns779 72185f4766 PX-Q3PE4への対応 など
LICENSEファイルを追加

driver:
px4: LNB電源供給の制限を削除
px4: px4_suspendで-ENOSYSを返すように変更
2018-07-21 17:32:45 +09:00

1460 lines
30 KiB
C

// px4.c
#include "print_format.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/atomic.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/cdev.h>
#include "px4.h"
#include "ptx_ioctl.h"
#include "i2c_comm.h"
#include "it930x-config.h"
#include "it930x-bus.h"
#include "it930x.h"
#include "tc90522.h"
#include "r850_lite.h"
#include "r850_channel.h"
#include "rt710.h"
#include "ringbuffer.h"
#if !defined(FIRMWARE_FILENAME)
#define FIRMWARE_FILENAME "it930x-firmware.bin"
#endif
#if !defined(MAX_DEVICE) || !MAX_DEVICE
#define MAX_DEVICE 16
#endif
#define TSDEV_NUM 4
#define MAX_TSDEV (MAX_DEVICE * TSDEV_NUM)
#define DEVICE_NAME "px4"
#define PID_PX_W3U4 0x083f
#define PID_PX_W3PE4 0x023f
#define PID_PX_Q3U4 0x084a
#define PID_PX_Q3PE4 0x024a
#define ISDB_T 0
#define ISDB_S 1
#define TS_SYNC_COUNT 4
struct px4_tsdev {
unsigned int id;
int isdb; // ISDB_T or ISDB_S
bool init;
bool open;
bool lnb_power;
struct px4_device *px4;
struct tc90522_demod tc90522;
union {
struct r850_tuner r850; // for ISDB-T
struct rt710_tuner rt710; // for ISDB-S
} t;
atomic_t streaming; // 0: not streaming, !0: streaming
struct ringbuffer *rgbuf;
};
struct px4_device {
atomic_t ref; // reference counter
atomic_t avail; // availability flag
wait_queue_head_t wait;
struct mutex lock;
int dev_idx;
u16 vid; // Vendor id
u16 pid; // Product id
unsigned int dev_id; // 1 or 2
struct it930x_bridge it930x;
struct cdev cdev;
unsigned int streaming_count;
struct px4_tsdev tsdev[TSDEV_NUM];
struct ringbuffer **rgbuf;
};
MODULE_AUTHOR("nns779");
MODULE_DESCRIPTION("PLEX PX-W3U4/W3PE4/Q3PE4 Unofficial Linux driver");
MODULE_LICENSE("GPL v2");
MODULE_FIRMWARE(FIRMWARE_FILENAME);
static DEFINE_MUTEX(glock);
static struct class *px4_class = NULL;
static dev_t px4_dev_first;
static struct px4_device *devs[MAX_DEVICE];
static bool devs_reserve[MAX_DEVICE];
static unsigned int xfer_packets = 816;
static unsigned int max_urbs = 6;
static unsigned int tsdev_max_packets = 1024;
static bool no_dma = false;
module_param(xfer_packets, uint, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(xfer_packets, "Number of transfer packets from the device. (default: 816)");
module_param(max_urbs, uint, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(max_urbs, "Maximum number of URBs. (default: 6)");
module_param(tsdev_max_packets, uint, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(tsdev_max_packets, "Maximum number of packets buffering in tsdev. (default: 1024)");
module_param(no_dma, bool, S_IRUSR | S_IWUSR);
static const struct usb_device_id px4_usb_ids[] = {
{ USB_DEVICE(0x0511, PID_PX_W3U4) },
{ USB_DEVICE(0x0511, PID_PX_W3PE4) },
{ USB_DEVICE(0x0511, PID_PX_Q3U4) },
{ USB_DEVICE(0x0511, PID_PX_Q3PE4) },
{ 0 },
};
MODULE_DEVICE_TABLE(usb, px4_usb_ids);
static int px4_init(struct px4_device *px4)
{
int ret = 0;
int i;
atomic_set(&px4->ref, 1);
atomic_set(&px4->avail, 1);
init_waitqueue_head(&px4->wait);
mutex_init(&px4->lock);
px4->streaming_count = 0;
for (i = 0; i < TSDEV_NUM; i++) {
struct px4_tsdev *tsdev = &px4->tsdev[i];
tsdev->id = i;
tsdev->init = false;
tsdev->open = false;
tsdev->lnb_power = false;
tsdev->px4 = px4;
atomic_set(&tsdev->streaming, 0);
ret = ringbuffer_create(&tsdev->rgbuf);
if (ret)
break;
ringbuffer_alloc(tsdev->rgbuf, 188 * tsdev_max_packets);
px4->rgbuf[i] = tsdev->rgbuf;
}
return ret;
}
static int px4_term(struct px4_device *px4)
{
int i;
for (i = 0; i < TSDEV_NUM; i++) {
struct px4_tsdev *tsdev = &px4->tsdev[i];
ringbuffer_destroy(tsdev->rgbuf);
}
return 0;
}
static int px4_ref(struct px4_device *px4)
{
return atomic_add_return(1, &px4->ref);
}
static int px4_unref(struct px4_device *px4)
{
return atomic_sub_return(1, &px4->ref);
}
static int px4_load_config(struct px4_device *px4)
{
int ret = 0;
struct it930x_bridge *it930x = &px4->it930x;
u8 tmp;
int i;
ret = it930x_read_reg(it930x, 0x4979, &tmp);
if (ret) {
pr_debug("px4_load_config: it930x_read_reg(0x4979) failed.\n");
pr_notice("Couldn't load configuration from the device.\n");
return ret;
} else if (!tmp) {
pr_warn("EEPROM is invalid.\n");
return ret;
} else {
u8 buf[1];
ret = it930x_read_reg(it930x, 0x49ac, &buf[0]);
if (!ret)
pr_info("IR mode: %x\n", buf[0]);
}
px4->tsdev[0].isdb = ISDB_S;
px4->tsdev[1].isdb = ISDB_S;
px4->tsdev[2].isdb = ISDB_T;
px4->tsdev[3].isdb = ISDB_T;
it930x->input[0].i2c_addr = 0x22;
it930x->input[1].i2c_addr = 0x26;
it930x->input[2].i2c_addr = 0x20;
it930x->input[3].i2c_addr = 0x24;
for (i = 0; i < TSDEV_NUM; i++) {
struct it930x_stream_input *input = &it930x->input[i];
struct px4_tsdev *tsdev = &px4->tsdev[i];
input->enable = true;
input->is_parallel = false;
input->port_number = i + 1;
input->slave_number = i;
input->i2c_bus = 2;
input->packet_len = 188;
input->sync_byte = ((i + 1) << 4) | 0x07; // 0x17 0x27 0x37 0x47
tsdev->tc90522.i2c = &it930x->i2c_master[0];
tsdev->tc90522.i2c_addr = input->i2c_addr;
switch (tsdev->isdb) {
case ISDB_S:
tsdev->t.rt710.i2c = &tsdev->tc90522.i2c_master;
tsdev->t.rt710.i2c_addr = 0x7a;
break;
case ISDB_T:
tsdev->t.r850.i2c = &tsdev->tc90522.i2c_master;
tsdev->t.r850.i2c_addr = 0x7c;
break;
}
}
it930x->input[4].enable = false;
return 0;
}
static int px4_set_power(struct px4_device *px4, bool on)
{
int ret = 0;
struct it930x_bridge *it930x = &px4->it930x;
if (on) {
int i;
ret = it930x_set_gpio(it930x, 7, false);
if (ret)
return ret;
ret = it930x_set_gpio(it930x, 2, false);
if (ret)
return ret;
msleep(10);
ret = it930x_set_gpio(it930x, 2, true);
if (ret)
return ret;
msleep(10);
for (i = 0; i < TSDEV_NUM; i++) {
px4->tsdev[i].init = false;
if (!px4->tsdev[i].open) {
switch (px4->tsdev[i].isdb) {
case ISDB_S:
ret = tc90522_sleep_s(&px4->tsdev[i].tc90522, true);
break;
case ISDB_T:
ret = tc90522_sleep_t(&px4->tsdev[i].tc90522, true);
break;
default:
break;
}
}
if (ret) {
// error
it930x_set_gpio(it930x, 7, true);
it930x_set_gpio(it930x, 2, false);
break;
}
}
} else {
it930x_set_gpio(it930x, 7, true);
it930x_set_gpio(it930x, 2, false);
}
return ret;
}
static int px4_set_lnb_power(struct px4_device *px4, bool on)
{
int i;
bool b = false;
for (i = 0; i < TSDEV_NUM; i++) {
if (px4->tsdev[i].lnb_power) {
b = true;
break;
}
}
if ((b && !on) || (!b && on))
return 0;
return it930x_set_gpio(&px4->it930x, 11, (on) ? true : false);
}
static bool px4_ts_sync(u8 **buf, u32 *len, bool *sync_remain)
{
bool ret = false;
u8 *p = *buf;
u32 remain = *len;
bool b = false;
while (remain) {
u32 i;
b = true;
for (i = 0; i < TS_SYNC_COUNT; i++) {
if (remain > (i * 188)) {
if ((p[i * 188] & 0x8f) != 0x07) {
b = false;
break;
}
} else {
break;
}
}
if (i == TS_SYNC_COUNT) {
// ok
b = false;
ret = true;
break;
}
if (b)
break;
p += 1;
remain -= 1;
}
*buf = p;
*len = remain;
*sync_remain = b;
return ret;
}
static void px4_ts_write(struct ringbuffer **rgbuf, u8 **buf, u32 *len)
{
u8 *p = *buf;
u32 remain = *len;
while (remain >= 188 && ((p[0] & 0x8f) == 0x07)) {
u8 id = (p[0] & 0x70) >> 4;
if (id && id < 5) {
p[0] = 0x47;
ringbuffer_write_atomic(rgbuf[id - 1], p, 188);
} else {
pr_debug("px4_ts_write: unknown id %d\n", id);
}
p += 188;
remain -= 188;
}
*buf = p;
*len = remain;
return;
}
static int px4_on_stream(void *context, void *buf, u32 len)
{
struct ringbuffer **rgbuf = context;
u8 *p = buf;
u32 remain = len;
bool sync_remain = false;
while (remain) {
if (!px4_ts_sync(&p, &remain, &sync_remain))
break;
px4_ts_write(rgbuf, &p, &remain);
}
if (sync_remain)
pr_debug("px4_on_stream: sync_remain remain: %u\n", remain);
return 0;
}
struct tc90522_regbuf tc_init_s[] = {
{ 0x15, NULL, { 0x00 } },
{ 0x1d, NULL, { 0x00 } },
{ 0x04, NULL, { 0x02 } }
};
struct tc90522_regbuf tc_init_t[] = {
{ 0xb0, NULL, { 0xa0 } },
{ 0xb2, NULL, { 0x3d } },
{ 0xb3, NULL, { 0x25 } },
{ 0xb4, NULL, { 0x8b } },
{ 0xb5, NULL, { 0x4b } },
{ 0xb6, NULL, { 0x3f } },
{ 0xb7, NULL, { 0xff } },
{ 0xb8, NULL, { 0xc0 } },
{ 0x1f, NULL, { 0x00 } },
{ 0x75, NULL, { 0x00 } }
};
// This function must be called after power on.
static int px4_tsdev_init(struct px4_tsdev *tsdev)
{
int ret = 0;
struct tc90522_demod *tc90522 = &tsdev->tc90522;
if (tsdev->init)
// already initialized
return 0;
switch (tsdev->isdb) {
case ISDB_S:
{
ret = tc90522_write_regs(tc90522, tc_init_s, ARRAY_SIZE(tc_init_s));
if (ret)
break;
// disable ts pins
ret = tc90522_enable_ts_pins_s(tc90522, false);
if (ret)
break;
// wake up
ret = tc90522_sleep_s(tc90522, false);
if (ret)
break;
ret = rt710_init(&tsdev->t.rt710);
if (ret) {
pr_debug("px4_tsdev_init %d:%u: rt710_init() failed.\n", tsdev->px4->dev_idx, tsdev->id);
break;
}
break;
}
case ISDB_T:
{
ret = tc90522_write_regs(tc90522, tc_init_t, ARRAY_SIZE(tc_init_t));
if (ret)
break;
// disable ts pins
ret = tc90522_enable_ts_pins_t(tc90522, false);
if (ret)
break;
// wake up
ret = tc90522_sleep_t(tc90522, false);
if (ret)
break;
ret = r850_init(&tsdev->t.r850);
if (ret) {
pr_debug("px4_tsdev_init %d:%u: r850_init() failed.\n", tsdev->px4->dev_idx, tsdev->id);
break;
}
break;
}
default:
ret = -EIO;
break;
}
if (!ret)
tsdev->init = true;
return ret;
}
static void px4_tsdev_uninit(struct px4_tsdev *tsdev)
{
#if 0
struct tc90522_demod *tc90522 = &tsdev->tc90522;
switch (tsdev->isdb) {
case ISDB_S:
tc90522_sleep_s(tc90522, true);
break;
case ISDB_T:
tc90522_sleep_t(tc90522, true);
break;
}
#endif
return;
}
static int px4_tsdev_set_channel(struct px4_tsdev *tsdev, struct ptx_freq *freq)
{
int ret = 0, dev_idx = tsdev->px4->dev_idx;
unsigned int tsdev_id = tsdev->id;
struct tc90522_demod *tc90522 = &tsdev->tc90522;
struct tc90522_regbuf regbuf_tc[3];
u32 real_freq;
pr_debug("px4_tsdev_set_channel %d:%u: freq_no: %d, slot: %d\n", dev_idx, tsdev_id, freq->freq_no, freq->slot);
switch (tsdev->isdb) {
case ISDB_S:
{
int i;
bool tuner_locked;
u16 tsid, tsid2;
if (freq->freq_no < 0) {
ret = -EINVAL;
break;
} else if (freq->freq_no < 12) {
if (freq->slot >= 8) {
ret = -EINVAL;
break;
}
real_freq = 1049480 + (38360 * freq->freq_no);
} else if (freq->freq_no < 24) {
real_freq = 1613000 + (40000 * (freq->freq_no - 12));
} else {
ret = -EINVAL;
break;
}
// set frequency
ret = tc90522_set_agc_s(tc90522, false);
if (ret)
break;
tc90522_regbuf_set_val(&regbuf_tc[0], 0x8e, 0x06/*0x02*/);
tc90522_regbuf_set_val(&regbuf_tc[1], 0xa3, 0xf7);
ret = tc90522_write_regs(tc90522, regbuf_tc, 2);
if (ret)
break;
ret = rt710_set_params(&tsdev->t.rt710, real_freq, 28860, 4);
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: rt710_set_params(%u, 28860, 4) failed.\n", dev_idx, tsdev_id, real_freq);
break;
}
i = 50;
while (i--) {
ret = rt710_get_pll_locked(&tsdev->t.rt710, &tuner_locked);
if (!ret && tuner_locked)
break;
msleep(10);
}
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: rt710_get_pll_locked() failed.\n", dev_idx, tsdev_id);
break;
} else {
pr_debug("px4_tsdev_set_channel %d:%u: rt710_get_pll_locked() locked: %d, count: %d\n", dev_idx, tsdev_id, tuner_locked, i);
}
if (!tuner_locked) {
// PLL error
ret = -EIO;
break;
}
ret = tc90522_set_agc_s(tc90522, true);
if (ret)
break;
// set slot
i = 50;
while (i--) {
ret = tc90522_tmcc_get_tsid_s(tc90522, freq->slot, &tsid);
if (!ret || ret == -EINVAL)
break;
msleep(10);
}
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: tc90522_tmcc_get_tsid_s() failed.\n", dev_idx, tsdev_id);
break;
} else {
pr_debug("px4_tsdev_set_channel %d:%u: tc90522_tmcc_get_tsid_s() tsid: %04x, count: %d\n", dev_idx, tsdev_id, tsid, i);
}
ret = tc90522_set_tsid_s(tc90522, tsid);
if (ret)
break;
i = 50;
while(i--) {
ret = tc90522_get_tsid_s(tc90522, &tsid2);
if (!ret && tsid2 == tsid)
break;
msleep(10);
}
if (tsid2 != tsid)
ret = -EAGAIN;
pr_debug("px4_tsdev_set_channel %d:%u: tc90522_get_tsid_s() tsid2: %04x, count: %d\n", dev_idx, tsdev_id, tsid2, i);
break;
}
case ISDB_T:
{
int i;
bool tuner_locked, demod_locked;
u8 regs[R850_NUM_REGS - 0x08];
if ((freq->freq_no >= 3 && freq->freq_no <= 12) || (freq->freq_no >= 22 && freq->freq_no <= 62)) {
// CATV C13-C22ch, C23-63ch
#if 0
real_freq = 93143 + freq->freq_no * 6000 + freq->slot/* addfreq */;
if (freq->freq_no == 12)
real_freq += 2000;
#else
ret = -ENOSYS;
break;
#endif
} else if (freq->freq_no >= 63 && freq->freq_no <= 102) {
// UHF 13-52ch
#if 0
real_freq = 95143 + freq->freq_no * 6000 + freq->slot/* addfreq */;
#else
ret = r850_channel_get_regs(freq->freq_no, regs);
if (ret)
break;
#endif
} else {
// Unknown channel
ret = -EINVAL;
break;
}
tc90522_regbuf_set_val(&regbuf_tc[0], 0x47, 0x30);
ret = tc90522_write_regs(tc90522, regbuf_tc, 1);
if (ret)
break;
ret = tc90522_set_agc_t(tc90522, false);
if (ret)
break;
tc90522_regbuf_set_val(&regbuf_tc[0], 0x76, 0x0c);
ret = tc90522_write_regs(tc90522, regbuf_tc, 1);
if (ret)
break;
ret = r850_write_config_regs(&tsdev->t.r850, regs);
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: r850_write_config_regs() 1 failed.\n", dev_idx, tsdev_id);
break;
}
msleep(40);
regs[0x2f - 0x08] |= 0x02;
ret = r850_write_config_regs(&tsdev->t.r850, regs);
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: r850_write_config_regs() 2 failed.\n", dev_idx, tsdev_id);
break;
}
i = 50;
while (i--) {
ret = r850_is_pll_locked(&tsdev->t.r850, &tuner_locked);
if (!ret && tuner_locked)
break;
msleep(10);
}
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: r850_is_pll_locked() failed.\n", dev_idx, tsdev_id);
break;
} else {
pr_debug("px4_tsdev_set_channel %d:%u: r850_is_pll_locked() locked: %d, count: %d\n", dev_idx, tsdev_id, tuner_locked, i);
}
if (!tuner_locked) {
// PLL error
ret = -EIO;
break;
}
ret = tc90522_set_agc_t(tc90522, true);
if (ret)
break;
tc90522_regbuf_set_val(&regbuf_tc[0], 0x71, 0x21);
tc90522_regbuf_set_val(&regbuf_tc[1], 0x72, 0x25);
tc90522_regbuf_set_val(&regbuf_tc[2], 0x75, 0x08);
ret = tc90522_write_regs(tc90522, regbuf_tc, 3);
if (ret)
break;
i = 50;
while (i--) {
ret = tc90522_is_signal_locked_t(tc90522, &demod_locked);
if (!ret && demod_locked)
break;
msleep(40);
}
if (ret) {
pr_debug("px4_tsdev_set_channel %d:%u: tc90522_is_signal_locked_t() failed.\n", dev_idx, tsdev_id);
break;
} else {
pr_debug("px4_tsdev_set_channel %d:%u: tc90522_is_signal_locked_t() locked: %d, count: %d\n", dev_idx, tsdev_id, demod_locked, i);
}
break;
}
default:
ret = -EIO;
break;
}
if (!ret)
pr_debug("px4_tsdev_set_channel %d:%u: succeeded.\n", dev_idx, tsdev_id);
else
pr_debug("px4_tsdev_set_channel %d:%u: failed. (ret: %d)\n", dev_idx, tsdev_id, ret);
return ret;
}
static int px4_tsdev_start_streaming(struct px4_tsdev *tsdev)
{
int ret = 0;
struct px4_device *px4 = tsdev->px4;
struct it930x_bus *bus = &px4->it930x.bus;
struct tc90522_demod *tc90522 = &tsdev->tc90522;
if (atomic_read(&tsdev->streaming))
// already started
return 0;
atomic_set(&tsdev->streaming, 1);
if (!px4->streaming_count) {
bus->usb.streaming_urb_num = max_urbs;
bus->usb.streaming_no_dma = no_dma;
pr_debug("px4_tsdev_start_streaming %d:%u: max_urbs: %u, no_dma: %c\n", px4->dev_idx, tsdev->id, bus->usb.streaming_urb_num, (bus->usb.streaming_no_dma) ? 'Y' : 'N');
it930x_purge_psb(&px4->it930x);
}
switch (tsdev->isdb) {
case ISDB_S:
// enable ts pins
ret = tc90522_enable_ts_pins_s(tc90522, true);
if (ret)
// disable ts pins
tc90522_enable_ts_pins_s(tc90522, false);
break;
case ISDB_T:
// enable ts pins
ret = tc90522_enable_ts_pins_t(tc90522, true);
if (ret)
// disable ts pins
tc90522_enable_ts_pins_t(tc90522, false);
break;
default:
ret = -EIO;
break;
}
if (ret)
goto fail;
ret = ringbuffer_alloc(tsdev->rgbuf, 188 * tsdev_max_packets);
if (ret)
goto fail;
ret = ringbuffer_start(tsdev->rgbuf);
if (ret)
goto fail;
if (!px4->streaming_count) {
pr_debug("px4_tsdev_start_streaming %d:%u: starting...\n", px4->dev_idx, tsdev->id);
ret = it930x_bus_start_streaming(bus, px4_on_stream, px4->rgbuf);
if (ret) {
pr_debug("px4_tsdev_start_streaming %d:%u: it930x_bus_start_streaming() failed.\n", px4->dev_idx, tsdev->id);
goto fail_after_ringbuffer;
}
}
px4->streaming_count++;
pr_debug("px4_tsdev_start_streaming %d:%u: streaming_count: %u\n", px4->dev_idx, tsdev->id, px4->streaming_count);
return ret;
fail_after_ringbuffer:
ringbuffer_stop(tsdev->rgbuf);
fail:
atomic_set(&tsdev->streaming, 0);
pr_debug("px4_tsdev_start_streaming %d:%u: failed. (ret: %d)\n", px4->dev_idx, tsdev->id, ret);
return ret;
}
static int px4_tsdev_stop_streaming(struct px4_tsdev *tsdev, bool avail)
{
int ret = 0;
struct px4_device *px4 = tsdev->px4;
struct tc90522_demod *tc90522 = &tsdev->tc90522;
if (!atomic_read(&tsdev->streaming))
// already stopped
return 0;
atomic_set(&tsdev->streaming, 0);
px4->streaming_count--;
if (!px4->streaming_count) {
pr_debug("px4_tsdev_stop_streaming %d:%u: stopping...\n", px4->dev_idx, tsdev->id);
it930x_bus_stop_streaming(&px4->it930x.bus);
}
ringbuffer_stop(tsdev->rgbuf);
if (!avail)
return 0;
switch (tsdev->isdb) {
case ISDB_S:
// disable ts pins
ret = tc90522_enable_ts_pins_s(tc90522, false);
break;
case ISDB_T:
// disable ts pins
ret = tc90522_enable_ts_pins_t(tc90522, false);
break;
default:
ret = -EIO;
break;
}
pr_debug("px4_tsdev_stop_streaming %d:%u: streaming_count: %u\n", px4->dev_idx, tsdev->id, px4->streaming_count);
return ret;
}
static int px4_tsdev_get_cn(struct px4_tsdev *tsdev, u32 *cn)
{
int ret = 0;
struct tc90522_demod *tc90522 = &tsdev->tc90522;
switch (tsdev->isdb) {
case ISDB_S:
ret = tc90522_get_cn_s(tc90522, (u16 *)cn);
break;
case ISDB_T:
ret = tc90522_get_cndat_t(tc90522, cn);
break;
default:
ret = -EIO;
break;
}
return ret;
}
struct tc90522_regbuf tc_init_s0[] = {
{ 0x07, NULL, { 0x31 } },
{ 0x08, NULL, { 0x77 } }
};
struct tc90522_regbuf tc_init_t0[] = {
{ 0x0e, NULL, { 0x77 } },
{ 0x0f, NULL, { 0x13 } }
};
static int px4_tsdev_open(struct inode *inode, struct file *file)
{
int ret = 0, ref;
int minor = (iminor(inode) - MINOR(px4_dev_first));
int dev_idx = (minor / TSDEV_NUM);
unsigned int tsdev_id = (minor % TSDEV_NUM);
struct px4_device *px4;
struct px4_tsdev *tsdev;
mutex_lock(&glock);
px4 = devs[dev_idx];
if (!px4) {
pr_err("px4_tsdev_open %d:%u: px4 is NULL.\n", dev_idx, tsdev_id);
mutex_unlock(&glock);
return -EFAULT;
}
if (!atomic_read(&px4->avail)) {
// not available
mutex_unlock(&glock);
return -EIO;
}
ref = px4_ref(px4);
pr_debug("px4_tsdev_open %d:%u: ref count: %d\n", dev_idx, tsdev_id, ref);
mutex_lock(&px4->lock);
mutex_unlock(&glock);
tsdev = &px4->tsdev[tsdev_id];
if (tsdev->open) {
// already used by another
ret = -EIO;
goto fail_already_used;
}
tsdev->open = true;
if (ref == 2) {
ret = px4_set_power(px4, true);
if (ret) {
pr_debug("px4_tsdev_open %d:%u: px4_set_power(true) failed.\n", dev_idx, tsdev_id);
goto fail;
}
}
ret = px4_tsdev_init(tsdev);
if (ret) {
pr_debug("px4_tsdev_open %d:%u: px4_tsdev_init() failed.\n", dev_idx, tsdev_id);
goto fail_after_power;
}
if (ref == 2) {
// S0
ret = tc90522_write_regs(&px4->tsdev[0].tc90522, tc_init_s0, ARRAY_SIZE(tc_init_s0));
if (ret) {
pr_debug("px4_tsdev_open %d:%u: tc90522_write_regs(tc_init_s0) failed.\n", dev_idx, tsdev_id);
goto fail_after_power;
}
// T0
ret = tc90522_write_regs(&px4->tsdev[2].tc90522, tc_init_t0, ARRAY_SIZE(tc_init_t0));
if (ret) {
pr_debug("px4_tsdev_open %d:%u: tc90522_write_regs(tc_init_t0) failed.\n", dev_idx, tsdev_id);
goto fail_after_power;
}
}
file->private_data = tsdev;
mutex_unlock(&px4->lock);
pr_debug("px4_tsdev_open %d:%u: ok\n", dev_idx, tsdev_id);
return 0;
fail_after_power:
if (ref == 2)
px4_set_power(px4, false);
fail:
tsdev->open = false;
fail_already_used:
mutex_unlock(&px4->lock);
px4_unref(px4);
pr_debug("px4_tsdev_open %d:%u: failed. (ret: %d)\n", dev_idx, tsdev_id, ret);
return ret;
}
static ssize_t px4_tsdev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
struct px4_device *px4;
struct px4_tsdev *tsdev;
size_t rd;
tsdev = file->private_data;
if (!tsdev) {
pr_err("px4_tsdev_read: tsdev is NULL.\n");
return -EFAULT;
}
px4 = tsdev->px4;
if (!atomic_read(&px4->avail) || !atomic_read(&tsdev->streaming))
return -EIO;
rd = count;
ret = ringbuffer_read_to_user(tsdev->rgbuf, buf, &rd);
return (ret) ? (ret) : (rd);
}
static int px4_tsdev_release(struct inode *inode, struct file *file)
{
int avail, ref;
struct px4_device *px4;
struct px4_tsdev *tsdev;
tsdev = file->private_data;
if (!tsdev) {
pr_err("px4_tsdev_release tsdev is NULL.\n");
return -EFAULT;
}
px4 = tsdev->px4;
avail = atomic_read(&px4->avail);
mutex_lock(&px4->lock);
px4_tsdev_stop_streaming(tsdev, (avail) ? true : false);
if (avail)
px4_tsdev_uninit(tsdev);
tsdev->open = false;
tsdev->lnb_power = false;
if (avail)
px4_set_lnb_power(px4, false);
ref = px4_unref(px4);
if (avail && ref == 1)
px4_set_power(px4, false);
mutex_unlock(&px4->lock);
wake_up(&px4->wait);
pr_debug("px4_tsdev_release %d:%u: ref count: %d\n", px4->dev_idx, tsdev->id, ref);
return 0;
}
static long px4_tsdev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long ret = -EIO;
struct px4_device *px4;
struct px4_tsdev *tsdev;
int dev_idx;
unsigned int tsdev_id;
unsigned long t;
tsdev = file->private_data;
if (!tsdev) {
pr_err("px4_tsdev_unlocked_ioctl: tsdev is NULL.\n");
return -EFAULT;
}
px4 = tsdev->px4;
if (!atomic_read(&px4->avail))
return -EIO;
dev_idx = px4->dev_idx;
tsdev_id = tsdev->id;
mutex_lock(&px4->lock);
switch (cmd) {
case PTX_SET_CHANNEL:
{
struct ptx_freq freq;
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_SET_CHANNEL\n", dev_idx, tsdev_id);
t = copy_from_user(&freq, (void *)arg, sizeof(freq));
ret = px4_tsdev_set_channel(tsdev, &freq);
break;
}
case PTX_START_STREAMING:
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_START_STREAMING\n", dev_idx, tsdev_id);
ret = px4_tsdev_start_streaming(tsdev);
break;
case PTX_STOP_STREAMING:
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_STOP_STREAMING\n", dev_idx, tsdev_id);
ret = px4_tsdev_stop_streaming(tsdev, true);
break;
case PTX_GET_CNR:
{
int cn = 0;
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_GET_CNR\n", dev_idx, tsdev_id);
ret = px4_tsdev_get_cn(tsdev, (u32 *)&cn);
if (!ret)
t = copy_to_user((void *)arg, &cn, sizeof(cn));
break;
}
case PTX_ENABLE_LNB_POWER:
{
int lnb;
lnb = (int)arg;
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_ENABLE_LNB_POWER lnb: %d\n", dev_idx, tsdev_id, lnb);
if (tsdev->isdb != ISDB_S) {
ret = -EINVAL;
break;
}
if (lnb == 0) {
// 0V
tsdev->lnb_power = false;
} else if (lnb == 2) {
// 15V
tsdev->lnb_power = true;
} else {
ret = -EINVAL;
break;
}
ret = px4_set_lnb_power(px4, tsdev->lnb_power);
break;
}
case PTX_DISABLE_LNB_POWER:
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: PTX_DISABLE_LNB_POWER\n", dev_idx, tsdev_id);
if (tsdev->isdb != ISDB_S) {
ret = -EINVAL;
break;
}
if (!tsdev->lnb_power) {
ret = 0;
break;
}
tsdev->lnb_power = false;
ret= px4_set_lnb_power(px4, false);
break;
default:
pr_debug("px4_tsdev_unlocked_ioctl %d:%u: unknown ioctl %08x\n", dev_idx, tsdev_id, cmd);
ret = -ENOSYS;
break;
}
mutex_unlock(&px4->lock);
return ret;
}
static struct file_operations px4_tsdev_fops = {
.owner = THIS_MODULE,
.open = px4_tsdev_open,
.read = px4_tsdev_read,
.release = px4_tsdev_release,
.unlocked_ioctl = px4_tsdev_unlocked_ioctl
};
static int px4_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int ret = 0, dev_idx = -1, i;
struct usb_device *usbdev;
struct px4_device *px4 = NULL;
struct it930x_bridge *it930x;
struct it930x_bus *bus;
pr_debug("px4_probe: xfer_packets: %u\n", xfer_packets);
mutex_lock(&glock);
for (i = 0; i < MAX_DEVICE; i++) {
if (!devs[i] && !devs_reserve[i]) {
dev_idx = i;
devs_reserve[i] = true;
break;
}
}
mutex_unlock(&glock);
pr_debug("px4_probe: dev_idx: %d\n", dev_idx);
if (dev_idx == -1) {
pr_err("Unused device index was not found.\n");
ret = -ECANCELED;
goto fail;
}
usbdev = interface_to_usbdev(intf);
px4 = kzalloc(sizeof(*px4), GFP_KERNEL);
if (!px4) {
pr_err("px4_probe: kzalloc() returns NULL.\n");
ret = -ENOMEM;
goto fail_before_bus;
}
px4->rgbuf = (struct ringbuffer **)kzalloc(sizeof(*px4->rgbuf) * TSDEV_NUM, GFP_ATOMIC);
if (!px4->rgbuf) {
pr_err("px4_probe: kzalloc() returns NULL (2).\n");
ret = -ENOMEM;
goto fail_before_bus;
}
px4->dev_idx = dev_idx;
px4->vid = id->idVendor;
px4->pid = id->idProduct;
px4->dev_id = 0;
if (strlen(usbdev->serial) == 15)
if (kstrtouint(&usbdev->serial[14], 16, &px4->dev_id))
pr_debug("px4_probe: kstrtouint() failed.\n");
else
pr_debug("px4_probe: dev_id: %u\n", px4->dev_id);
else
pr_debug("px4_probe: the length of serial number is invalid.\n");
it930x = &px4->it930x;
bus = &it930x->bus;
// Initialize px4 structure
ret = px4_init(px4);
if (ret)
goto fail_before_bus;
// Initialize bus operator
bus->type = IT930X_BUS_USB;
bus->usb.dev = usbdev;
bus->usb.ctrl_timeout = 3000;
bus->usb.streaming_xfer_size = xfer_packets * 188;
ret = it930x_bus_init(bus);
if (ret)
goto fail_before_bus;
// Load config from eeprom
ret = px4_load_config(px4);
if (ret)
goto fail;
// Initialize IT930x bridge
ret = it930x_init(it930x);
if (ret)
goto fail;
ret = it930x_load_firmware(it930x, FIRMWARE_FILENAME);
if (ret)
goto fail;
ret = it930x_init_device(it930x);
if (ret)
goto fail;
// Initialize tc90522 structure
for (i = 0; i < TSDEV_NUM; i++)
tc90522_init(&px4->tsdev[i].tc90522);
// GPIO configurations
ret = it930x_set_gpio(it930x, 7, true);
if (ret)
goto fail;
ret = it930x_set_gpio(it930x, 2, false);
if (ret)
goto fail;
// LNB power supply: off
ret = it930x_set_gpio(it930x, 11, false);
if (ret)
goto fail;
// cdev
cdev_init(&px4->cdev, &px4_tsdev_fops);
px4->cdev.owner = THIS_MODULE;
ret = cdev_add(&px4->cdev, MKDEV(MAJOR(px4_dev_first), MINOR(px4_dev_first) + (dev_idx * TSDEV_NUM)), TSDEV_NUM);
if (ret < 0) {
pr_err("Couldn't add cdev to the system.\n");
goto fail;
}
mutex_lock(&glock);
// create /dev/px4video*
for (i = 0; i < TSDEV_NUM; i++) {
pr_info("tsdev %i: px4video%u\n", i, (MINOR(px4_dev_first) + (dev_idx * TSDEV_NUM) + i));
device_create(px4_class, &intf->dev, MKDEV(MAJOR(px4_dev_first), (MINOR(px4_dev_first) + (dev_idx * TSDEV_NUM) + i)), NULL, "px4video%u", (MINOR(px4_dev_first) + (dev_idx * TSDEV_NUM) + i));
}
devs[dev_idx] = px4;
devs_reserve[dev_idx] = false;
mutex_unlock(&glock);
usb_set_intfdata(intf, px4);
return 0;
fail:
it930x_bus_term(&px4->it930x.bus);
fail_before_bus:
if (px4) {
if (px4->rgbuf)
kfree(px4->rgbuf);
kfree(px4);
}
mutex_lock(&glock);
devs_reserve[dev_idx] = false;
mutex_unlock(&glock);
return ret;
}
static void px4_disconnect(struct usb_interface *intf)
{
int i, ref;
struct px4_device *px4;
px4 = usb_get_intfdata(intf);
if (!px4)
return;
usb_set_intfdata(intf, NULL);
atomic_set(&px4->avail, 0);
mutex_lock(&px4->lock);
mutex_lock(&glock);
devs[px4->dev_idx] = NULL;
// delete /dev/px4video*
for (i = 0; i < TSDEV_NUM; i++)
device_destroy(px4_class, MKDEV(MAJOR(px4_dev_first), (MINOR(px4_dev_first) + (px4->dev_idx * TSDEV_NUM) + i)));
mutex_unlock(&glock);
cdev_del(&px4->cdev);
ref = px4_unref(px4);
mutex_unlock(&px4->lock);
while (ref) {
wait_event(px4->wait, (ref != atomic_read(&px4->ref)));
ref = atomic_read(&px4->ref);
}
// uninitialize
px4_term(px4);
it930x_bus_term(&px4->it930x.bus);
kfree(px4->rgbuf);
kfree(px4);
return;
}
static int px4_suspend(struct usb_interface *intf, pm_message_t message)
{
return -ENOSYS;
}
static int px4_resume(struct usb_interface *intf)
{
return 0;
}
static struct usb_driver px4_usb_driver = {
.name = "px4_drv",
.probe = px4_probe,
.disconnect = px4_disconnect,
.suspend = px4_suspend,
.resume = px4_resume,
.id_table = px4_usb_ids
};
static int px4_module_init(void)
{
int ret = 0, i;
#ifdef PX4_DRIVER_VERSION
pr_info(KBUILD_MODNAME " version " PX4_DRIVER_VERSION "\n");
#endif
for (i = 0; i < MAX_DEVICE; i++) {
devs[i] = NULL;
devs_reserve[i] = false;
}
ret = alloc_chrdev_region(&px4_dev_first, 0, MAX_TSDEV, DEVICE_NAME);
if (ret < 0) {
pr_debug("px4_module_init: alloc_chrdev_region() failed.\n");
return ret;
}
px4_class = class_create(THIS_MODULE, "px4");
if (IS_ERR(px4_class)) {
pr_debug("px4_module_init: class_create() failed.\n");
return PTR_ERR(px4_class);
}
ret = usb_register(&px4_usb_driver);
if (ret)
pr_debug("px4_module_init: usb_register() failed.\n");
return ret;
}
static void px4_module_exit(void)
{
pr_debug("px4_module_exit\n");
usb_deregister(&px4_usb_driver);
class_destroy(px4_class);
unregister_chrdev_region(px4_dev_first, MAX_TSDEV);
pr_debug("px4_module_exit: quit\n");
}
module_init(px4_module_init);
module_exit(px4_module_exit);