From d7d941e745b2e03b6086eb34e161480cb35c9f37 Mon Sep 17 00:00:00 2001 From: JC Kuo Date: Tue, 11 Apr 2023 11:57:13 +0000 Subject: [PATCH] drivers: tegra: Add Tegra USS io-proxy driver This commit adds the Tegra USS io-proxy driver which allows userspace to control clock and GPIO signals to the Ultrasonic sensor subsystem found on Tegra platform. Bug 3360749 Change-Id: I0b765e8b9e14b7de87f0f9205237d4dbfdd9f8f5 Signed-off-by: JC Kuo Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2886017 Reviewed-by: Laxman Dewangan GVS: Gerrit_Virtual_Submit --- drivers/platform/tegra/Makefile | 1 + drivers/platform/tegra/tegra-uss-io-proxy.c | 513 ++++++++++++++++++++ 2 files changed, 514 insertions(+) create mode 100644 drivers/platform/tegra/tegra-uss-io-proxy.c diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index 59e08553..d36961fd 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -32,3 +32,4 @@ obj-m += dce/ obj-m += psc/ obj-m += rtcpu/ obj-m += aon/ +obj-m += tegra-uss-io-proxy.o diff --git a/drivers/platform/tegra/tegra-uss-io-proxy.c b/drivers/platform/tegra/tegra-uss-io-proxy.c new file mode 100644 index 00000000..de61695f --- /dev/null +++ b/drivers/platform/tegra/tegra-uss-io-proxy.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tegra Ultrasonics Sensor Subsystem IO-Proxy driver + * + * Copyright (c) 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SAFETY_I2S_CTRL_0 (0x114) +#define MASTER (1 << 5) + +struct tegra_uss_io_proxy { + void __iomem *i2s8_base; + struct reset_control *i2s8_reset; + struct clk *uss_clk; + + unsigned int hss_version; + int uss_reset_gpio; + int vsup_dia_gpio; + unsigned int vsup_fault_gpio; + unsigned int vsup_latch_gpio; + struct gpio_descs *g1_gpiods; + struct gpio_descs *g2_gpiods; + unsigned long g1_bitmap; + unsigned long g2_bitmap; +}; + +static const struct of_device_id tegra_uss_io_proxy_of_match[] = { + { .compatible = "nvidia,uss-io-proxy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_uss_io_proxy_of_match); + +static bool uss_clk_enabled; +static ssize_t uss_clk_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + int val, rc; + u32 reg; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + if (val == 0) { + rc = reset_control_assert(proxy->i2s8_reset); + if (rc) { + dev_err(dev, "i2s8 reset assert failed: %d\n", rc); + return -EIO; + } + clk_disable_unprepare(proxy->uss_clk); + uss_clk_enabled = false; + } else { + rc = clk_prepare_enable(proxy->uss_clk); + if (rc) { + dev_err(dev, "i2s8 clock enable failed: %d\n", rc); + return -EIO; + } + + rc = reset_control_deassert(proxy->i2s8_reset); + if (rc) { + dev_err(dev, "i2s8 reset deassert failed: %d\n", rc); + clk_disable_unprepare(proxy->uss_clk); + return -EIO; + } + + reg = ioread32(proxy->i2s8_base + SAFETY_I2S_CTRL_0); + reg |= MASTER; + iowrite32(reg, proxy->i2s8_base + SAFETY_I2S_CTRL_0); + + uss_clk_enabled = true; + } + + return count; +} + +static ssize_t uss_clk_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (uss_clk_enabled) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static DEVICE_ATTR_RW(uss_clk); + +static ssize_t uss_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + if (val == 0) + gpio_set_value(proxy->uss_reset_gpio, 1); + else + gpio_set_value(proxy->uss_reset_gpio, 0); + + return count; +} + +static ssize_t uss_reset_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", !gpio_get_value(proxy->uss_reset_gpio)); +} + +static DEVICE_ATTR_RW(uss_reset); + +static ssize_t vsup_dia_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + if (val == 0) + gpio_set_value(proxy->vsup_dia_gpio, 0); + else + gpio_set_value(proxy->vsup_dia_gpio, 1); + + return count; +} + +static ssize_t vsup_dia_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", gpio_get_value(proxy->vsup_dia_gpio)); +} + +static DEVICE_ATTR_RW(vsup_dia); + +static ssize_t vsup_latch_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + if (val == 0) + gpio_set_value(proxy->vsup_latch_gpio, 0); + else + gpio_set_value(proxy->vsup_latch_gpio, 1); + + return count; +} + +static ssize_t vsup_latch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", gpio_get_value(proxy->vsup_latch_gpio)); +} + +static DEVICE_ATTR_RW(vsup_latch); + +static ssize_t vsup_fault_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", gpio_get_value(proxy->vsup_fault_gpio)); +} + +static DEVICE_ATTR_ADMIN_RO(vsup_fault); + +static ssize_t sensor_gpios_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + unsigned long bitmap; + int rc; + u32 val; + u8 *b = (u8 *)&val; + + if (kstrtou32(buf, 16, &val)) + return -EINVAL; + + if (b[2] != 0) { + bitmap = proxy->g1_bitmap & (~b[2]); + bitmap |= (b[0] & b[2]); + rc = gpiod_set_array_value_cansleep(proxy->g1_gpiods->ndescs, + proxy->g1_gpiods->desc, + proxy->g1_gpiods->info, + &bitmap); + if (rc) { + dev_err(dev, "set group-1 GPIOs failed %d\n", rc); + return rc; + } + proxy->g1_bitmap = bitmap; + } + + if (b[3] != 0) { + bitmap = proxy->g2_bitmap & (~b[3]); + bitmap |= (b[1] & b[3]); + rc = gpiod_set_array_value_cansleep(proxy->g2_gpiods->ndescs, + proxy->g2_gpiods->desc, + proxy->g2_gpiods->info, + &bitmap); + if (rc) { + dev_err(dev, "set group-2 GPIOs failed %d\n", rc); + return rc; + } + + proxy->g2_bitmap = bitmap; + } + + return count; +} + +static ssize_t sensor_gpios_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_uss_io_proxy *proxy = dev_get_drvdata(dev); + size_t n = PAGE_SIZE; + int i; + + n -= snprintf(&buf[PAGE_SIZE - n], n, "Sensor Group 1\n"); + for (i = 0; i < 6; i++) { + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tSensor %d: %d\n", + i + 1, !!(proxy->g1_bitmap & (1 << i))); + } + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tVSUP_EN1: %d\n", + !!(proxy->g1_bitmap & (1 << 6))); + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tVSUP_SEL1: %d\n", + !!(proxy->g1_bitmap & (1 << 7))); + + n -= snprintf(&buf[PAGE_SIZE - n], n, "Sensor Group 2\n"); + for (i = 0; i < 6; i++) { + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tSensor %d: %d\n", + i + 1, !!(proxy->g2_bitmap & (1 << i))); + } + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tVSUP_EN2: %d\n", + !!(proxy->g2_bitmap & (1 << 6))); + if (proxy->hss_version == 1) { + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tVSUP_SEL2: %d\n", + !!(proxy->g2_bitmap & (1 << 7))); + } + if (proxy->hss_version == 2) { + n -= snprintf(&buf[PAGE_SIZE - n], n, "\tVSUP_LATCH: %d\n", + !!(proxy->g2_bitmap & (1 << 7))); + } + + return PAGE_SIZE - n; +} + +static DEVICE_ATTR_RW(sensor_gpios); + +static int tegra_uss_create_dev_attrs(struct platform_device *pdev) +{ + struct tegra_uss_io_proxy *proxy = platform_get_drvdata(pdev); + int err; + + err = device_create_file(&pdev->dev, &dev_attr_uss_clk); + if (err) + return err; + + err = device_create_file(&pdev->dev, &dev_attr_uss_reset); + if (err) + goto err_reset; + + err = device_create_file(&pdev->dev, &dev_attr_vsup_dia); + if (err) + goto err_vsup_dia; + + if (proxy->hss_version == 1) + err = device_create_file(&pdev->dev, &dev_attr_vsup_latch); + + if (proxy->hss_version == 2) + err = device_create_file(&pdev->dev, &dev_attr_vsup_fault); + + if (err) + goto err_vsup_latch_fault; + + err = device_create_file(&pdev->dev, &dev_attr_sensor_gpios); + if (err) + goto err_sensor_gpios; + + return 0; + + +err_sensor_gpios: + if (proxy->hss_version == 1) + device_remove_file(&pdev->dev, &dev_attr_vsup_latch); + if (proxy->hss_version == 2) + device_remove_file(&pdev->dev, &dev_attr_vsup_fault); +err_vsup_latch_fault: + device_remove_file(&pdev->dev, &dev_attr_vsup_dia); +err_vsup_dia: + device_remove_file(&pdev->dev, &dev_attr_uss_reset); +err_reset: + device_remove_file(&pdev->dev, &dev_attr_uss_clk); + return err; +} + +static void tegra_uss_remove_dev_attrs(struct platform_device *pdev) +{ + struct tegra_uss_io_proxy *proxy = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_uss_clk); + device_remove_file(&pdev->dev, &dev_attr_uss_reset); + device_remove_file(&pdev->dev, &dev_attr_vsup_dia); + if (proxy->hss_version == 1) + device_remove_file(&pdev->dev, &dev_attr_vsup_latch); + if (proxy->hss_version == 2) + device_remove_file(&pdev->dev, &dev_attr_vsup_fault); + device_remove_file(&pdev->dev, &dev_attr_sensor_gpios); +} + +static int tegra_uss_request_gpio(struct platform_device *pdev, const char *name) +{ + struct device_node *np = pdev->dev.of_node; + int gpio, rc; + + gpio = of_get_named_gpio(np, name, 0); + if (gpio == -EPROBE_DEFER) + return gpio; + + if (!gpio_is_valid(gpio)) { + dev_err(&pdev->dev, "%s is invalid\n", name); + return -EINVAL; + } + + rc = devm_gpio_request(&pdev->dev, gpio, name); + if (rc) { + dev_err(&pdev->dev, "could not request %s %d\n", name, rc); + return rc; + } + + rc = gpio_direction_output(gpio, 0); + if (rc) { + dev_err(&pdev->dev, "could not set %s output %d\n", name, rc); + return rc; + } + + return gpio; +} + +static int tegra_uss_io_proxy_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct tegra_uss_io_proxy *proxy; + int rc; + + proxy = devm_kzalloc(&pdev->dev, sizeof(*proxy), GFP_KERNEL); + if (unlikely(proxy == NULL)) + return -ENOMEM; + + proxy->i2s8_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(proxy->i2s8_base)) { + return dev_err_probe(&pdev->dev, PTR_ERR(proxy->i2s8_base), + "failed to get I2S8 mmio\n"); + } + + proxy->uss_clk = devm_clk_get(&pdev->dev, "i2s8"); + if (IS_ERR(proxy->uss_clk)) { + return dev_err_probe(&pdev->dev, PTR_ERR(proxy->uss_clk), + "failed to get I2S8 clock\n"); + } + + proxy->i2s8_reset = devm_reset_control_get(&pdev->dev, "i2s8"); + if (IS_ERR(proxy->i2s8_reset)) { + return dev_err_probe(&pdev->dev, PTR_ERR(proxy->i2s8_reset), + "failed to get I2S8 reset\n"); + } + + rc = of_property_read_u32(node, "uss-hss-version", &proxy->hss_version); + if (rc != 0) { + /* backward compatibility */ + dev_info(&pdev->dev, "uss-hss-version property doesn't exist, version=1\n"); + proxy->hss_version = 1; + } + + if (proxy->hss_version > 2) { + return dev_err_probe(&pdev->dev, -EINVAL, + "uss_hss_version is not valid."); + } + + proxy->uss_reset_gpio = tegra_uss_request_gpio(pdev, "uss-nres"); + if (proxy->uss_reset_gpio < 0) { + return dev_err_probe(&pdev->dev, proxy->uss_reset_gpio, + "failed to get uss-nres GPIO\n"); + } + + proxy->vsup_dia_gpio = tegra_uss_request_gpio(pdev, "uss-vsup-dia"); + if (proxy->vsup_dia_gpio < 0) { + return dev_err_probe(&pdev->dev, proxy->vsup_dia_gpio, + "failed to get vsup-dia GPIO\n"); + } + + if (proxy->hss_version == 1) { + rc = tegra_uss_request_gpio(pdev, "uss-vsup-latch"); + if (rc < 0) + return dev_err_probe(&pdev->dev, rc, "failed to get vsup-latch GPIO\n"); + + proxy->vsup_fault_gpio = rc; + } + + if (proxy->hss_version == 2) { + rc = tegra_uss_request_gpio(pdev, "uss-vsup-fault"); + if (rc < 0) + return dev_err_probe(&pdev->dev, rc, "failed to get vsup-fault GPIO\n"); + + proxy->vsup_fault_gpio = rc; + gpio_direction_input(proxy->vsup_fault_gpio); + } + + proxy->g1_gpiods = devm_gpiod_get_array(&pdev->dev, "sensor-group-1", + GPIOD_OUT_LOW); + if (IS_ERR(proxy->g1_gpiods)) { + return dev_err_probe(&pdev->dev, PTR_ERR(proxy->g1_gpiods), + "failed to get sensor-group-1 GPIOs\n"); + } + + proxy->g2_gpiods = devm_gpiod_get_array(&pdev->dev, "sensor-group-2", + GPIOD_OUT_LOW); + if (IS_ERR(proxy->g2_gpiods)) { + return dev_err_probe(&pdev->dev, PTR_ERR(proxy->g2_gpiods), + "failed to get sensor-group-2 GPIOs\n"); + } + + platform_set_drvdata(pdev, proxy); + tegra_uss_create_dev_attrs(pdev); + + return 0; +} + +static void tegra_uss_reset(struct platform_device *pdev) +{ + struct tegra_uss_io_proxy *proxy = platform_get_drvdata(pdev); + unsigned long bitmap = 0; + int rc; + + gpio_set_value(proxy->uss_reset_gpio, 0); + + rc = reset_control_assert(proxy->i2s8_reset); + if (rc) + dev_err(&pdev->dev, "i2s8 reset assert failed: %d\n", rc); + + clk_disable_unprepare(proxy->uss_clk); + + rc = gpiod_set_array_value_cansleep(proxy->g1_gpiods->ndescs, + proxy->g1_gpiods->desc, + proxy->g1_gpiods->info, + &bitmap); + if (rc) + dev_err(&pdev->dev, "set group-1 GPIOs failed %d\n", rc); + + proxy->g1_bitmap = 0; + + rc = gpiod_set_array_value_cansleep(proxy->g2_gpiods->ndescs, + proxy->g2_gpiods->desc, + proxy->g2_gpiods->info, + &bitmap); + if (rc) + dev_err(&pdev->dev, "set group-2 GPIOs failed %d\n", rc); + + proxy->g1_bitmap = 0; + +} + +static int tegra_uss_io_proxy_remove(struct platform_device *pdev) +{ + tegra_uss_remove_dev_attrs(pdev); + tegra_uss_reset(pdev); + return 0; +} + +static struct platform_driver tegra_uss_io_proxy_driver = { + .probe = tegra_uss_io_proxy_probe, + .remove = tegra_uss_io_proxy_remove, + .driver = { + .name = "tegra-uss-io-proxy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_uss_io_proxy_of_match), + }, +}; + +module_platform_driver(tegra_uss_io_proxy_driver); + +MODULE_AUTHOR("JC Kuo "); +MODULE_DESCRIPTION("NVIDIA Tegra USS IO-PROXY driver"); +MODULE_LICENSE("GPL");