diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 0f5a49fc7c9e0e1029086883415fcb8dd3007d56..5cfe340dafdb7c5578e900c9b7a3c5371b1a299d 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -487,4 +487,5 @@ source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" source "drivers/misc/pvpanic/Kconfig" +source "drivers/misc/seconorth/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a086197af54470227a4448f0840c956ce8bf67db..efce51d029a0386f01cb8481bf28d877c1fdfbbc 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o +obj-y += seconorth/ diff --git a/drivers/misc/seconorth/Kconfig b/drivers/misc/seconorth/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..a8a21bb7e93a9b87f4d992f3765774dcf10ccfc1 --- /dev/null +++ b/drivers/misc/seconorth/Kconfig @@ -0,0 +1,24 @@ +menuconfig SECONORTH + bool "Drivers by Seco NE" + +if SECONORTH +config KUK_TR8MCU + tristate "KUK Trizeps VIII Kinetis MCU support" + depends on OF && I2C + help + This enables support for GPIOs handled by the MCU + and also resistive touchscreens. + + Say Y if you dare. + If unsure, say N. + To compile this driver as a module, say M. + +config KUK_TR8FPGA + tristate "KuK tr8fpga DSI to RGB bridge driver" + depends on OF && I2C && DRM + select DRM_MIPI_DSI + select DRM_PANEL + help + Say Yes if you dare. + +endif diff --git a/drivers/misc/seconorth/Makefile b/drivers/misc/seconorth/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d848605bf605db57f30dd3fc465fcad27a310015 --- /dev/null +++ b/drivers/misc/seconorth/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_KUK_TR8MCU) += kuk_tr8mcu_touch.o +obj-$(CONFIG_KUK_TR8FPGA) += tr8fpga-dsi2rgb.o diff --git a/drivers/misc/seconorth/kuk_tr8mcu_touch.c b/drivers/misc/seconorth/kuk_tr8mcu_touch.c new file mode 100644 index 0000000000000000000000000000000000000000..9156e16f0d984c027575528247dd95d6611b0175 --- /dev/null +++ b/drivers/misc/seconorth/kuk_tr8mcu_touch.c @@ -0,0 +1,1243 @@ +/* + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This is a driver for the Trizeps VIII Kinetis MCU resistive touch firmware + */ + +#include <linux/module.h> +#include <linux/ratelimit.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#define DEBUG_REPORTS 0 +#define MAX_SUPPORT_POINTS 1 + +#define MCU_REG_ID 0x01 +#define MCU_REG_CONTROL 0x02 +#define MCU_REG_CONFIG1 0x03 +#define MCU_REG_PIN_CONFIG 0x04 +#define MCU_REG_PIN_SET 0x05 +#define MCU_REG_PIN_GET 0x06 +#define MCU_REG_ADC_CONFIG 0x10 +#define MCU_REG_ADC 0x11 +#define MCU_REG_LSB 0x12 +#define MCU_REG_MSB 0x13 + +#define TOUCH_CONFIG 0x20 +#define TOUCH_WAITTIME 0x21 +#define TOUCH_XSCALE_LSB 0x22 +#define TOUCH_XSCALE_MSB 0x23 +#define TOUCH_YSCALE_LSB 0x24 +#define TOUCH_YSCALE_MSB 0x25 +#define TOUCH_XMIN_LSB 0x26 +#define TOUCH_XMIN_MSB 0x27 +#define TOUCH_YMIN_LSB 0x28 +#define TOUCH_YMIN_MSB 0x29 +#define TOUCH_XMAX_Z_LSB 0x2A +#define TOUCH_XMAX_Z_MSB 0x2B +#define TOUCH_YMAX_LSB 0x2C +#define TOUCH_YMAX_MSB 0x2D +#define TOUCH_AVERAGE 0x2E + +#define MCU_REG_CONFIG1_RESET2USBBOOT 0x01 +#define MCU_REG_CONFIG1_SDVSEL 0x02 + +enum t_ { + T_XSCALE, + T_YSCALE, + T_XMIN, + T_XMAX, + T_YMIN, + T_YMAX, + T_X, + T_Y, + T_XRAW, + T_YRAW, + T_ZRAW, + T_THRESHOLD, + T_SETTLE, + T_AVERAGE, + T_SWAPXY, + T_ADC0, + T_ADC1, + T_ADC2, + T_ADC3, + T_TSMX, + T_TSPX, + T_TSMY, + T_TSPY +}; + + +#define KUK_SWAPXY 0x1 +#define KUK_XNEG 0x2 +#define KUK_YNEG 0x4 +#define KUK_NAME_LEN 23 + +/* 0 1 2 3 4 5 6 7 8 9 10 11, 12, 13, 14, 15 */ +int32_t gpio_to_pin[] = { 2, 4, 6, 8, 14, 16, 18, 20, 87, 97, 99, -1, -1, -1, -1, -1 }; + + +int tr8_read_aux_adc(struct i2c_client *client, int adcsel); + +struct mcu_gpio { + struct gpio_chip chip; + struct irq_chip irqchip; + struct i2c_client *client; + struct mutex lock; /* protect 'out' */ + unsigned out; /* software latch */ + unsigned status; /* current status */ + unsigned int irq_parent; + unsigned irq_enabled; /* enabled irqs */ +}; + +struct kuk_tr8mcu_ts_data { + struct i2c_client *client; + struct input_dev *input; + struct touchscreen_properties prop; + + int reset_pin; + int enable_touch; + int enable_wakeup; + int irq_pin; + int swapxy; + + struct mutex mutex; + + u16 xscale; + u16 yscale; + u16 xmin; + u16 ymin; + u16 xmax; + u16 ymax; + u8 threshold; + u8 settle; + u8 average; + + u8 reg_config1; + + char name[KUK_NAME_LEN]; + + int driver_suspended; + int driver_sendbuttonup; + struct mcu_gpio tr8mcugpio; + + struct regulator_desc mcu_regulator_desc; + struct regulator_dev *mcu_regulator_dev; +}; + +struct kuk_tr8mcu_attribute { + struct device_attribute dattr; + u16 limit_low; + u16 limit_high; + u8 addr; +}; +static int kuk_tr8mcu_ts_readwrite(struct i2c_client *client, + u16 wr_len, u8 *wr_buf, + u16 rd_len, u8 *rd_buf) +{ + struct i2c_msg wrmsg[2]; + int i = 0; + int ret; + if( client == NULL ) + { + printk(KERN_ERR "kuk_tr8mcu_ts_readwrite Client=0 \n"); + return(-1); + } + + if( client->adapter == 0) + { + printk(KERN_ERR "kuk_tr8mcu_ts_readwrite client->adapter=0 \n"); + return(-1); + } + + if (wr_len) { + wrmsg[i].addr = client->addr; + wrmsg[i].flags = 0; + wrmsg[i].len = wr_len; + wrmsg[i].buf = wr_buf; + i++; + } + if (rd_len) { + wrmsg[i].addr = client->addr; + wrmsg[i].flags = I2C_M_RD; + wrmsg[i].len = rd_len; + wrmsg[i].buf = rd_buf; + i++; + } + + ret = i2c_transfer(client->adapter, wrmsg, i); + if (ret < 0) + return ret; + if (ret != i) + return -EIO; + + return 0; +} + +static int kuk_tr8mcu_ts_read8(struct i2c_client *client, u8 reg) +{ + u8 data; + int datalen = sizeof( data); + int error; + error = kuk_tr8mcu_ts_readwrite( client, + sizeof(reg), ®, + datalen, (u8*)&data); + if (error) { + dev_err_ratelimited(&client->dev, "kuk_tr8mcu_ts_read8: Unable to fetch data, error: %d\n", + error); + return 0; + } + return data; +} +static int kuk_tr8mcu_ts_read16(struct i2c_client *client, u8 reg) +{ + u16 data; + int datalen = sizeof( data); + int error; + error = kuk_tr8mcu_ts_readwrite( client, + sizeof(reg), ®, + datalen, (u8*)&data); + if (error) { + dev_err_ratelimited(&client->dev, "kuk_tr8mcu_ts_read16: Unable to fetch data, error: %d\n", + error); + return 0; + } + return data; +} +static int kuk_tr8mcu_ts_read24(struct i2c_client *client, u8 reg) +{ + u32 data = 0; + int datalen = 3; + int error; + error = kuk_tr8mcu_ts_readwrite( client, + sizeof(reg), ®, + datalen, (u8*)&data); + if (error) { + dev_err_ratelimited(&client->dev, "kuk_tr8mcu_ts_read24: Unable to fetch data, error: %d\n", + error); + return 0; + } + return data; +} +static int kuk_tr8mcu_ts_write16(struct i2c_client *client, u8 reg, u16 data ) +{ + u8 buf[3]; + buf[0] = reg; + buf[1] = data & 0xFF; + buf[2] = (data >> 8)&0xFF; + return kuk_tr8mcu_ts_readwrite( client, + 3, &buf[0], + 0, NULL); +} + +static int kuk_tr8mcu_ts_write8(struct i2c_client *client, u8 reg, u8 data ) +{ + u8 buf[2]; + buf[0] = reg; + buf[1] = data; + return kuk_tr8mcu_ts_readwrite( client, + 2, &buf[0], + 0, NULL); +} + + +static int kuk_tr8mcu_gpio_read(struct gpio_chip *chip, unsigned int offset) +{ + struct mcu_gpio *gpio = gpiochip_get_data(chip); + u8 level, wbuf[4]; + int sodimmpin=gpio_to_pin[offset&0x0f]; + int error=0; + + if( sodimmpin == -1 ) + return(sodimmpin); + + wbuf[0] = MCU_REG_PIN_GET; + wbuf[1] = sodimmpin; + + level = 0; + error = kuk_tr8mcu_ts_readwrite(gpio->client, 2, &wbuf[0], 1, (u8*) &wbuf[0]); + + level = wbuf[0]; + + printk(KERN_ERR "kuk_tr8mcu_gpio_read %d(sodimm %d)=%d [0x%02x, 0x%02x 0x%02x, 0x%02x]***\n", + offset, sodimmpin, level,wbuf[0],wbuf[1],wbuf[2],wbuf[3]); + + if (error) { + dev_err(&gpio->client->dev, "Unable to fetch MCU-pin: %d err:%d\n",sodimmpin, error); + return 0; + } + return level; +} + +static int kuk_tr8mcu_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int level) +{ + struct mcu_gpio *gpio= gpiochip_get_data(chip); + u8 buf[3]; + int pin=gpio_to_pin[offset&0x0f]; + + if( pin == -1 ) + return(pin); + + buf[0] = MCU_REG_PIN_CONFIG; + buf[1] = pin & 0xFF; + if( level ) buf[2] = 21; /* Alt1-Output High */ + else buf[2] = 20; /* Alt1-Output Low */ + + printk(KERN_ERR "kuk_tr8mcu_gpio_direction_output %d(sodimm %d)=%d ***\n", offset, pin, level); + return(kuk_tr8mcu_ts_readwrite(gpio->client,3, &buf[0],0,NULL)); +} + +static void kuk_tr8mcu_gpio_write(struct gpio_chip *chip, unsigned offset, int level) +{ +#if 1 + /* Works, seems, taht mcu-FW needs a fix.... */ + kuk_tr8mcu_gpio_direction_output(chip, offset, level); +#else + struct mcu_gpio *gpio= gpiochip_get_data(chip); + u8 buf[3]; + int sodimmpin=gpio_to_pin[offset&0x0f]; + + if( sodimmpin == -1 ) + return; + + buf[0] = MCU_REG_PIN_SET; + buf[1] = sodimmpin & 0xFF; + buf[2] = level & 0xff; + + printk(KERN_ERR "kuk_tr8mcu_gpio_write %d(sodimm %d) %d ***\n", offset, sodimmpin, level); + kuk_tr8mcu_ts_readwrite(gpio->client, 3, &buf[0], 0, NULL); +#endif +} + + +static int kuk_tr8mcu_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct mcu_gpio *gpio= gpiochip_get_data(chip); + u8 buf[3]; + int pin=gpio_to_pin[offset&0x0f]; + + if( pin == -1 ) + return(pin); + + buf[0] = MCU_REG_PIN_CONFIG; + buf[1] = pin & 0xFF; + buf[2] = 1; /* Alt1=Input */ + + printk(KERN_ERR "kuk_tr8mcu_gpio_direction_input %d(sodimm %d) ***\n", offset, pin); + return(kuk_tr8mcu_ts_readwrite(gpio->client, 3, &buf[0], 0, NULL)); +} + +/* + * Touch Protocol: + * on command, the controller sends a data load with a predefined length + * with a predefined structure + */ +static irqreturn_t kuk_tr8mcu_ts_isr(int irq, void *dev_id) +{ + struct kuk_tr8mcu_ts_data *tsdata = dev_id; + struct device *dev = &tsdata->client->dev; + int x, y, datalen; + int error; + u8 cmd; + u8 rdbuf[8]; + + // printk(KERN_ERR "*** TouchScreen IRQ ***\n"); + + memset(rdbuf, 0, sizeof(rdbuf)); + cmd = TOUCH_XSCALE_LSB; + datalen = 8; // 16bit x, 16bit y + error = kuk_tr8mcu_ts_readwrite(tsdata->client, + sizeof(cmd), &cmd, + datalen, rdbuf); + if (error) { + dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n", + error); + goto out; + } + + x = (rdbuf[0] | (rdbuf[1]<<8)) & 0xFFFF; + y = (rdbuf[2] | (rdbuf[3]<<8)) & 0xFFFF; + + if (((x|y) & 0x8000)!=0) + { // invalid / pen-up + input_mt_slot(tsdata->input, 0); + input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, false); // pen up +#if DEBUG_REPORTS + dev_err(&tsdata->client->dev,"Touch UP\n"); +#endif + + }else + { + if( tsdata->swapxy & KUK_XNEG ) x = tsdata->xscale - x; + if( tsdata->swapxy & KUK_YNEG ) y = tsdata->yscale - y; + if( tsdata->swapxy & KUK_SWAPXY) + { + int t; + t = x; + x = y; + y = t; + } + input_mt_slot(tsdata->input, 0); + input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, true); // pen down + input_report_abs(tsdata->input, ABS_MT_POSITION_X, x); + input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y); + + //touchscreen_report_pos(tsdata->input, &tsdata->prop, x, y, true); +#if DEBUG_REPORTS + dev_err(&tsdata->client->dev,"Touch [%d,%d]\n",x,y); +#endif + } + + + if(tsdata->enable_wakeup ) + { + if(tsdata->driver_sendbuttonup!=0) + { + // printk(KERN_ERR "*** TouchScreen Send PowerButton Up ***\n"); + input_event(tsdata->input, EV_KEY, KEY_POWER, 0); + tsdata->driver_sendbuttonup++; + if( tsdata->driver_suspended == 0 || tsdata->driver_sendbuttonup > 10 ) + tsdata->driver_sendbuttonup=0; + }else + if( tsdata->driver_suspended) + { + // printk(KERN_ERR "*** TouchScreen IRQ in Sleep ***\n"); + input_event(tsdata->input, EV_KEY, KEY_POWER, 1); + tsdata->driver_sendbuttonup=1; + } + } + + input_mt_report_pointer_emulation(tsdata->input, true); + input_sync(tsdata->input); + +out: + return IRQ_HANDLED; +} + +static ssize_t kuk_tr8mcu_setting_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kuk_tr8mcu_ts_data *tsdata = i2c_get_clientdata(client); + struct kuk_tr8mcu_attribute *attr = + container_of(dattr, struct kuk_tr8mcu_attribute, dattr); + int val; + size_t count = 0; + int error = 0; + + mutex_lock(&tsdata->mutex); + + switch( attr->addr) + { + case T_XSCALE: + val = tsdata->xscale; + break; + case T_YSCALE: + val = tsdata->yscale; + break; + case T_XMIN: + val = tsdata->xmin; + break; + case T_XMAX: + val = tsdata->xmax; + break; + case T_YMIN: + val = tsdata->ymin; + break; + case T_YMAX: + val = tsdata->ymax; + break; + case T_THRESHOLD: + val = tsdata->threshold; + break; + case T_SETTLE: + val = tsdata->settle; + break; + case T_SWAPXY: + val = tsdata->swapxy; + break; + case T_AVERAGE: + val = tsdata->average; + break; + case T_X: + val = kuk_tr8mcu_ts_read16(tsdata->client, TOUCH_XSCALE_LSB); + break; + case T_Y: + val = kuk_tr8mcu_ts_read16(tsdata->client, TOUCH_YSCALE_LSB); + break; + case T_XRAW: + val = kuk_tr8mcu_ts_read16(tsdata->client, TOUCH_XMIN_LSB); + break; + case T_YRAW: + val = kuk_tr8mcu_ts_read16(tsdata->client, TOUCH_YMIN_LSB); + break; + case T_ZRAW: + val = kuk_tr8mcu_ts_read16(tsdata->client, TOUCH_XMAX_Z_LSB); + break; + case T_ADC0: + val = tr8_read_aux_adc(tsdata->client, 8); + break; + case T_ADC1: + val = tr8_read_aux_adc(tsdata->client, 6); + break; + case T_ADC2: + val = tr8_read_aux_adc(tsdata->client, 4); + break; + case T_ADC3: + val = tr8_read_aux_adc(tsdata->client, 2); + break; + case T_TSMX: + val = tr8_read_aux_adc(tsdata->client, 16); + break; + case T_TSPX: + val = tr8_read_aux_adc(tsdata->client, 14); + break; + case T_TSMY: + val = tr8_read_aux_adc(tsdata->client, 20); + break; + case T_TSPY: + val = tr8_read_aux_adc(tsdata->client, 18); + break; + default: + val = 0; + break; + } + + count = scnprintf(buf, PAGE_SIZE, "%d\n", val); + + mutex_unlock(&tsdata->mutex); + return error ?: count; +} + +static ssize_t kuk_tr8mcu_setting_store(struct device *dev, + struct device_attribute *dattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kuk_tr8mcu_ts_data *tsdata = i2c_get_clientdata(client); + struct kuk_tr8mcu_attribute *attr = + container_of(dattr, struct kuk_tr8mcu_attribute, dattr); + unsigned int val; + int error; + + mutex_lock(&tsdata->mutex); + + error = kstrtouint(buf, 0, &val); + if (error) + goto out; + + if (val < attr->limit_low || val > attr->limit_high) { + error = -ERANGE; + goto out; + } + + + switch( attr->addr) + { + case T_XSCALE: + tsdata->xscale = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_XSCALE_LSB, tsdata->xscale ); + break; + case T_YSCALE: + tsdata->yscale = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_YSCALE_LSB, tsdata->yscale ); + break; + case T_XMIN: + tsdata->xmin = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_XMIN_LSB, tsdata->xmin ); + break; + case T_XMAX: + tsdata->xmax = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_XMAX_Z_LSB, tsdata->xmax ); + break; + case T_YMIN: + tsdata->ymin = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_YMIN_LSB, tsdata->ymin ); + break; + case T_YMAX: + tsdata->ymax = val; + kuk_tr8mcu_ts_write16(tsdata->client, TOUCH_YMAX_LSB, tsdata->ymax ); + break; + case T_THRESHOLD: + tsdata->threshold = val & 0x7F; + kuk_tr8mcu_ts_write8(tsdata->client, TOUCH_CONFIG, tsdata->threshold | (tsdata->enable_touch?0x80:0x00)); // 0x80: Enable Touch, 0x40 Do Z measurement + break; + case T_SETTLE: + tsdata->settle = val; + kuk_tr8mcu_ts_write8(tsdata->client, TOUCH_WAITTIME, tsdata->settle ); + break; + case T_AVERAGE: + tsdata->average = val; + kuk_tr8mcu_ts_write8(tsdata->client, TOUCH_AVERAGE, tsdata->average ); + break; + case T_SWAPXY: + tsdata->swapxy = val; + break; + case T_X: + break; + case T_Y: + break; + case T_XRAW: + break; + case T_YRAW: + break; + case T_ZRAW: + break; + case T_ADC0: + break; + case T_ADC1: + break; + case T_ADC2: + break; + case T_ADC3: + break; + case T_TSMX: + break; + case T_TSPX: + break; + case T_TSMY: + break; + case T_TSPY: + break; + } +out: + mutex_unlock(&tsdata->mutex); + return error ?: count; +} + +#define KUK_ATTR(_field, _mode, _addr, _limit_low, _limit_high) \ + struct kuk_tr8mcu_attribute kuk_tr8mcu_attr_##_field = { \ + .dattr = __ATTR(_field, _mode, \ + kuk_tr8mcu_setting_show, \ + kuk_tr8mcu_setting_store), \ + .addr = _addr, \ + .limit_low = _limit_low, \ + .limit_high = _limit_high \ + } + +static KUK_ATTR( xscale , S_IWUSR | S_IRUGO , T_XSCALE , 0 , 4095); +static KUK_ATTR( yscale , S_IWUSR | S_IRUGO , T_YSCALE , 0 , 4095); +static KUK_ATTR( xmin , S_IWUSR | S_IRUGO , T_XMIN , 0 , 0xFFFF); +static KUK_ATTR( xmax , S_IWUSR | S_IRUGO , T_XMAX , 0 , 0xFFFF); +static KUK_ATTR( ymin , S_IWUSR | S_IRUGO , T_YMIN , 0 , 0xFFFF); +static KUK_ATTR( ymax , S_IWUSR | S_IRUGO , T_YMAX , 0 , 0xFFFF); +static KUK_ATTR( threshold , S_IWUSR | S_IRUGO , T_THRESHOLD , 0 , 64); +static KUK_ATTR( settle , S_IWUSR | S_IRUGO , T_SETTLE , 0 , 255); +static KUK_ATTR( average , S_IWUSR | S_IRUGO , T_AVERAGE , 0 , 0xFFFF); +static KUK_ATTR( swapxy , S_IWUSR | S_IRUGO , T_SWAPXY , 0 , 16); +static KUK_ATTR( x , S_IWUSR | S_IRUGO , T_X , 0 , 0); +static KUK_ATTR( y , S_IWUSR | S_IRUGO , T_Y , 0 , 0); +static KUK_ATTR( xraw , S_IWUSR | S_IRUGO , T_XRAW , 0 , 0); +static KUK_ATTR( yraw , S_IWUSR | S_IRUGO , T_YRAW , 0 , 0); +static KUK_ATTR( zraw , S_IWUSR | S_IRUGO , T_ZRAW , 0 , 0); +static KUK_ATTR( adc0 , S_IWUSR | S_IRUGO , T_ADC0 , 0 , 0); +static KUK_ATTR( adc1 , S_IWUSR | S_IRUGO , T_ADC1 , 0 , 0); +static KUK_ATTR( adc2 , S_IWUSR | S_IRUGO , T_ADC2 , 0 , 0); +static KUK_ATTR( adc3 , S_IWUSR | S_IRUGO , T_ADC3 , 0 , 0); +static KUK_ATTR( tsmx , S_IWUSR | S_IRUGO , T_TSMX , 0 , 0); +static KUK_ATTR( tspx , S_IWUSR | S_IRUGO , T_TSPX , 0 , 0); +static KUK_ATTR( tsmy , S_IWUSR | S_IRUGO , T_TSMY , 0 , 0); +static KUK_ATTR( tspy , S_IWUSR | S_IRUGO , T_TSPY , 0 , 0); + + +static struct attribute *kuk_tr8mcu_attrs[] = +{ + &kuk_tr8mcu_attr_xscale.dattr.attr, + &kuk_tr8mcu_attr_yscale.dattr.attr, + &kuk_tr8mcu_attr_xmin.dattr.attr, + &kuk_tr8mcu_attr_xmax.dattr.attr, + &kuk_tr8mcu_attr_ymin.dattr.attr, + &kuk_tr8mcu_attr_ymax.dattr.attr, + &kuk_tr8mcu_attr_threshold.dattr.attr, + &kuk_tr8mcu_attr_settle.dattr.attr, + &kuk_tr8mcu_attr_average.dattr.attr, + &kuk_tr8mcu_attr_swapxy.dattr.attr, + &kuk_tr8mcu_attr_x.dattr.attr, + &kuk_tr8mcu_attr_y.dattr.attr, + &kuk_tr8mcu_attr_xraw.dattr.attr, + &kuk_tr8mcu_attr_yraw.dattr.attr, + &kuk_tr8mcu_attr_zraw.dattr.attr, + &kuk_tr8mcu_attr_adc0.dattr.attr, + &kuk_tr8mcu_attr_adc1.dattr.attr, + &kuk_tr8mcu_attr_adc2.dattr.attr, + &kuk_tr8mcu_attr_adc3.dattr.attr, + &kuk_tr8mcu_attr_tsmx.dattr.attr, + &kuk_tr8mcu_attr_tspx.dattr.attr, + &kuk_tr8mcu_attr_tsmy.dattr.attr, + &kuk_tr8mcu_attr_tspy.dattr.attr, + NULL +}; + +static const struct attribute_group kuk_tr8mcu_attr_group = { + .attrs = kuk_tr8mcu_attrs, +}; + + +#ifdef CONFIG_OF +static int kuk_tr8mcu_i2c_ts_probe_dt(struct device *dev, + struct kuk_tr8mcu_ts_data *tsdata) +{ + struct device_node *np = dev->of_node; + const char *prop; + u32 val; + /* + * irq_pin is not needed for DT setup. + * irq is associated via 'interrupts' property in DT + */ + tsdata->reset_pin = of_get_named_gpio(np, "reset-gpios", 0); + + prop = of_get_property(np, "swapxy", NULL); + if (prop && (strcmp(prop, "true")==0)) tsdata->swapxy |= KUK_SWAPXY; + + prop = of_get_property(np, "xreverse", NULL); + if (prop && (strcmp(prop, "true")==0)) tsdata->swapxy |= KUK_XNEG; + + prop = of_get_property(np, "yreverse", NULL); + if (prop && (strcmp(prop, "true")==0)) tsdata->swapxy |= KUK_YNEG; + + prop = of_get_property(np, "enable_wakeup", NULL); + if (prop && (strcmp(prop, "true")==0)) tsdata->enable_wakeup = 1; + else tsdata->enable_wakeup = 0; + + prop = of_get_property(np, "enable_touch", NULL); + if (prop && (strcmp(prop, "true")==0)) tsdata->enable_touch = 1; + else tsdata->enable_touch = 0; + + if ( of_property_read_u32(np, "xscale" , &val ) == 0) + tsdata->xscale = (u16)val; + if ( of_property_read_u32(np, "yscale" , &val ) == 0) + tsdata->yscale = (u16)val; + if ( of_property_read_u32(np, "xmin" , &val ) == 0) + tsdata->xmin = (u16)val; + if ( of_property_read_u32(np, "xmax" , &val ) == 0) + tsdata->xmax = (u16)val; + if ( of_property_read_u32(np, "ymin" , &val ) == 0) + tsdata->ymin = (u16)val; + if ( of_property_read_u32(np, "ymax" , &val ) == 0) + tsdata->ymax = (u16)val; + if ( of_property_read_u32(np, "threshold", &val ) == 0) + tsdata->threshold = (u8)val; + if ( of_property_read_u32(np, "settle" , &val ) == 0) + tsdata->settle = (u8)val; + if ( of_property_read_u32(np, "average" , &val ) == 0) + tsdata->average = (u8)val; + + return 0; +} +#else +static inline int kuk_tr8mcu_i2c_ts_probe_dt(struct device *dev, + struct kuk_tr8mcu_ts_data *tsdata) +{ + return -ENODEV; +} +#endif + +extern int handle_reserved_delayed(void); +extern int handle_reserved_mcu(void); + +static int probe_mcu_gpio(struct i2c_client *client, + struct kuk_tr8mcu_ts_data *tsdata) +{ + struct mcu_gpio *gpio; + int status; + + printk(KERN_ERR "probe_mcu_gpio *********************************\n"); + + gpio = &tsdata->tr8mcugpio; + + mutex_init(&gpio->lock); + gpio->chip.can_sleep = true; + gpio->chip.parent = &client->dev; + gpio->chip.label = client->name; + gpio->chip.owner = THIS_MODULE; + gpio->chip.get = kuk_tr8mcu_gpio_read; + gpio->chip.set = kuk_tr8mcu_gpio_write; + gpio->chip.direction_input = kuk_tr8mcu_gpio_direction_input; + gpio->chip.direction_output = kuk_tr8mcu_gpio_direction_output; + gpio->chip.base = 320; + gpio->chip.ngpio = 11; + gpio->chip.request = NULL; // kuk_tr8mcu_gpio_request; + gpio->client = client; + if( client->adapter ) + { + gpio->client->adapter = client->adapter; + }else + { + printk("KERN_ERR *** probe_mcu_gpio No client->adapter \n"); + } + // i2c_set_clientdata(client, tsdata); + + if( kuk_tr8mcu_ts_read8(client, MCU_REG_ID) != 0x61 ) + { + printk(KERN_ERR " *** probe_mcu_gpio No MCU found ! \n"); + return -ENODEV; + } + + status = devm_gpiochip_add_data(&gpio->client->dev, &gpio->chip, gpio); + //status = devm_gpiochip_add_data(&client->dev, &gpio->chip, gpio); + + if (status < 0) + goto fail; + + printk(KERN_ERR "probe_mcu_gpio success !***\n"); + //return handle_reserved_mcu(); + return 0; + //return(0); + + +fail: + dev_dbg(&client->dev, "probe_mcu_gpio error %d for '%s'\n", status, + client->name); + + return status; + +} + +DEFINE_MUTEX( sync_kuk_tr8mcu_config1_mutex); +static int mcu_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct kuk_tr8mcu_ts_data *tsdata = rdev_get_drvdata(rdev); + + if ( tsdata->reg_config1 & MCU_REG_CONFIG1_SDVSEL) + return 1800000; + else + return 3300000; + +} +static int mcu_regulator_set_voltage(struct regulator_dev *rdev, + int req_min_uV, int req_max_uV, + unsigned int *selector) +{ + struct kuk_tr8mcu_ts_data *tsdata = rdev_get_drvdata(rdev); + int new_config; + + mutex_lock(&sync_kuk_tr8mcu_config1_mutex); + new_config = tsdata->reg_config1; + if (( req_min_uV <= 1800000)&&( 1800000 <= req_max_uV)) + { + new_config |= MCU_REG_CONFIG1_SDVSEL; + dev_dbg(&tsdata->client->dev, "[MCU] Set SDCard voltage to 1.8V.\n"); + }else + if (( req_min_uV <= 3300000)&&( 3300000 <= req_max_uV)) + { + new_config &= ~MCU_REG_CONFIG1_SDVSEL; + dev_dbg(&tsdata->client->dev, "[MCU] Set SDCard voltage to 3.3V.\n"); + }else + { + mutex_unlock(&sync_kuk_tr8mcu_config1_mutex); + dev_err(&tsdata->client->dev, "[MCU] Set Voltage unsupported %d..%dmV\n", req_min_uV, req_max_uV); + return -EINVAL; + } + if ( new_config != tsdata->reg_config1) + { + tsdata->reg_config1 = new_config; + dev_dbg(&tsdata->client->dev, "[MCU] Set MCU_REG_CONFIG1=0x%x\n", tsdata->reg_config1); + kuk_tr8mcu_ts_write8( tsdata->client, MCU_REG_CONFIG1, tsdata->reg_config1); + } + mutex_unlock(&sync_kuk_tr8mcu_config1_mutex); + + return 0; +} +static int mcu_regulator_enable(struct regulator_dev *dev) +{ + return 0; +} + +static int mcu_regulator_disable(struct regulator_dev *dev) +{ + return 0; +} + +static int mcu_regulator_is_enabled(struct regulator_dev *dev) +{ + return true; +} + +static const struct regulator_ops mcu_regulator_ops = { + .get_voltage = mcu_regulator_get_voltage, + .set_voltage = mcu_regulator_set_voltage, + .enable = mcu_regulator_enable, + .disable = mcu_regulator_disable, + .is_enabled = mcu_regulator_is_enabled, + .map_voltage = regulator_map_voltage_linear, + .list_voltage = regulator_list_voltage_linear, +}; +static int probe_mcu_regulator(struct i2c_client *client, + struct kuk_tr8mcu_ts_data *tsdata) +{ + struct regulator_desc *rdesc = &tsdata->mcu_regulator_desc; + struct regulator_config cfg = { }; + const struct regulator_init_data *init_data; + int id=0; + + dev_dbg(&client->dev, "MCU probe regulator\n"); + rdesc->name = "mcu-reg"; + rdesc->supply_name = "vdd_sdcard"; + rdesc->ops = &mcu_regulator_ops; + rdesc->type = REGULATOR_VOLTAGE; + rdesc->n_voltages = 2; + rdesc->min_uV = 1800000; + rdesc->uV_step = (3300000-1800000); + rdesc->owner = THIS_MODULE; + + init_data = of_get_regulator_init_data(&client->dev, client->dev.of_node, rdesc); + cfg.dev = &client->dev; + cfg.init_data = init_data; + cfg.driver_data = tsdata; + cfg.of_node = client->dev.of_node; + + id = kuk_tr8mcu_ts_read8(tsdata->client, MCU_REG_ID); + if( id != 0x61 ) + { + return -ENODEV; + } + + tsdata->reg_config1 = kuk_tr8mcu_ts_read8(tsdata->client, MCU_REG_CONFIG1); + tsdata->mcu_regulator_dev = devm_regulator_register(&client->dev, rdesc, &cfg); + + if( tsdata->mcu_regulator_dev == NULL ) + return -ENODEV; + + if(IS_ERR(tsdata->mcu_regulator_dev)) + { + dev_dbg(&client->dev, "MCU probe regulator Failed\n"); + // return(PTR_ERR(tsdata->mcu_regulator_dev)); + return -ENODEV; + } + + dev_err(&client->dev, "mcu-reg regulator probe OK!\n"); + + return 0; // PTR_ERR_OR_ZERO(tsdata->mcu_regulator_dev); +} + +struct i2c_client *tr8_adc_get_handle(void) +{ + return(NULL); +} + +DEFINE_MUTEX( sync_kuk_tr8mcu_adc_mutex); + +struct i2c_client *tr8_i2c_client=NULL; +struct kuk_tr8mcu_ts_data *tr8_ts_data=NULL; +struct device *tr8_mcu_dev=NULL; + +struct i2c_client *tr8_adc_get_i2c_client(void) +{ + return(tr8_i2c_client); +} + + + +int tr8_read_aux_adc(struct i2c_client *client, int adcsel) +{ + int data; + int tries = 5; + + mutex_lock(&sync_kuk_tr8mcu_adc_mutex); + { + kuk_tr8mcu_ts_write8( client, MCU_REG_ADC, adcsel); + while ( tries--) + { + data = kuk_tr8mcu_ts_read24( client, MCU_REG_ADC); + if (( data&0xFF) == adcsel) + break; + } + } + mutex_unlock(&sync_kuk_tr8mcu_adc_mutex); + + if (( tries <= 0)||((data&0xFF) != adcsel)) + { + dev_err(&client->dev, "failed to read adc(%d) %d,0x%x.\n", adcsel, tries, data); + return -1; + } + + dev_dbg(&client->dev, "adc%d: %d (tries %d; scaled:%dmV)\n", adcsel, ((data>>8)&0xFFFF), tries, (((data>>8)&0xFFFF)*3300*2)/4096); + return ((data>>8)&0xFFFF); +} + +int tr8_read_adc(int adcsel) +{ + struct i2c_client *client; + + if(tr8_mcu_dev==NULL) return -1; + + client = to_i2c_client(tr8_mcu_dev); + + if(client==NULL) return -1; + + return(tr8_read_aux_adc(client,adcsel)); +} + + +static int kuk_tr8mcu_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct kuk_tr8mcu_ts_data *tsdata; + struct input_dev *input; + int error; + int count __maybe_unused; + + dev_dbg(&client->dev, "probing for KUK TR8MCU Touch I2C\n"); + + tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL); + if (!tsdata) { + dev_err(&client->dev, "failed to allocate driver data.\n"); + return -ENOMEM; + } + + + strcpy(tsdata->name, "TR8MCU_TOUCH"); + tsdata->irq_pin = -EINVAL; + tsdata->swapxy = 0; + + tsdata->xscale = 800; + tsdata->yscale = 480; + tsdata->xmin = 0; + tsdata->xmax = 4095; + tsdata->ymin = 0; + tsdata->ymax = 4095; + tsdata->threshold = 32; + tsdata->settle = 3; + tsdata->average = 2; + + if( kuk_tr8mcu_ts_read8(client, MCU_REG_ID) != 0x61 ) + { + dev_err(&client->dev," *** kuk_tr8mcu_ts_probe No MCU found ! \n"); + return -ENODEV; + } + + error = kuk_tr8mcu_i2c_ts_probe_dt(&client->dev, tsdata); + if (error) { + dev_err(&client->dev, + "DT probe failed and no platform data present\n"); + return error; + } + +#ifdef CONFIG_OF + count = of_property_count_u32_elems(client->dev.of_node, "mcu,gpios"); + if(count > 0) + { + dev_info(&client->dev, "%d mcu,gpios configured\n", count); + if(of_property_read_u32_array(client->dev.of_node, "mcu,gpios", + (int32_t *)gpio_to_pin, count)) + dev_warn(&client->dev, "failed to read mcu,gpios, using defaults\n"); + } + else + dev_info(&client->dev, "mcu,gpios not configured\n"); +#endif + + if (gpio_is_valid(tsdata->irq_pin)) { + error = devm_gpio_request_one(&client->dev, tsdata->irq_pin, + GPIOF_IN, "kuk-tr8mcu irq"); + if (error) { + dev_err(&client->dev, + "Failed to request GPIO %d, error %d\n", + tsdata->irq_pin, error); + return error; + } + } + + input = devm_input_allocate_device(&client->dev); + if (!input) { + dev_err(&client->dev, "failed to allocate input device.\n"); + return -ENOMEM; + } + + mutex_init(&tsdata->mutex); + tsdata->client = client; + tsdata->input = input; + tsdata->driver_suspended = 0; + tsdata->driver_sendbuttonup = 0; + + + printk( KERN_ERR "TR8MCU_TOUCH : swapxy=0x%x enable_wakeup=%d irq_pin=0x%x reset_pin=0x%x\n", + tsdata->swapxy, tsdata->enable_wakeup, tsdata->irq_pin, tsdata->reset_pin); + + + if(kuk_tr8mcu_ts_read8(client, MCU_REG_ID) != 0x61) + { + dev_err(&client->dev, "TR8MCU_TOUCH : probe No MCU found ! \n"); + return -ENODEV; + } + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_XSCALE_LSB , tsdata->xscale); + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_YSCALE_LSB , tsdata->yscale); + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_XMIN_LSB , tsdata->xmin); + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_XMAX_Z_LSB , tsdata->xmax); + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_YMIN_LSB , tsdata->ymin); + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_YMAX_LSB , tsdata->ymax); + kuk_tr8mcu_ts_write8(tsdata->client , TOUCH_CONFIG , tsdata->threshold); + kuk_tr8mcu_ts_write8(tsdata->client , TOUCH_WAITTIME , tsdata->settle); + kuk_tr8mcu_ts_write8(tsdata->client , TOUCH_AVERAGE , tsdata->average); + + input->name = tsdata->name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + + __set_bit(EV_SYN, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_REP, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(KEY_POWER, input->keybit); + + if( tsdata->swapxy & KUK_SWAPXY) + { + input_set_abs_params(input, ABS_Y, 0, tsdata->xscale, 0, 0); + input_set_abs_params(input, ABS_X, 0, tsdata->yscale, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, tsdata->xscale, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, tsdata->yscale, 0, 0); + }else + { + input_set_abs_params(input, ABS_X, 0, tsdata->xscale, 0, 0); + input_set_abs_params(input, ABS_Y, 0, tsdata->yscale, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, 0, tsdata->xscale, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, tsdata->yscale, 0, 0); + } + + touchscreen_parse_properties(input, true, &tsdata->prop); + + error = input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0); + + if (error) { + dev_err(&client->dev, "Unable to init MT slots.\n"); + return error; + } + + input_set_drvdata(input, tsdata); + i2c_set_clientdata(client, tsdata); + if( tsdata->enable_touch != 0 ) + { + kuk_tr8mcu_ts_write16(tsdata->client , TOUCH_CONFIG, tsdata->threshold | (tsdata->enable_touch?0x80:0x00)); // Enable Touch! + + error = devm_request_threaded_irq(&client->dev, client->irq, NULL, + kuk_tr8mcu_ts_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->name, tsdata); + if (error) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + return error; + } + } + + error = sysfs_create_group(&client->dev.kobj, &kuk_tr8mcu_attr_group); + if (error) + return error; + + error = input_register_device(input); + if (error) + goto err_remove_attrs; + + device_init_wakeup(&client->dev, 1); + + dev_err(&client->dev, + "KUK TR8MCU Touch initialized: IRQ %d, Reset Gpio %d. Probe OK\n", + client->irq, tsdata->reset_pin); + + tr8_i2c_client = tsdata->client; + tr8_ts_data = tsdata; + tr8_mcu_dev = &client->dev; +#if 0 + dev_err(&client->dev, "KUK TR8MCU ADC0 = %d\n ", tr8_read_adc( 8 )); + dev_err(&client->dev, "KUK TR8MCU ADC1 = %d\n ", tr8_read_adc( 6 )); + dev_err(&client->dev, "KUK TR8MCU ADC2 = %d\n ", tr8_read_adc( 4 )); + dev_err(&client->dev, "KUK TR8MCU ADC3 = %d\n ", tr8_read_adc( 2 )); +#endif + probe_mcu_gpio(client,tsdata); + probe_mcu_regulator( client, tsdata); + + return 0; + +err_remove_attrs: + sysfs_remove_group(&client->dev.kobj, &kuk_tr8mcu_attr_group); + return error; +} + +static int kuk_tr8mcu_ts_remove(struct i2c_client *client) +{ + sysfs_remove_group(&client->dev.kobj, &kuk_tr8mcu_attr_group); + + return 0; +} + +static int __maybe_unused kuk_tr8mcu_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kuk_tr8mcu_ts_data *tsdata = i2c_get_clientdata(client); + + tsdata->driver_suspended++; + if (device_may_wakeup(dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int __maybe_unused kuk_tr8mcu_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kuk_tr8mcu_ts_data *tsdata = i2c_get_clientdata(client); + tsdata->driver_suspended--; + if (device_may_wakeup(dev)) + disable_irq_wake(client->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(kuk_tr8mcu_ts_pm_ops, + kuk_tr8mcu_ts_suspend, kuk_tr8mcu_ts_resume); + +static const struct i2c_device_id kuk_tr8mcu_ts_id[] = { + { "kuk-tr8mcu-touch", 0, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, kuk_tr8mcu_ts_id); + +#ifdef CONFIG_OF +static const struct of_device_id kuk_tr8mcu_of_match[] = { + { .compatible = "kuk,tr8mcu-touch", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, kuk_tr8mcu_of_match); +#endif + +static struct i2c_driver kuk_tr8mcu_ts_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "kuk_tr8mcu_touch", + .of_match_table = of_match_ptr(kuk_tr8mcu_of_match), + .pm = &kuk_tr8mcu_ts_pm_ops, + }, + .id_table = kuk_tr8mcu_ts_id, + .probe = kuk_tr8mcu_ts_probe, + .remove = kuk_tr8mcu_ts_remove, +}; + +module_i2c_driver(kuk_tr8mcu_ts_driver); + +MODULE_AUTHOR("Sven Hillger <hillger@keith-koep.com>"); +MODULE_DESCRIPTION("KUK TR8MCU I2C Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/seconorth/tr8fpga-dsi2rgb.c b/drivers/misc/seconorth/tr8fpga-dsi2rgb.c new file mode 100644 index 0000000000000000000000000000000000000000..cb3852e55324e5f26b9001d922ac14c04b3f4f52 --- /dev/null +++ b/drivers/misc/seconorth/tr8fpga-dsi2rgb.c @@ -0,0 +1,218 @@ +/* + * Keith & Koep FPGA DSI to RGB bridge driver. + * + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> + +static int tr8fpga_i2c_read(struct i2c_client *client, char *writebuf, + int writelen, char *readbuf, int readlen) +{ + int ret; + + if (writelen > 0) { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + dev_err(&client->dev, "%s: i2c read error.\n", __func__); + } else { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s:i2c read error.\n", __func__); + } + + return ret; +} + +static int tr8fpga_i2c_write(struct i2c_client *client, char *writebuf, + int writelen) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s: i2c write error.\n", __func__); + + return ret; +} + +static int tr8fpga_write_reg(struct i2c_client *client, u8 addr, const u8 val) +{ + u8 buf[2] = {0}; + + buf[0] = addr; + buf[1] = val; + + return tr8fpga_i2c_write(client, buf, sizeof(buf)); +} + +static int tr8fpga_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct property *prop; + int err; + int i, size; + struct device_node *np = client->dev.of_node; + int addresses[100]; + int values[100]; + char address, value; + struct gpio_desc *enable_gpio; + + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); + if (enable_gpio) + gpiod_set_value_cansleep(enable_gpio, 1); + + address = (char)0; + err = tr8fpga_i2c_read(client, &address, 1, &value, 1); + if (err < 0) { + dev_err(&client->dev, "failed to read chip id\n"); + return err; + } + if (value != 0x61) { + dev_err(&client->dev, "chip id is not correct\n"); + return err; + } + + prop = of_find_property(np, "tr8fpga,addresses", NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + size = prop->length / sizeof(int); + + err = of_property_read_u32_array(np, "tr8fpga,addresses", addresses, size); + if (err && (err != -EINVAL)) { + dev_err(&client->dev, "Unable to read 'tr8fpga,addresses'\n"); + return err; + } + + prop = of_find_property(np, "tr8fpga,values", NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + i = prop->length / sizeof(u32); + if (i != size) { + dev_err(&client->dev, "invalid 'tr8fpga,values' length should be same as addresses\n"); + return -EINVAL; + } + + err = of_property_read_u32_array(np, "tr8fpga,values", values, i); + if (err && (err != -EINVAL)) { + dev_err(&client->dev, "Unable to read 'tr8fpga,values'\n"); + return err; + } + + for (i = 0; i < size; i++) + { + printk(KERN_ERR"tr8fpga_probe: tr8fpga_write_reg 0x%x 0x%x\n",addresses[i], values[i]); + + tr8fpga_write_reg(client, addresses[i], values[i]); + if (err < 0) { + dev_err(&client->dev, "failed to write data to the chip\n"); + return err; + } + } + + + return 0; +} + +static int tr8fpga_remove(struct i2c_client *client) +{ + struct gpio_desc *enable_gpio; + + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); + if (enable_gpio) + gpiod_set_value_cansleep(enable_gpio, 0); + + return 0; +} + +static const struct i2c_device_id tr8fpga_id[] = { + {"tr8fpga", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tr8fpga_id); + +static struct of_device_id tr8fpga_match_table[] = { + { .compatible = "kuk,tr8fpga",}, + { }, +}; + +static struct i2c_driver tr8fpga_i2c_driver = { + .probe = tr8fpga_probe, + .remove = tr8fpga_remove, + .driver = { + .name = "tr8fpga", + .owner = THIS_MODULE, + .of_match_table = tr8fpga_match_table, + }, + .id_table = tr8fpga_id, +}; + +static int __init tr8fpga_init(void) +{ + return i2c_add_driver(&tr8fpga_i2c_driver); +} + +static void __exit tr8fpga_exit(void) +{ + i2c_del_driver(&tr8fpga_i2c_driver); +} + +module_init(tr8fpga_init); +module_exit(tr8fpga_exit); + +MODULE_DESCRIPTION("KuK tr8fpga DSI to RGB bridge driver"); +MODULE_LICENSE("GPL v2"); +