
Add all drivers for new platforms. Change-Id: Ie9947b0c6f8ddfee7dab6dfa80d6aca62323f4da Signed-off-by: Fei Mao <feim1@codeaurora.org>
673 lines
17 KiB
C
673 lines
17 KiB
C
/*
|
|
* Synaptics DSX touchscreen driver
|
|
*
|
|
* Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved.
|
|
*
|
|
* Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
|
|
* Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
|
|
* Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.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; 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.
|
|
*
|
|
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
|
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
|
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
|
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
|
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
|
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
|
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
|
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
|
* DOLLARS.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/input.h>
|
|
#include <linux/types.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/input/synaptics_dsx.h>
|
|
#include "synaptics_dsx_core.h"
|
|
#include "linux/moduleparam.h"
|
|
|
|
#define SYN_I2C_RETRY_TIMES 10
|
|
#define rd_msgs 1
|
|
|
|
#ifdef CONFIG_DRM
|
|
#include <drm/drm_panel.h>
|
|
struct drm_panel *active_panel;
|
|
#endif
|
|
|
|
static unsigned char *wr_buf;
|
|
|
|
static struct synaptics_dsx_hw_interface hw_if;
|
|
|
|
static struct platform_device *synaptics_dsx_i2c_device;
|
|
|
|
|
|
#ifdef CONFIG_OF
|
|
static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata)
|
|
{
|
|
int retval;
|
|
u32 value;
|
|
const char *name;
|
|
struct property *prop;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
bdata->irq_gpio = of_get_named_gpio_flags(np,
|
|
"synaptics,irq-gpio", 0,
|
|
(enum of_gpio_flags *)&bdata->irq_flags);
|
|
|
|
retval = of_property_read_u32(np, "synaptics,irq-on-state",
|
|
&value);
|
|
if (retval < 0)
|
|
bdata->irq_on_state = 0;
|
|
else
|
|
bdata->irq_on_state = value;
|
|
|
|
retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);
|
|
if (retval < 0)
|
|
bdata->pwr_reg_name = NULL;
|
|
else
|
|
bdata->pwr_reg_name = name;
|
|
|
|
retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);
|
|
if (retval < 0)
|
|
bdata->bus_reg_name = NULL;
|
|
else
|
|
bdata->bus_reg_name = name;
|
|
|
|
prop = of_find_property(np, "synaptics,power-gpio", NULL);
|
|
if (prop && prop->length) {
|
|
bdata->power_gpio = of_get_named_gpio_flags(np,
|
|
"synaptics,power-gpio", 0, NULL);
|
|
retval = of_property_read_u32(np, "synaptics,power-on-state",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->power_on_state = value;
|
|
} else {
|
|
bdata->power_gpio = -1;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,power-delay-ms", NULL);
|
|
if (prop && prop->length) {
|
|
retval = of_property_read_u32(np, "synaptics,power-delay-ms",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->power_delay_ms = value;
|
|
} else {
|
|
bdata->power_delay_ms = 0;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,reset-gpio", NULL);
|
|
if (prop && prop->length) {
|
|
bdata->reset_gpio = of_get_named_gpio_flags(np,
|
|
"synaptics,reset-gpio", 0, NULL);
|
|
retval = of_property_read_u32(np, "synaptics,reset-on-state",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->reset_on_state = value;
|
|
retval = of_property_read_u32(np, "synaptics,reset-active-ms",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->reset_active_ms = value;
|
|
} else {
|
|
bdata->reset_gpio = -1;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,reset-delay-ms", NULL);
|
|
if (prop && prop->length) {
|
|
retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->reset_delay_ms = value;
|
|
} else {
|
|
bdata->reset_delay_ms = 0;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,max-y-for-2d", NULL);
|
|
if (prop && prop->length) {
|
|
retval = of_property_read_u32(np, "synaptics,max-y-for-2d",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->max_y_for_2d = value;
|
|
} else {
|
|
bdata->max_y_for_2d = -1;
|
|
}
|
|
|
|
bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes");
|
|
bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip");
|
|
bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip");
|
|
|
|
prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL);
|
|
if (prop && prop->length) {
|
|
retval = of_property_read_u32(np, "synaptics,ub-i2c-addr",
|
|
&value);
|
|
if (retval < 0) {
|
|
dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
bdata->ub_i2c_addr = (unsigned short)value;
|
|
} else {
|
|
bdata->ub_i2c_addr = -1;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,cap-button-codes", NULL);
|
|
if (prop && prop->length) {
|
|
bdata->cap_button_map->map = devm_kzalloc(dev,
|
|
prop->length,
|
|
GFP_KERNEL);
|
|
if (!bdata->cap_button_map->map)
|
|
return -ENOMEM;
|
|
bdata->cap_button_map->nbuttons = prop->length / sizeof(u32);
|
|
retval = of_property_read_u32_array(np,
|
|
"synaptics,cap-button-codes",
|
|
bdata->cap_button_map->map,
|
|
bdata->cap_button_map->nbuttons);
|
|
if (retval < 0) {
|
|
bdata->cap_button_map->nbuttons = 0;
|
|
bdata->cap_button_map->map = NULL;
|
|
}
|
|
} else {
|
|
bdata->cap_button_map->nbuttons = 0;
|
|
bdata->cap_button_map->map = NULL;
|
|
}
|
|
|
|
prop = of_find_property(np, "synaptics,vir-button-codes", NULL);
|
|
if (prop && prop->length) {
|
|
bdata->vir_button_map->map = devm_kzalloc(dev,
|
|
prop->length,
|
|
GFP_KERNEL);
|
|
if (!bdata->vir_button_map->map)
|
|
return -ENOMEM;
|
|
bdata->vir_button_map->nbuttons = prop->length / sizeof(u32);
|
|
bdata->vir_button_map->nbuttons /= 5;
|
|
retval = of_property_read_u32_array(np,
|
|
"synaptics,vir-button-codes",
|
|
bdata->vir_button_map->map,
|
|
bdata->vir_button_map->nbuttons * 5);
|
|
if (retval < 0) {
|
|
bdata->vir_button_map->nbuttons = 0;
|
|
bdata->vir_button_map->map = NULL;
|
|
}
|
|
} else {
|
|
bdata->vir_button_map->nbuttons = 0;
|
|
bdata->vir_button_map->map = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int synaptics_rmi4_i2c_alloc_buf(struct synaptics_rmi4_data *rmi4_data,
|
|
unsigned int count)
|
|
{
|
|
static unsigned int buf_size;
|
|
|
|
if (count > buf_size) {
|
|
if (buf_size)
|
|
kfree(wr_buf);
|
|
wr_buf = kzalloc(count, GFP_KERNEL);
|
|
if (!wr_buf) {
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: Failed to alloc mem for buffer\n",
|
|
__func__);
|
|
buf_size = 0;
|
|
return -ENOMEM;
|
|
}
|
|
buf_size = count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void synaptics_rmi4_i2c_check_addr(struct synaptics_rmi4_data *rmi4_data,
|
|
struct i2c_client *i2c)
|
|
{
|
|
if (hw_if.board_data->ub_i2c_addr == -1)
|
|
return;
|
|
|
|
if (hw_if.board_data->i2c_addr == i2c->addr)
|
|
hw_if.board_data->i2c_addr = hw_if.board_data->ub_i2c_addr;
|
|
else
|
|
hw_if.board_data->i2c_addr = i2c->addr;
|
|
}
|
|
|
|
static int synaptics_rmi4_i2c_set_page(struct synaptics_rmi4_data *rmi4_data,
|
|
unsigned short addr)
|
|
{
|
|
int retval = 0;
|
|
unsigned char retry;
|
|
unsigned char buf[PAGE_SELECT_LEN];
|
|
unsigned char page;
|
|
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
|
struct i2c_msg msg[2];
|
|
|
|
msg[0].addr = hw_if.board_data->i2c_addr;
|
|
msg[0].flags = 0;
|
|
msg[0].len = PAGE_SELECT_LEN;
|
|
msg[0].buf = buf;
|
|
|
|
page = ((addr >> 8) & MASK_8BIT);
|
|
buf[0] = MASK_8BIT;
|
|
buf[1] = page;
|
|
|
|
if (page != rmi4_data->current_page) {
|
|
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
|
if (i2c_transfer(i2c->adapter, &msg[0], 1) == 1) {
|
|
rmi4_data->current_page = page;
|
|
retval = PAGE_SELECT_LEN;
|
|
break;
|
|
}
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: I2C retry %d\n",
|
|
__func__, retry + 1);
|
|
msleep(20);
|
|
|
|
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
|
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
|
msg[0].addr = hw_if.board_data->i2c_addr;
|
|
}
|
|
}
|
|
} else {
|
|
retval = PAGE_SELECT_LEN;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data,
|
|
unsigned short addr, unsigned char *data, unsigned int length)
|
|
{
|
|
int retval = 0;
|
|
unsigned char retry;
|
|
unsigned char buf;
|
|
unsigned char index = 0;
|
|
unsigned char xfer_msgs;
|
|
unsigned char remaining_msgs;
|
|
unsigned short i2c_addr;
|
|
unsigned short data_offset = 0;
|
|
unsigned int remaining_length = length;
|
|
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
|
struct i2c_adapter *adap = i2c->adapter;
|
|
struct i2c_msg msg[rd_msgs + 1];
|
|
|
|
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
|
|
|
retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr);
|
|
if (retval != PAGE_SELECT_LEN) {
|
|
retval = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
msg[0].addr = hw_if.board_data->i2c_addr;
|
|
msg[0].flags = 0;
|
|
msg[0].len = 1;
|
|
msg[0].buf = &buf;
|
|
msg[rd_msgs].addr = hw_if.board_data->i2c_addr;
|
|
msg[rd_msgs].flags = I2C_M_RD;
|
|
msg[rd_msgs].len = (unsigned short)remaining_length;
|
|
msg[rd_msgs].buf = &data[data_offset];
|
|
|
|
buf = addr & MASK_8BIT;
|
|
|
|
remaining_msgs = rd_msgs + 1;
|
|
|
|
while (remaining_msgs) {
|
|
xfer_msgs = remaining_msgs;
|
|
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
|
retval = i2c_transfer(adap, &msg[index], xfer_msgs);
|
|
if (retval == xfer_msgs)
|
|
break;
|
|
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: I2C retry %d\n",
|
|
__func__, retry + 1);
|
|
msleep(20);
|
|
|
|
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
|
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
|
i2c_addr = hw_if.board_data->i2c_addr;
|
|
msg[0].addr = i2c_addr;
|
|
msg[rd_msgs].addr = i2c_addr;
|
|
}
|
|
}
|
|
|
|
if (retry == SYN_I2C_RETRY_TIMES) {
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: I2C read over retry limit\n",
|
|
__func__);
|
|
retval = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
remaining_msgs -= xfer_msgs;
|
|
index += xfer_msgs;
|
|
}
|
|
|
|
retval = length;
|
|
|
|
exit:
|
|
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data,
|
|
unsigned short addr, unsigned char *data, unsigned int length)
|
|
{
|
|
int retval;
|
|
unsigned char retry;
|
|
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
|
struct i2c_msg msg[2];
|
|
|
|
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
|
|
|
retval = synaptics_rmi4_i2c_alloc_buf(rmi4_data, length + 1);
|
|
if (retval < 0)
|
|
goto exit;
|
|
|
|
retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr);
|
|
if (retval != PAGE_SELECT_LEN) {
|
|
retval = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
msg[0].addr = hw_if.board_data->i2c_addr;
|
|
msg[0].flags = 0;
|
|
msg[0].len = (unsigned short)(length + 1);
|
|
msg[0].buf = wr_buf;
|
|
|
|
wr_buf[0] = addr & MASK_8BIT;
|
|
retval = secure_memcpy(&wr_buf[1], length, &data[0], length, length);
|
|
if (retval < 0) {
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: Failed to copy data\n",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
|
|
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
|
if (i2c_transfer(i2c->adapter, &msg[0], 1) == 1) {
|
|
retval = length;
|
|
break;
|
|
}
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: I2C retry %d\n",
|
|
__func__, retry + 1);
|
|
msleep(20);
|
|
|
|
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
|
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
|
msg[0].addr = hw_if.board_data->i2c_addr;
|
|
}
|
|
}
|
|
|
|
if (retry == SYN_I2C_RETRY_TIMES) {
|
|
dev_err(rmi4_data->pdev->dev.parent,
|
|
"%s: I2C write over retry limit\n",
|
|
__func__);
|
|
retval = -EIO;
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
#ifdef CONFIG_DRM
|
|
static int check_dt(struct device_node *np)
|
|
{
|
|
int i;
|
|
int count;
|
|
struct device_node *node;
|
|
struct drm_panel *panel;
|
|
|
|
count = of_count_phandle_with_args(np, "panel", NULL);
|
|
if (count <= 0)
|
|
return 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
node = of_parse_phandle(np, "panel", i);
|
|
panel = of_drm_find_panel(node);
|
|
of_node_put(node);
|
|
if (!IS_ERR(panel)) {
|
|
active_panel = panel;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return PTR_ERR(panel);
|
|
}
|
|
#endif
|
|
|
|
static int check_default_tp(struct device_node *dt, const char *prop)
|
|
{
|
|
const char *active_tp;
|
|
const char *compatible;
|
|
char *start;
|
|
int ret;
|
|
|
|
ret = of_property_read_string(dt->parent, prop, &active_tp);
|
|
if (ret) {
|
|
pr_err(" %s:fail to read %s %d\n", __func__, prop, ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = of_property_read_string(dt, "compatible", &compatible);
|
|
if (ret < 0) {
|
|
pr_err(" %s:fail to read %s %d\n", __func__, "compatible", ret);
|
|
return -ENODEV;
|
|
}
|
|
|
|
start = strnstr(active_tp, compatible, strlen(active_tp));
|
|
if (start == NULL) {
|
|
pr_err(" %s:no match compatible, %s, %s\n",
|
|
__func__, compatible, active_tp);
|
|
ret = -ENODEV;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct synaptics_dsx_bus_access bus_access = {
|
|
.type = BUS_I2C,
|
|
.read = synaptics_rmi4_i2c_read,
|
|
.write = synaptics_rmi4_i2c_write,
|
|
};
|
|
|
|
static void synaptics_rmi4_i2c_dev_release(struct device *dev)
|
|
{
|
|
kfree(synaptics_dsx_i2c_device);
|
|
}
|
|
|
|
static int synaptics_rmi4_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *dev_id)
|
|
{
|
|
int retval;
|
|
struct device_node *dp = client->dev.of_node;
|
|
|
|
#ifdef CONFIG_DRM
|
|
retval = check_dt(dp);
|
|
if (retval == -EPROBE_DEFER)
|
|
return retval;
|
|
|
|
if (retval) {
|
|
if (!check_default_tp(dp, "qcom,i2c-touch-active"))
|
|
retval = -EPROBE_DEFER;
|
|
else
|
|
retval = -ENODEV;
|
|
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
dev_err(&client->dev,
|
|
"%s: SMBus byte data commands not supported by host\n",
|
|
__func__);
|
|
return -EIO;
|
|
}
|
|
|
|
synaptics_dsx_i2c_device = kzalloc(
|
|
sizeof(struct platform_device),
|
|
GFP_KERNEL);
|
|
if (!synaptics_dsx_i2c_device) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to allocate memory for synaptics_dsx_i2c_device\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
if (client->dev.of_node) {
|
|
hw_if.board_data = devm_kzalloc(&client->dev,
|
|
sizeof(struct synaptics_dsx_board_data),
|
|
GFP_KERNEL);
|
|
if (!hw_if.board_data) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to allocate memory for board data\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev,
|
|
sizeof(struct synaptics_dsx_button_map),
|
|
GFP_KERNEL);
|
|
if (!hw_if.board_data->cap_button_map) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to allocate memory for 0D button map\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev,
|
|
sizeof(struct synaptics_dsx_button_map),
|
|
GFP_KERNEL);
|
|
if (!hw_if.board_data->vir_button_map) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to allocate memory for virtual button map\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
parse_dt(&client->dev, hw_if.board_data);
|
|
}
|
|
#else
|
|
hw_if.board_data = client->dev.platform_data;
|
|
#endif
|
|
|
|
hw_if.bus_access = &bus_access;
|
|
hw_if.board_data->i2c_addr = client->addr;
|
|
|
|
synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME;
|
|
synaptics_dsx_i2c_device->id = 0;
|
|
synaptics_dsx_i2c_device->num_resources = 0;
|
|
synaptics_dsx_i2c_device->dev.parent = &client->dev;
|
|
synaptics_dsx_i2c_device->dev.platform_data = &hw_if;
|
|
synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release;
|
|
|
|
retval = platform_device_register(synaptics_dsx_i2c_device);
|
|
if (retval) {
|
|
dev_err(&client->dev,
|
|
"%s: Failed to register platform device\n",
|
|
__func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int synaptics_rmi4_i2c_remove(struct i2c_client *client)
|
|
{
|
|
platform_device_unregister(synaptics_dsx_i2c_device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id synaptics_rmi4_id_table[] = {
|
|
{I2C_DRIVER_NAME, 0},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table);
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id synaptics_rmi4_of_match_table[] = {
|
|
{
|
|
.compatible = "synaptics,dsx-i2c",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table);
|
|
#else
|
|
#define synaptics_rmi4_of_match_table NULL
|
|
#endif
|
|
|
|
static struct i2c_driver synaptics_rmi4_i2c_driver = {
|
|
.driver = {
|
|
.name = I2C_DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = synaptics_rmi4_of_match_table,
|
|
},
|
|
.probe = synaptics_rmi4_i2c_probe,
|
|
.remove = synaptics_rmi4_i2c_remove,
|
|
.id_table = synaptics_rmi4_id_table,
|
|
};
|
|
|
|
int synaptics_rmi4_bus_init(void)
|
|
{
|
|
return i2c_add_driver(&synaptics_rmi4_i2c_driver);
|
|
}
|
|
EXPORT_SYMBOL(synaptics_rmi4_bus_init);
|
|
|
|
void synaptics_rmi4_bus_exit(void)
|
|
{
|
|
kfree(wr_buf);
|
|
|
|
i2c_del_driver(&synaptics_rmi4_i2c_driver);
|
|
}
|
|
EXPORT_SYMBOL(synaptics_rmi4_bus_exit);
|
|
|
|
MODULE_AUTHOR("Synaptics, Inc.");
|
|
MODULE_DESCRIPTION("Synaptics DSX I2C Bus Support Module");
|
|
MODULE_LICENSE("GPL v2");
|