// SPDX-License-Identifier: GPL-2.0
// tm6000-core.c - driver for TM5600/TM6000/TM6010 USB video capture devices
//
// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
//
// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
// - DVB-T support
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>
#include "tm6000.h"
#include "tm6000-regs.h"
#include <media/v4l2-common.h>
#include <media/tuner.h>
#define USB_TIMEOUT (5 * HZ) /* ms */
int tm6000_read_write_usb(struct tm6000_core *dev, u8 req_type, u8 req,
u16 value, u16 index, u8 *buf, u16 len)
{
int ret, i;
unsigned int pipe;
u8 *data = NULL;
int delay = 5000;
if (len) {
data = kzalloc(len, GFP_KERNEL);
if (!data)
return -ENOMEM;
}
mutex_lock(&dev->usb_lock);
if (req_type & USB_DIR_IN)
pipe = usb_rcvctrlpipe(dev->udev, 0);
else {
pipe = usb_sndctrlpipe(dev->udev, 0);
memcpy(data, buf, len);
}
if (tm6000_debug & V4L2_DEBUG_I2C) {
printk(KERN_DEBUG "(dev %p, pipe %08x): ", dev->udev, pipe);
printk(KERN_CONT "%s: %02x %02x %02x %02x %02x %02x %02x %02x ",
(req_type & USB_DIR_IN) ? " IN" : "OUT",
req_type, req, value&0xff, value>>8, index&0xff,
index>>8, len&0xff, len>>8);
if (!(req_type & USB_DIR_IN)) {
printk(KERN_CONT ">>> ");
for (i = 0; i < len; i++)
printk(KERN_CONT " %02x", buf[i]);
printk(KERN_CONT "\n");
}
}
ret = usb_control_msg(dev->udev, pipe, req, req_type, value, index,
data, len, USB_TIMEOUT);
if (req_type & USB_DIR_IN)
memcpy(buf, data, len);
if (tm6000_debug & V4L2_DEBUG_I2C) {
if (ret < 0) {
if (req_type & USB_DIR_IN)
printk(KERN_DEBUG "<<< (len=%d)\n", len);
printk(KERN_CONT "%s: Error #%d\n", __func__, ret);
} else if (req_type & USB_DIR_IN) {
printk(KERN_CONT "<<< ");
for (i = 0; i < len; i++)
printk(KERN_CONT " %02x", buf[i]);
printk(KERN_CONT "\n");
}
}
kfree(data);
if (dev->quirks & TM6000_QUIRK_NO_USB_DELAY)
delay = 0;
if (req == REQ_16_SET_GET_I2C_WR1_RDN && !(req_type & USB_DIR_IN)) {
unsigned int tsleep;
/* Calculate delay time, 14000us for 64 bytes */
tsleep = (len * 200) + 200;
if (tsleep < delay)
tsleep = delay;
usleep_range(tsleep, tsleep + 1000);
}
else if (delay)
usleep_range(delay, delay + 1000);
mutex_unlock(&dev->usb_lock);
return ret;
}
int tm6000_set_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
return
tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR,
req, value, index, NULL, 0);
}
EXPORT_SYMBOL_GPL(tm6000_set_reg);
int tm6000_get_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
int rc;
u8 buf[1];
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, index, buf, 1);
if (rc < 0)
return rc;
return *buf;
}
EXPORT_SYMBOL_GPL(tm6000_get_reg);
int tm6000_set_reg_mask(struct tm6000_core *dev, u8 req, u16 value,
u16 index, u16 mask)
{
int rc;
u8 buf[1];
u8 new_index;
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, 0, buf, 1);
if (rc < 0)
return rc;
new_index = (buf[0] & ~mask) | (index & mask);
if (new_index == buf[0])
return 0;
return tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR,
req, value, new_index, NULL, 0);
}
EXPORT_SYMBOL_GPL(tm6000_set_reg_mask);
int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
int rc;
u8 buf[2];
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, index, buf, 2);
if (rc < 0)
return rc;
return buf[1]|buf[0]<<8;
}
int tm6000_get_reg32(struct tm6000_core *dev, u8 req, u16 value, u16 index)
{
int rc;
u8 buf[4];
rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
value, index, buf, 4);
if (rc < 0)
return rc;
return buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24;
}
int tm6000_i2c_reset(struct tm6000_core *dev, u16 tsleep)
{
int rc;
rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_CLK, 0);
if (rc < 0)
return rc;
msleep(tsleep);
rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PI