336 lines
7 KiB
C
Executable file
336 lines
7 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) Samsung Electronics Co., Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
|
|
#include "panel_drv.h"
|
|
#include "panel_i2c.h"
|
|
#include "panel_debug.h"
|
|
|
|
#define PANEL_I2C_DRIVER_NAME "panel_i2c"
|
|
|
|
#define MAX_TX_SIZE 16
|
|
#define MAX_RX_SIZE 16
|
|
|
|
__visible_for_testing int panel_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!adap) {
|
|
panel_err("invalid adap.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!msgs) {
|
|
panel_err("invalid msgs.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (num <= 0) {
|
|
panel_err("invalid num(%d).\n", num);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = i2c_transfer(adap, msgs, num);
|
|
|
|
return ret;
|
|
}
|
|
|
|
__visible_for_testing int panel_i2c_tx(struct panel_i2c_dev *i2c_dev, u8 *arr, u32 arr_len)
|
|
{
|
|
int ret = 0;
|
|
int tx_len = 0;
|
|
int tx_cnt = 0;
|
|
int i = 0;
|
|
u8 tx_buf[MAX_TX_SIZE] = {0, };
|
|
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0,
|
|
.flags = 0,
|
|
.len = tx_len,
|
|
.buf = tx_buf,
|
|
}
|
|
};
|
|
|
|
if (!i2c_dev) {
|
|
panel_err("i2c_dev is null.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!arr) {
|
|
panel_err("arr is null.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (arr_len <= 0) {
|
|
panel_err("arr_len is invalid(%d).\n", arr_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!i2c_dev->client) {
|
|
panel_err("failed, i2c Client is NULL.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx_len = i2c_dev->addr_len + i2c_dev->data_len;
|
|
if (tx_len > MAX_TX_SIZE) {
|
|
panel_err("failed, tx_len is too long.(%d)\n", tx_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msg[0].addr = i2c_dev->client->addr;
|
|
msg[0].len = tx_len;
|
|
|
|
mutex_lock(&i2c_dev->lock);
|
|
|
|
tx_cnt = arr_len / tx_len;
|
|
|
|
if (arr_len % tx_len) {
|
|
panel_err("there is rest. arr_size: %d rest: %d.\n",
|
|
arr_len, arr_len % tx_len);
|
|
ret = -EINVAL;
|
|
goto tx_ret;
|
|
}
|
|
|
|
for (i = 0; i < tx_cnt; i++) {
|
|
memcpy(tx_buf, arr + (i * tx_len), tx_len);
|
|
|
|
ret = i2c_dev->ops->transfer(i2c_dev->client->adapter, msg, 1);
|
|
|
|
if (unlikely(ret < 0)) {
|
|
panel_err("i2c tx failed. [0x%02x]... : [0x%02x]... error %d\n",
|
|
msg[0].buf[0], msg[0].buf[1], ret);
|
|
goto tx_ret;
|
|
}
|
|
}
|
|
|
|
tx_ret:
|
|
mutex_unlock(&i2c_dev->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define PANEL_I2C_RX_DUMP
|
|
__visible_for_testing int panel_i2c_rx(struct panel_i2c_dev *i2c_dev, u8 *arr, u32 arr_len)
|
|
{
|
|
int tx_len = 0;
|
|
int rx_cnt = 0;
|
|
int i = 0;
|
|
int j = 0;
|
|
int printout_pos;
|
|
int ret = 0;
|
|
char printout_buf[128] = {0, };
|
|
u8 addr_buf[MAX_TX_SIZE] = {0, };
|
|
u8 rx_buf[MAX_RX_SIZE] = {0, };
|
|
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0,
|
|
.flags = 0,
|
|
.len = 0,
|
|
.buf = addr_buf,
|
|
},
|
|
{
|
|
.addr = 0,
|
|
.flags = I2C_M_RD,
|
|
.len = 0,
|
|
.buf = rx_buf,
|
|
},
|
|
};
|
|
|
|
if (!i2c_dev) {
|
|
panel_err("i2c_dev is null.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!arr) {
|
|
panel_err("arr is null.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (arr_len <= 0) {
|
|
panel_err("arr_len is invalid(%d).\n", arr_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!i2c_dev->client) {
|
|
panel_err("failed, i2c Client is NULL.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx_len = i2c_dev->addr_len + i2c_dev->data_len;
|
|
if (tx_len > MAX_TX_SIZE) {
|
|
panel_err("failed, tx_len is too long.(%d)\n", tx_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msg[0].addr = i2c_dev->client->addr;
|
|
msg[1].addr = i2c_dev->client->addr;
|
|
msg[0].len = i2c_dev->addr_len;
|
|
msg[1].len = i2c_dev->data_len;
|
|
|
|
mutex_lock(&i2c_dev->lock);
|
|
|
|
rx_cnt = arr_len / tx_len;
|
|
|
|
if (arr_len % tx_len) {
|
|
panel_err("there is rest. arr_size: %d rest: %d.\n",
|
|
arr_len, arr_len % tx_len);
|
|
ret = -EINVAL;
|
|
goto rx_ret;
|
|
}
|
|
|
|
for (i = 0; i < rx_cnt; i++) {
|
|
memcpy(addr_buf, arr + (i * tx_len), i2c_dev->addr_len);
|
|
memset(printout_buf, 0x00, ARRAY_SIZE(printout_buf));
|
|
printout_pos = 0;
|
|
|
|
ret = i2c_dev->ops->transfer(i2c_dev->client->adapter, msg, 2);
|
|
if (unlikely(ret < 0)) {
|
|
panel_err("i2c rx failed at [%d][0x%02x] error %d\n",
|
|
i, msg[0].buf[0], ret);
|
|
goto rx_ret;
|
|
}
|
|
|
|
/* addr * addr_len */
|
|
for (j = 0; j < i2c_dev->addr_len; j++)
|
|
printout_pos += snprintf(printout_buf + printout_pos,
|
|
sizeof(printout_buf) - printout_pos, "[0x%02x]", addr_buf[j]);
|
|
|
|
printout_pos += snprintf(printout_buf + printout_pos, sizeof(printout_buf) - printout_pos, ": ");
|
|
|
|
/* rx_buf * data_len */
|
|
for (j = 0; j < i2c_dev->data_len; j++)
|
|
snprintf(printout_buf + printout_pos, sizeof(printout_buf) - printout_pos, "[0x%02x]", rx_buf[j]);
|
|
|
|
memcpy(arr + (i * tx_len) + i2c_dev->addr_len, &rx_buf[0], i2c_dev->data_len);
|
|
#ifdef PANEL_I2C_RX_DUMP
|
|
panel_info("%s\n", printout_buf);
|
|
#endif
|
|
}
|
|
rx_ret:
|
|
mutex_unlock(&i2c_dev->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct i2c_drv_ops panel_i2c_drv_ops = {
|
|
.tx = panel_i2c_tx,
|
|
.rx = panel_i2c_rx,
|
|
.transfer = panel_i2c_transfer,
|
|
};
|
|
|
|
__visible_for_testing int panel_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct panel_device *panel = NULL;
|
|
struct panel_i2c_dev *panel_i2c_dev;
|
|
int ret = 0;
|
|
|
|
if (unlikely(!client)) {
|
|
panel_err("invalid i2c.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!id || !id->driver_data) {
|
|
panel_err("invalid argument\n");
|
|
return -EINVAL;
|
|
}
|
|
panel = (struct panel_device *)id->driver_data;
|
|
|
|
if (panel->nr_i2c_dev >= PANEL_I2C_MAX) {
|
|
panel_err("i2c_dev array is full. (%d)\n", panel->nr_i2c_dev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel_i2c_dev = kzalloc(sizeof(struct panel_i2c_dev), GFP_KERNEL);
|
|
|
|
if (!panel_i2c_dev) {
|
|
pr_err("i2c_dev is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel_i2c_dev->client = client;
|
|
panel_i2c_dev->reg = client->addr;
|
|
panel_i2c_dev->ops = &panel_i2c_drv_ops;
|
|
|
|
ret = of_property_read_u32(client->dev.of_node, "len,addr", &panel_i2c_dev->addr_len);
|
|
ret = of_property_read_u32(client->dev.of_node, "len,data", &panel_i2c_dev->data_len);
|
|
|
|
panel_i2c_dev->id = panel->nr_i2c_dev;
|
|
|
|
memcpy(&panel->i2c_dev[panel->nr_i2c_dev], panel_i2c_dev, sizeof(struct panel_i2c_dev));
|
|
mutex_init(&panel->i2c_dev[panel->nr_i2c_dev].lock);
|
|
panel->nr_i2c_dev++;
|
|
|
|
kfree(panel_i2c_dev);
|
|
|
|
dev_info(&client->dev, "%s:(%s)(%s)(nr:%d)(addr:%d)\n", __func__, dev_name(&client->adapter->dev),
|
|
of_node_full_name(client->dev.of_node),
|
|
panel->nr_i2c_dev, client->addr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int panel_i2c_remove(struct i2c_client *client)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct i2c_device_id panel_i2c_id[] = {
|
|
{"i2c", 0},
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, panel_i2c_id);
|
|
|
|
static const struct of_device_id panel_i2c_dt_ids[] = {
|
|
{ .compatible = "panel_drv,i2c" },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, panel_i2c_dt_ids);
|
|
|
|
static struct i2c_driver panel_i2c_driver = {
|
|
.driver = {
|
|
.name = PANEL_I2C_DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(panel_i2c_dt_ids),
|
|
},
|
|
.id_table = panel_i2c_id,
|
|
.probe = panel_i2c_probe,
|
|
.remove = panel_i2c_remove,
|
|
};
|
|
|
|
int panel_i2c_drv_probe(struct panel_device *panel)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (panel == NULL) {
|
|
panel_err("panel is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel_i2c_id->driver_data = (kernel_ulong_t)panel;
|
|
|
|
ret = i2c_add_driver(&panel_i2c_driver);
|
|
if (ret) {
|
|
panel_err("failed to register for i2c device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DESCRIPTION("i2c driver for panel");
|
|
MODULE_LICENSE("GPL");
|