p3898: Bridge driver for MAX96751

Implemented the bridge driver
for MAX96751 HDMI serializer.
In initial version following
features were added.

1) Bring serializer into functional state
2) Drive the RCLKOUT
3) Power management operations

Bug 4069908

Change-Id: Ifbfe5fed3eba5b35303a5072d7eb5935884f1a0f
Signed-off-by: Sagar Tyagi <satyagi@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2905398
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Shu Zhong <shuz@nvidia.com>
This commit is contained in:
Sagar Tyagi
2023-05-16 15:10:55 +05:30
committed by mobile promotions
parent 898a587665
commit a65561e6b4
2 changed files with 378 additions and 0 deletions

View File

@@ -2,4 +2,5 @@
# Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
obj-m += maxim_gmsl_dp_serializer.o
obj-m += maxim_gmsl_hdmi_serializer.o
obj-m += ti_fpdlink_dp_serializer.o

View File

@@ -0,0 +1,377 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* MAXIM HDMI Serializer driver for MAXIM GMSL Serializers
*
* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_graph.h>
#include <linux/regmap.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/version.h>
#define MAX_GMSL_HDMI_SER_INTR2 0x1A
#define MAX_GMSL_HDMI_SER_INTR3 0x1B
#define MAX_GMSL_HDMI_SER_REG_13 0xD
#define MAX_GMSL_HDMI_SER_RESET 0x10
#define MAX_GMSL_HDMI_SER_RCLKOUT 0x03
#define MAX_GMSL_HDMI_SER_HDMI 0x01
#define MAX_GMSL_HDMI_SER_HPD 0xF5
#define MAX_GMSL_HDMI_SERIALIZER_MAX96751F 0x83
#define MAX_GMSL_HDMI_SERIALIZER_MAX96753F 0x85
#define MAX_GMSL_HDMI_TRANSMIT_CRC 0x50
#define ENABLE_REMOTE_SIDE_ERROR_STATUS 0x2B
#define REMOTE_ERROR_STATUS (1 << 5)
#define RESET_SERIALIZER 0x80
#define ENABLE_RCLKOUT 0x30
#define ENABLE_AUTO_HDMI 0x88
#define TOGGLE_HPD 0x01
struct maxim_gmsl_hdmi_ser_priv {
struct i2c_client *client;
struct gpio_desc *gpiod_pwrdn;
struct mutex mutex;
struct regmap *regmap;
int ser_errb;
int ser_pwrdn;
unsigned int ser_irq;
bool enable_rclkout;
};
static const struct regmap_config maxim_gmsl_hdmi_ser_i2c_regmap = {
.reg_bits = 16,
.val_bits = 8,
};
static int maxim_gmsl_hdmi_ser_read(struct maxim_gmsl_hdmi_ser_priv *priv, int reg)
{
int ret, val = 0;
ret = regmap_read(priv->regmap, reg, &val);
if (ret < 0)
dev_err(&priv->client->dev,
"%s: register 0x%02x read failed (%d)\n",
__func__, reg, ret);
return val;
}
static int maxim_gmsl_hdmi_ser_write(struct maxim_gmsl_hdmi_ser_priv *priv, u32 reg, u8 val)
{
int ret;
ret = regmap_write(priv->regmap, reg, val);
if (ret < 0)
dev_err(&priv->client->dev,
"%s: register 0x%02x write failed (%d)\n",
__func__, reg, ret);
return ret;
}
static void maxim_gmsl_hdmi_detect_remote_error(struct maxim_gmsl_hdmi_ser_priv *priv,
struct device *dev)
{
int ret = 0;
ret = maxim_gmsl_hdmi_ser_read(priv, MAX_GMSL_HDMI_SER_INTR3);
if ((ret & REMOTE_ERROR_STATUS) != 0)
dev_err(dev, "%s: Remote side error detected..\n", __func__);
}
static irqreturn_t maxim_gmsl_hdmi_ser_irq_handler(int irq, void *dev_id)
{
struct maxim_gmsl_hdmi_ser_priv *priv;
struct device *dev;
priv = dev_id;
dev = &priv->client->dev;
dev_err(dev, "%s: Interrupt came to MAX96751 Serializer, IRQ: %d\n", __func__, irq);
/* Detect remote error across GMSL link */
maxim_gmsl_hdmi_detect_remote_error(priv, dev);
/* returning just irq handled */
return IRQ_HANDLED;
}
static int maxim_gmsl_hdmi_ser_init(struct device *dev)
{
struct maxim_gmsl_hdmi_ser_priv *priv;
struct i2c_client *client;
int ret = 0, val = 0;
dev_info(dev, "%s: serializer initialization function:\n", __func__);
client = to_i2c_client(dev);
priv = i2c_get_clientdata(client);
ret = devm_gpio_request_one(&client->dev, priv->ser_pwrdn,
GPIOF_DIR_OUT, "GPIO_MAXIM_SER_PWRDN");
if (ret < 0) {
dev_err(dev, "%s: GPIO request failed ret: %d\n", __func__, ret);
return ret;
}
/* powering on the serializer */
gpio_set_value(priv->ser_pwrdn, 1);
/* Wait ~1.2ms for powerup to complete */
usleep_range(1200, 1300);
/* read the register to detect the serializer on board */
val = maxim_gmsl_hdmi_ser_read(priv, MAX_GMSL_HDMI_SER_REG_13);
if (val == MAX_GMSL_HDMI_SERIALIZER_MAX96751F) {
dev_info(dev, "%s: MAXIM Serializer MAX96751F detected\n", __func__);
} else if (val == MAX_GMSL_HDMI_SERIALIZER_MAX96753F) {
dev_info(dev, "%s: MAXIM Serializer MAX96753F detected\n", __func__);
} else {
dev_err(dev, "%s: MAXIM Serializer Not detected\n", __func__);
return -ENODEV;
}
/* Reset the serializer */
ret = maxim_gmsl_hdmi_ser_write(priv, MAX_GMSL_HDMI_SER_RESET, RESET_SERIALIZER);
if (ret != 0) {
dev_err(dev, "%s: error resetting the serializer\n", __func__);
return ret;
}
/* Wait ~1.2ms for powerup to complete */
usleep_range(1200, 1300);
if (priv->enable_rclkout) {
/*If DT has this property, Enable display serializer RCLKOUT*/
dev_info(dev, "%s: RCLKOUT Enable from DT\n", __func__);
ret = maxim_gmsl_hdmi_ser_write(priv, MAX_GMSL_HDMI_SER_RCLKOUT, ENABLE_RCLKOUT);
if (ret != 0) {
dev_err(dev, "%s: error enabling rclkout\n", __func__);
return ret;
}
}
/* Enable HDMI Auto Start */
ret = maxim_gmsl_hdmi_ser_write(priv, MAX_GMSL_HDMI_SER_HDMI, ENABLE_AUTO_HDMI);
if (ret != 0) {
dev_err(dev, "%s: error enabling HDMI auto start\n", __func__);
return ret;
}
/* Toggle HPD */
ret = maxim_gmsl_hdmi_ser_write(priv, MAX_GMSL_HDMI_SER_HPD, TOGGLE_HPD);
if (ret != 0) {
dev_err(dev, "%s: error in powering on HPD\n", __func__);
return ret;
}
dev_info(dev, "%s: driver initialization done successfully\n", __func__);
return ret;
}
static int maxim_gmsl_hdmi_ser_parse_dt(struct maxim_gmsl_hdmi_ser_priv *priv)
{
struct device *dev = &priv->client->dev;
struct device_node *ser = dev->of_node;
int err = 0;
dev_info(dev, "%s: parsing serializer device tree:\n", __func__);
priv->ser_pwrdn = of_get_named_gpio(ser, "maxim_gmsl_hdmi_ser-pwrdn", 0);
if (priv->ser_pwrdn < 0) {
err = priv->ser_pwrdn;
dev_err(dev, "%s: ser_pwrdn is not present in the DT: %d\n",
__func__, err);
return err;
}
priv->enable_rclkout = of_property_read_bool(ser, "enable_rclkout");
priv->ser_errb = of_get_named_gpio(ser, "ser-errb", 0);
if (priv->ser_irq < 0) {
err = priv->ser_irq;
dev_err(dev, "%s: ser_errb is not present in the DT: %d\n",
__func__, err);
return err;
}
return 0;
}
static int maxim_gmsl_hdmi_ser_probe(struct i2c_client *client)
{
struct maxim_gmsl_hdmi_ser_priv *priv;
struct device *dev;
int ret;
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
mutex_init(&priv->mutex);
priv->client = client;
i2c_set_clientdata(client, priv);
priv->regmap = devm_regmap_init_i2c(client, &maxim_gmsl_hdmi_ser_i2c_regmap);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
dev = &priv->client->dev;
/* parse device-tree */
ret = maxim_gmsl_hdmi_ser_parse_dt(priv);
if (ret < 0) {
dev_err(dev, "%s: error parsing device tree\n", __func__);
return -EFAULT;
}
/* driver init function */
ret = maxim_gmsl_hdmi_ser_init(&client->dev);
if (ret < 0) {
dev_err(dev, "%s: hdmi serializer init failed\n", __func__);
gpio_set_value(priv->ser_pwrdn, 0);
return -EFAULT;
}
ret = devm_gpio_request_one(&client->dev, priv->ser_errb,
GPIOF_DIR_IN, "GPIO_MAXIM_SER");
if (ret < 0) {
dev_err(dev, "%s: GPIO request failed\n ret: %d",
__func__, ret);
gpio_set_value(priv->ser_pwrdn, 0);
return ret;
}
dev_info(dev, "%s: mapping gpio to irq and interrupt handler:\n", __func__);
/* maps gpio to irq and interrupt handler */
if (gpio_is_valid(priv->ser_errb)) {
priv->ser_irq = gpio_to_irq(priv->ser_errb);
if (priv->ser_irq < 0) {
ret = priv->ser_irq;
dev_err(dev, "%s: GPIO not configure for interrupts: %d\n",
__func__, ret);
return ret;
}
ret = request_threaded_irq(priv->ser_irq, NULL,
maxim_gmsl_hdmi_ser_irq_handler,
IRQF_TRIGGER_FALLING
| IRQF_ONESHOT, "SER", priv);
if (ret < 0) {
dev_err(dev, "%s: Unable to register IRQ handler ret: %d\n",
__func__, ret);
return ret;
}
}
/* Enable Remote Side Error */
ret = maxim_gmsl_hdmi_ser_write(priv, MAX_GMSL_HDMI_SER_INTR2,
ENABLE_REMOTE_SIDE_ERROR_STATUS);
if (ret != 0) {
dev_err(dev, "%s: Enabling Remote error reporting failed : %d\n", __func__, ret);
return ret;
}
return 0;
}
#if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE
static void maxim_gmsl_hdmi_ser_remove(struct i2c_client *client)
#else
static int maxim_gmsl_hdmi_ser_remove(struct i2c_client *client)
#endif
{
struct maxim_gmsl_hdmi_ser_priv *priv = i2c_get_clientdata(client);
i2c_unregister_device(client);
gpio_set_value(priv->ser_pwrdn, 0);
#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE
return 0;
#endif
}
#ifdef CONFIG_PM
static int maxim_gmsl_hdmi_ser_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct maxim_gmsl_hdmi_ser_priv *priv = i2c_get_clientdata(client);
dev_err(dev, "%s: gmsl hdmi serializer suspended\n", __func__);
/* Drive PWRDNB pin low to power down the serializer */
gpio_set_value(priv->ser_pwrdn, 0);
gpio_free(priv->ser_pwrdn);
return 0;
}
static int maxim_gmsl_hdmi_ser_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct maxim_gmsl_hdmi_ser_priv *priv = i2c_get_clientdata(client);
int ret = 0;
dev_err(dev, "%s: gmsl hdmi serializer resumed\n", __func__);
/* Drive PWRDNB pin high to power up the serializer and initialize all registers */
ret = maxim_gmsl_hdmi_ser_init(&client->dev);
if (ret < 0)
dev_err(&priv->client->dev, "%s: hdmi serializer init failed\n", __func__);
return ret;
}
const struct dev_pm_ops maxim_gmsl_hdmi_ser_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(
maxim_gmsl_hdmi_ser_suspend, maxim_gmsl_hdmi_ser_resume)
};
#endif
static const struct of_device_id maxim_gmsl_hdmi_ser_dt_ids[] = {
{ .compatible = "maxim,maxim_gmsl_hdmi_ser" },
{},
};
MODULE_DEVICE_TABLE(of, maxim_gmsl_hdmi_ser_dt_ids);
static struct i2c_driver maxim_gmsl_hdmi_ser_i2c_driver = {
.driver = {
.name = "maxim_gmsl_hdmi_ser",
.of_match_table = of_match_ptr(maxim_gmsl_hdmi_ser_dt_ids),
#ifdef CONFIG_PM
.pm = &maxim_gmsl_hdmi_ser_pm_ops,
#endif
},
.probe_new = maxim_gmsl_hdmi_ser_probe,
.remove = maxim_gmsl_hdmi_ser_remove,
};
module_i2c_driver(maxim_gmsl_hdmi_ser_i2c_driver);
MODULE_DESCRIPTION("Maxim MAX96751 Serializer Driver");
MODULE_AUTHOR("Sagar Tyagi");
MODULE_LICENSE("GPL");