// SPDX-License-Identifier: GPL-2.0-only /* * Tegra Ultrasonics Sensor Subsystem IO-Proxy driver * * SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include #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; struct gpio_desc *uss_reset_gpio; struct gpio_desc *vsup_dia_gpio; struct gpio_desc *vsup_fault_gpio; struct gpio_desc *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) gpiod_set_raw_value(proxy->uss_reset_gpio, 1); else gpiod_set_raw_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", !gpiod_get_raw_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) gpiod_set_raw_value(proxy->vsup_dia_gpio, 0); else gpiod_set_raw_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", gpiod_get_raw_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) gpiod_set_raw_value(proxy->vsup_latch_gpio, 0); else gpiod_set_raw_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", gpiod_get_raw_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", gpiod_get_raw_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 struct gpio_desc *tegra_uss_request_gpio(struct platform_device *pdev, const char *name, bool is_output) { struct gpio_desc *gpiod; int rc; gpiod = devm_gpiod_get(&pdev->dev, name, GPIOD_ASIS); if (IS_ERR(gpiod)) { dev_err(&pdev->dev, "could not request %s %ld\n", name, PTR_ERR(gpiod)); return gpiod; } if (is_output) rc = gpiod_direction_output_raw(gpiod, 0); else rc = gpiod_direction_input(gpiod); if (rc) { dev_err(&pdev->dev, "could not set direction for %s %d\n", name, rc); return ERR_PTR(rc); } return gpiod; } 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", true); if (IS_ERR(proxy->uss_reset_gpio)) { return dev_err_probe(&pdev->dev, PTR_ERR(proxy->uss_reset_gpio), "failed to get uss-nres GPIO\n"); } proxy->vsup_dia_gpio = tegra_uss_request_gpio(pdev, "uss-vsup-dia", true); if (IS_ERR(proxy->vsup_dia_gpio)) { return dev_err_probe(&pdev->dev, PTR_ERR(proxy->vsup_dia_gpio), "failed to get vsup-dia GPIO\n"); } if (proxy->hss_version == 1) { proxy->vsup_fault_gpio = tegra_uss_request_gpio(pdev, "uss-vsup-latch", true); if (IS_ERR(proxy->vsup_fault_gpio)) return dev_err_probe(&pdev->dev, PTR_ERR(proxy->vsup_fault_gpio), "failed to get vsup-latch GPIO\n"); } if (proxy->hss_version == 2) { proxy->vsup_fault_gpio = tegra_uss_request_gpio(pdev, "uss-vsup-fault", false); if (IS_ERR(proxy->vsup_fault_gpio)) return dev_err_probe(&pdev->dev, PTR_ERR(proxy->vsup_fault_gpio), "failed to get vsup-fault GPIO\n"); } 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; gpiod_set_raw_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; } #if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */ static void tegra_uss_io_proxy_remove_wrapper(struct platform_device *pdev) { tegra_uss_io_proxy_remove(pdev); } #else static int tegra_uss_io_proxy_remove_wrapper(struct platform_device *pdev) { return tegra_uss_io_proxy_remove(pdev); } #endif static struct platform_driver tegra_uss_io_proxy_driver = { .probe = tegra_uss_io_proxy_probe, .remove = tegra_uss_io_proxy_remove_wrapper, .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");