diff --git a/drivers/video/tegra/dc/bridge/Makefile b/drivers/video/tegra/dc/bridge/Makefile index 2a268425..4adfe47c 100644 --- a/drivers/video/tegra/dc/bridge/Makefile +++ b/drivers/video/tegra/dc/bridge/Makefile @@ -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 diff --git a/drivers/video/tegra/dc/bridge/maxim_gmsl_hdmi_serializer.c b/drivers/video/tegra/dc/bridge/maxim_gmsl_hdmi_serializer.c new file mode 100644 index 00000000..aafff28b --- /dev/null +++ b/drivers/video/tegra/dc/bridge/maxim_gmsl_hdmi_serializer.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");