tegrakernel/kernel/nvidia/drivers/video/tegra/dc/bridge/ds90ub947-q1.c

418 lines
10 KiB
C

/*
* DS90UB947-Q1 1080p OpenLDS to FPD-Link III Serializer driver
*
* Copyright (C) 2016-2018, NVIDIA Corporation. All rights reserved.
*
* 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; version 2 of the License.
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include "dsi.h"
#include "ds90ub947-q1.h"
/* TODO: support multiple instances */
struct ds90ub947_data *g_ds90ub947_data;
static void ds90ub947_lvds2fpdl_en_gpio(struct ds90ub947_data *lvds2fpdl,
bool enable)
{
if (!gpio_is_valid(lvds2fpdl->en_gpio))
return;
if (enable) {
gpio_direction_output(lvds2fpdl->en_gpio,
!(lvds2fpdl->en_gpio_flags & OF_GPIO_ACTIVE_LOW));
/* Wait > 1.5ms before accessing I2C */
usleep_range(1500, 2000);
} else {
gpio_direction_output(lvds2fpdl->en_gpio,
lvds2fpdl->en_gpio_flags & OF_GPIO_ACTIVE_LOW);
}
}
bool ds90ub947_lvds2fpdlink3_detect(struct tegra_dc *dc)
{
int ret, val;
if (g_ds90ub947_data == NULL)
return false;
mutex_lock(&g_ds90ub947_data->lock);
ret = regmap_read(g_ds90ub947_data->regmap, DS90UB947_GENERAL_STATUS,
&val);
mutex_unlock(&g_ds90ub947_data->lock);
if (ret == 0 && (val & 0x01))
return true;
return false;
}
static int ds90ub947_init(struct ds90ub947_data *data)
{
int i;
unsigned int val, reg, mask;
int ret = 0;
mutex_lock(&data->lock);
if (gpio_is_valid(data->en_gpio)) {
ret = devm_gpio_request_one(&data->client->dev,
data->en_gpio,
data->en_gpio_flags & OF_GPIO_ACTIVE_LOW,
"lvds2fpdl");
if (ret)
pr_err("%d: lvds2fpdl GPIO request failed\n", ret);
} else {
pr_err("%d: lvds2fpdl GPIO is invalid\n", ret);
}
ds90ub947_lvds2fpdl_en_gpio(data, true);
/* set backchanel crc setting */
/*setting not needed since it is enabled by default */
/* set backchannel i2c setting */
ret = regmap_update_bits(data->regmap, DS90UB947_GENERAL_CONFIG,
BIT(DS90UB947_I2C_PASSTHROUGH_BIT),
data->en_bchnl_i2c_passthrough <<
DS90UB947_I2C_PASSTHROUGH_BIT);
if (ret)
goto fault;
ret = regmap_update_bits(data->regmap, DS90UB947_I2C_CONTROL,
BIT(DS90UB947_I2C_PASSALL_BIT),
data->en_bchnl_i2c_passthrough <<
DS90UB947_I2C_PASSALL_BIT);
if (ret)
goto fault;
/* set backchannel irq settings */
ret = regmap_update_bits(data->regmap, DS90UB947_INTR_CNTRL_REG,
BIT(DS90UB947_INTR_ENABLE_BIT) |
BIT(DS90UB947_INTR_RX_ENABLE_BIT),
data->en_bchnl_irq <<
DS90UB947_INTR_ENABLE_BIT |
data->en_bchnl_irq <<
DS90UB947_INTR_RX_ENABLE_BIT);
if (ret)
goto fault;
/* set deserializer address */
ret = regmap_update_bits(data->regmap, DS90UB947_DESER_ADDR,
0xFF << DS90UB947_DES_ID_SHIFT_WIDTH,
data->deser_address <<
DS90UB947_DES_ID_SHIFT_WIDTH);
if (ret)
goto fault;
/* set slave address */
ret = regmap_update_bits(data->regmap, DS90UB947_SLAVE_ADDR,
0xFF << DS90UB947_SLAVE_ADDR_SHIFT_WIDTH,
data->slave_address <<
DS90UB947_SLAVE_ADDR_SHIFT_WIDTH);
if (ret)
goto fault;
/* set slave alias address*/
ret = regmap_update_bits(data->regmap, DS90UB947_SLAVE_ALIAS,
0xFF << DS90UB947_SLAVE_ALIAS_SHIFT_WIDTH,
data->slave_alias_address <<
DS90UB947_SLAVE_ALIAS_SHIFT_WIDTH);
if (ret)
goto fault;
for (i = 0; i < data->n_init_regs; i += 3) {
reg = data->init_regs[i];
mask = data->init_regs[i + 1];
val = data->init_regs[i + 2];
ret = regmap_update_bits(data->regmap, reg, mask, val);
pr_info("%s: regmap_update_bits returned %d\n", __func__, ret);
if (ret) {
dev_err(&data->client->dev,
"failed to write to reg %#x\n", reg);
break;
}
}
fault:
mutex_unlock(&data->lock);
return ret;
}
#ifdef CONFIG_DEBUG_FS
static int ds90ub947_init_regs_show(struct seq_file *s, void *unused)
{
struct ds90ub947_data *data = s->private;
int i, ret;
unsigned int val, reg, mask, cval;
mutex_lock(&data->lock);
seq_printf(s, "%13s%13s%13s%13s\n",
"reg", "mask", "init val", "current val");
for (i = 0; i < data->n_init_regs; i += 3) {
reg = data->init_regs[i];
mask = data->init_regs[i + 1];
val = data->init_regs[i + 2];
ret = regmap_read(data->regmap, reg, &cval);
if (ret)
seq_printf(s, "%#13x%#13x%#13x%13s\n",
reg, mask, val, "read error");
else
seq_printf(s, "%#13x%#13x%#13x%#13x\n",
reg, mask, val, cval);
}
mutex_unlock(&data->lock);
return 0;
}
static int ds90ub947_init_regs_open(struct inode *inode, struct file *file)
{
return single_open(file, ds90ub947_init_regs_show, inode->i_private);
}
static const struct file_operations init_regs_fops = {
.open = ds90ub947_init_regs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void ds90ub947_panel_remove_debugfs(struct ds90ub947_data *data)
{
/* debugfs_remove_recursive(NULL) is safe */
debugfs_remove_recursive(data->debugdir);
data->debugdir = NULL;
}
static void ds90ub947_panel_create_debugfs(struct ds90ub947_data *data)
{
struct dentry *pEntry;
data->debugdir = debugfs_create_dir(DEV_NAME, NULL);
pr_debug("%s: data->debugdir = %p\n", __func__, data->debugdir);
if (!data->debugdir)
goto err;
pEntry = debugfs_create_file("init_regs", 0644,
data->debugdir, data, &init_regs_fops);
pr_debug("%s: debugfs_create_file returned %p\n", __func__, pEntry);
if (pEntry == NULL)
goto err;
return;
err:
ds90ub947_panel_remove_debugfs(data);
dev_err(&data->client->dev, "Failed to create debugfs\n");
}
#else
static void ds90ub947_panel_create_debugfs(struct ds90ub947_data *data) { }
static void ds90ub947_panel_remove_debugfs(struct ds90ub947_data *data) { }
#endif
static int of_ds90ub947_parse_pdata(struct i2c_client *client,
struct ds90ub947_data *data)
{
struct device_node *np = client->dev.of_node;
int count = 0;
int i = 0;
struct property *prop;
const __be32 *p;
u32 u, temp;
u32 *init_regs;
enum of_gpio_flags flags;
data->en_gpio = of_get_named_gpio_flags(np,
"ti,enable-gpio", 0, &flags);
data->en_gpio_flags = flags;
if (data->en_gpio < 0)
dev_err(&client->dev, "lvds2fpdl: gpio number not provided\n");
else if (!gpio_is_valid(data->en_gpio))
dev_err(&client->dev, "lvds2fpdl: en gpio is invalid\n");
/*
* init-regs is an array of groups of 3 values: reg address, mask,
* and value to write
*/
of_property_for_each_u32(np, "init-regs", prop, p, u)
count++;
if (!count)
return 0;
if ((count % 3) != 0) {
dev_err(&data->client->dev, "invalid \"init-regs\" data\n");
return -EINVAL;
}
init_regs = devm_kzalloc(&client->dev, count * sizeof(u), GFP_KERNEL);
if (!init_regs) {
dev_err(&client->dev, "Failed to allocate init_regs\n");
return -ENOMEM;
}
of_property_for_each_u32(np, "init-regs", prop, p, u)
init_regs[i++] = u & 0xff;
data->init_regs = init_regs;
data->n_init_regs = count;
if (!of_property_read_u32(np, "ti,enable-bchnl-i2c", &temp))
data->en_bchnl_i2c_passthrough = temp;
else
data->en_bchnl_i2c_passthrough = 0;
if (!of_property_read_u32(np, "ti,enable-bchnl-irq", &temp))
data->en_bchnl_irq = temp;
else
data->en_bchnl_irq = 0;
if (!of_property_read_u32(np, "ti,deser-addr", &temp))
data->deser_address = temp;
else
data->deser_address = 0;
if (!of_property_read_u32(np, "ti,slave-addr", &temp))
data->slave_address = temp;
else
data->slave_address = 0;
if (!of_property_read_u32(np, "ti,slave-alias-addr", &temp))
data->slave_alias_address = temp;
else
data->slave_alias_address = 0;
return 0;
}
static int ds90ub947_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = client->adapter;
struct ds90ub947_data *data;
struct regmap_config rconfig;
int ret;
pr_info("%s: entered\n", __func__);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "i2c functionality check fail.\n");
ret = -ENODEV;
goto err1;
}
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err1;
}
g_ds90ub947_data = data;
data->client = client;
ret = of_ds90ub947_parse_pdata(client, data);
if (ret) {
dev_err(&client->dev, "of data parse failed\n");
goto err2;
}
memset(&rconfig, 0, sizeof(rconfig));
rconfig.reg_bits = 8;
rconfig.val_bits = 8;
rconfig.cache_type = REGCACHE_NONE;
data->regmap = regmap_init_i2c(client, &rconfig);
if (!data->regmap) {
dev_err(&client->dev, "Failed to allocate register map\n");
ret = -ENOMEM;
goto err2;
}
mutex_init(&data->lock);
ret = ds90ub947_init(data);
if (ret) {
dev_err(&client->dev, "initializing registers failed\n");
goto err3;
}
ds90ub947_panel_create_debugfs(data);
pr_info("%s: returning %d\n", __func__, ret);
return ret;
err3:
mutex_destroy(&data->lock);
err2:
if (data->init_regs)
devm_kfree(&client->dev, data->init_regs);
g_ds90ub947_data = NULL;
devm_kfree(&client->dev, data);
err1:
pr_info("%s: returning %d\n", __func__, ret);
return ret;
}
static int ds90ub947_remove(struct i2c_client *client)
{
struct ds90ub947_data *data = i2c_get_clientdata(client);
ds90ub947_panel_remove_debugfs(data);
ds90ub947_lvds2fpdl_en_gpio(data, false);
devm_gpio_free(&client->dev, data->en_gpio);
mutex_destroy(&data->lock);
devm_kfree(&client->dev, data->init_regs);
devm_kfree(&client->dev, data);
return 0;
}
static const struct i2c_device_id ds90ub947_id[] = {
{DEV_NAME, 0},
{ }
};
MODULE_DEVICE_TABLE(i2c, ds90ub947_id);
static const struct of_device_id ds90ub947_of_match[] = {
{.compatible = "ti,ds90ub947-q1", },
{ },
};
static struct i2c_driver ds90ub947_driver = {
.driver = {
.name = DEV_NAME,
.owner = THIS_MODULE,
.of_match_table =
of_match_ptr(ds90ub947_of_match),
},
.probe = ds90ub947_probe,
.remove = ds90ub947_remove,
.id_table = ds90ub947_id,
};
module_i2c_driver(ds90ub947_driver);
MODULE_AUTHOR("Daniel Solomon <daniels@nvidia.com>");
MODULE_DESCRIPTION("DS90UB947-Q1 1080p OpenLDS to FPD-Link III Serializer driver");
MODULE_LICENSE("GPL");