mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
In Linux v6.7, both gpio_device_find() and gpio_device_get_chip() were
added. However, don't assume that if one is present then so is the other
and so add a test to check if gpio_device_get_chip() is also present.
Bug 4471899
Change-Id: I0c547a52b0f397aef43884ab76d815573e8ed3f8
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3112133
(cherry picked from commit 9458d6b97f)
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3119068
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
360 lines
7.9 KiB
C
360 lines
7.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2017-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/nospec.h>
|
|
|
|
#include "isc-gpio-priv.h"
|
|
|
|
#define MAX_STR_SIZE 255
|
|
|
|
static int of_isc_gpio_pdata(struct platform_device *pdev,
|
|
struct isc_gpio_plat_data *pdata)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int err;
|
|
|
|
err = of_property_read_string(np, "parent-gpio-chip",
|
|
&pdata->gpio_prnt_chip);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = of_property_read_u32(pdev->dev.of_node,
|
|
"max-gpios", &pdata->max_gpio);
|
|
|
|
return err;
|
|
}
|
|
|
|
#if defined(NV_GPIO_DEVICE_FIND_HAS_CONST_DATA_ARG) /* Linux v6.9 */
|
|
static int isc_gpio_chip_match(struct gpio_chip *chip, const void *data)
|
|
#else
|
|
static int isc_gpio_chip_match(struct gpio_chip *chip, void *data)
|
|
#endif
|
|
{
|
|
return !strcmp(chip->label, data);
|
|
}
|
|
|
|
static struct gpio_chip *isc_gpio_get_chip(struct platform_device *pdev,
|
|
struct isc_gpio_plat_data *pd)
|
|
{
|
|
struct gpio_chip *gc = NULL;
|
|
char name[MAX_STR_SIZE];
|
|
#if defined(NV_GPIO_DEVICE_FIND_PRESENT) && \
|
|
defined(NV_GPIO_DEVICE_GET_CHIP_PRESENT) /* Linux 6.7 */
|
|
struct gpio_device *gdev;
|
|
#endif
|
|
|
|
if (strlen(pd->gpio_prnt_chip) > MAX_STR_SIZE) {
|
|
dev_err(&pdev->dev, "%s: gpio chip name is too long: %s\n",
|
|
__func__, pd->gpio_prnt_chip);
|
|
return NULL;
|
|
}
|
|
strcpy(name, pd->gpio_prnt_chip);
|
|
|
|
#if defined(NV_GPIO_DEVICE_FIND_PRESENT) && \
|
|
defined(NV_GPIO_DEVICE_GET_CHIP_PRESENT) /* Linux 6.7 */
|
|
gdev = gpio_device_find(name, isc_gpio_chip_match);
|
|
if (gdev) {
|
|
gc = gpio_device_get_chip(gdev);
|
|
gpio_device_put(gdev);
|
|
}
|
|
#else
|
|
gc = gpiochip_find(name, isc_gpio_chip_match);
|
|
#endif
|
|
if (!gc) {
|
|
dev_err(&pdev->dev, "%s: unable to find gpio parent chip %s\n",
|
|
__func__, pd->gpio_prnt_chip);
|
|
return NULL;
|
|
}
|
|
|
|
return gc;
|
|
}
|
|
|
|
static int isc_gpio_init_desc(struct platform_device *pdev,
|
|
struct isc_gpio_priv *isc_gpio)
|
|
{
|
|
struct isc_gpio_desc *desc = NULL;
|
|
u32 i;
|
|
|
|
desc = devm_kzalloc(&pdev->dev,
|
|
(sizeof(struct isc_gpio_desc) *
|
|
isc_gpio->pdata.max_gpio),
|
|
GFP_KERNEL);
|
|
if (!desc) {
|
|
dev_err(&pdev->dev, "Unable to allocate memory!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < isc_gpio->pdata.max_gpio; i++) {
|
|
desc[i].gpio = 0;
|
|
atomic_set(&desc[i].ref_cnt, 0);
|
|
}
|
|
|
|
isc_gpio->gpios = desc;
|
|
return 0;
|
|
}
|
|
|
|
static int isc_gpio_get_index(struct device *dev,
|
|
struct isc_gpio_priv *isc_gpio, u32 gpio)
|
|
{
|
|
u32 i;
|
|
int idx = -1;
|
|
|
|
/* find gpio in array */
|
|
for (i = 0; i < isc_gpio->num_gpio; i++) {
|
|
if (isc_gpio->gpios[i].gpio == gpio) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* gpio exists, return idx */
|
|
if (idx >= 0)
|
|
return idx;
|
|
|
|
/* add gpio if it doesn't exist and there is memory available */
|
|
if (isc_gpio->num_gpio < isc_gpio->pdata.max_gpio) {
|
|
idx = isc_gpio->num_gpio;
|
|
isc_gpio->gpios[idx].gpio = gpio;
|
|
isc_gpio->num_gpio++;
|
|
|
|
return idx;
|
|
}
|
|
|
|
dev_err(dev, "%s: Unable to add gpio to desc\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
static int isc_gpio_direction_input(struct gpio_chip *gc, unsigned int off)
|
|
{
|
|
struct gpio_chip *tgc = NULL;
|
|
struct isc_gpio_priv *isc_gpio = NULL;
|
|
int err;
|
|
|
|
isc_gpio = gpiochip_get_data(gc);
|
|
if (!isc_gpio)
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&isc_gpio->mutex);
|
|
|
|
tgc = isc_gpio->tgc;
|
|
err = tgc->direction_input(tgc, off);
|
|
|
|
mutex_unlock(&isc_gpio->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int isc_gpio_direction_output(struct gpio_chip *gc, unsigned int off,
|
|
int val)
|
|
{
|
|
struct gpio_chip *tgc = NULL;
|
|
struct isc_gpio_priv *isc_gpio = NULL;
|
|
int err;
|
|
|
|
isc_gpio = gpiochip_get_data(gc);
|
|
if (!isc_gpio)
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&isc_gpio->mutex);
|
|
|
|
tgc = isc_gpio->tgc;
|
|
err = tgc->direction_output(tgc, off, val);
|
|
|
|
mutex_unlock(&isc_gpio->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int isc_gpio_get_value(struct gpio_chip *gc, unsigned int off)
|
|
{
|
|
int gpio_val;
|
|
struct gpio_chip *tgc = NULL;
|
|
struct isc_gpio_priv *isc_gpio = NULL;
|
|
|
|
isc_gpio = gpiochip_get_data(gc);
|
|
if (!isc_gpio)
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&isc_gpio->mutex);
|
|
|
|
tgc = isc_gpio->tgc;
|
|
gpio_val = tgc->get(tgc, off);
|
|
|
|
mutex_unlock(&isc_gpio->mutex);
|
|
|
|
return gpio_val;
|
|
}
|
|
|
|
static void isc_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val)
|
|
{
|
|
int idx;
|
|
struct gpio_chip *tgc = NULL;
|
|
struct isc_gpio_priv *isc_gpio = NULL;
|
|
atomic_t *ref_cnt;
|
|
struct device *dev = NULL;
|
|
|
|
isc_gpio = gpiochip_get_data(gc);
|
|
if (!isc_gpio)
|
|
return;
|
|
|
|
mutex_lock(&isc_gpio->mutex);
|
|
|
|
dev = isc_gpio->pdev;
|
|
tgc = isc_gpio->tgc;
|
|
|
|
idx = isc_gpio_get_index(dev, isc_gpio, off);
|
|
if (idx < 0) {
|
|
mutex_unlock(&isc_gpio->mutex);
|
|
return;
|
|
}
|
|
idx = array_index_nospec(idx, isc_gpio->pdata.max_gpio);
|
|
|
|
/* set gpio value based on refcount */
|
|
ref_cnt = &isc_gpio->gpios[idx].ref_cnt;
|
|
switch (val) {
|
|
case 0:
|
|
if ((atomic_read(ref_cnt) > 0) &&
|
|
atomic_dec_and_test(ref_cnt)) {
|
|
tgc->set(tgc, off, val);
|
|
}
|
|
dev_info(dev, "%s: gpio idx: %d, val to set: %d, refcount: %d\n",
|
|
__func__, idx, val, atomic_read(ref_cnt));
|
|
break;
|
|
case 1:
|
|
if (!atomic_inc_and_test(ref_cnt))
|
|
tgc->set(tgc, off, val);
|
|
|
|
dev_info(dev, "%s: gpio idx: %d, val to set: %d, refcount: %d\n",
|
|
__func__, idx, val, atomic_read(ref_cnt));
|
|
break;
|
|
default:
|
|
dev_err(dev, "%s: Invalid gpio value provided\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&isc_gpio->mutex);
|
|
}
|
|
|
|
static int isc_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct isc_gpio_priv *isc_gpio;
|
|
struct isc_gpio_plat_data *pd = NULL;
|
|
struct gpio_chip *tgc, *gc;
|
|
int err;
|
|
|
|
dev_info(&pdev->dev, "probing %s...\n", __func__);
|
|
|
|
isc_gpio = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct isc_gpio_priv),
|
|
GFP_KERNEL);
|
|
if (!isc_gpio) {
|
|
dev_err(&pdev->dev, "Unable to allocate memory!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* get platform data from device tree */
|
|
err = of_isc_gpio_pdata(pdev, &isc_gpio->pdata);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
pd = &isc_gpio->pdata;
|
|
|
|
/* get tegra gpio chip */
|
|
tgc = isc_gpio_get_chip(pdev, pd);
|
|
if (!tgc)
|
|
return -ENXIO;
|
|
|
|
isc_gpio->tgc = tgc;
|
|
|
|
/* initialize gpio desc */
|
|
err = isc_gpio_init_desc(pdev, isc_gpio);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
isc_gpio->num_gpio = 0;
|
|
|
|
/* setup gpio chip */
|
|
gc = &isc_gpio->gpio_chip;
|
|
gc->direction_input = isc_gpio_direction_input;
|
|
gc->direction_output = isc_gpio_direction_output;
|
|
gc->get = isc_gpio_get_value;
|
|
gc->set = isc_gpio_set_value;
|
|
|
|
gc->can_sleep = false;
|
|
gc->base = -1;
|
|
gc->ngpio = pd->max_gpio;
|
|
gc->label = "isc-gpio";
|
|
gc->parent = &pdev->dev;
|
|
#if defined(NV_GPIO_CHIP_STRUCT_HAS_OF_NODE_PRESENT) /* Linux 6.2 */
|
|
gc->of_node = pdev->dev.of_node;
|
|
#endif
|
|
gc->owner = THIS_MODULE;
|
|
|
|
err = gpiochip_add_data(gc, isc_gpio);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to add GPIO controller\n");
|
|
return err;
|
|
}
|
|
|
|
mutex_init(&isc_gpio->mutex);
|
|
isc_gpio->pdev = &pdev->dev;
|
|
dev_set_drvdata(&pdev->dev, isc_gpio);
|
|
|
|
dev_info(&pdev->dev, "%s: successfully registered gpio device\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
|
|
static int isc_gpio_remove(struct platform_device *pdev)
|
|
{
|
|
struct isc_gpio_priv *isc_gpio = platform_get_drvdata(pdev);
|
|
|
|
gpiochip_remove(&isc_gpio->gpio_chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id isc_gpio_dt_ids[] = {
|
|
{ .compatible = "nvidia,isc-gpio", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, isc_gpio_dt_ids);
|
|
|
|
static struct platform_driver isc_gpio_driver = {
|
|
.probe = isc_gpio_probe,
|
|
.remove = isc_gpio_remove,
|
|
.driver = {
|
|
.name = "isc-gpio",
|
|
.of_match_table = isc_gpio_dt_ids,
|
|
.owner = THIS_MODULE,
|
|
}
|
|
};
|
|
|
|
static int __init isc_gpio_init(void)
|
|
{
|
|
return platform_driver_register(&isc_gpio_driver);
|
|
}
|
|
|
|
static void __exit isc_gpio_exit(void)
|
|
{
|
|
platform_driver_unregister(&isc_gpio_driver);
|
|
}
|
|
|
|
/* call in subsys so that this module loads before isc-mgr driver */
|
|
subsys_initcall(isc_gpio_init);
|
|
module_exit(isc_gpio_exit);
|
|
|
|
MODULE_DESCRIPTION("Tegra Auto ISC GPIO Driver");
|
|
MODULE_AUTHOR("Anurag Dosapati <adosapati@nvidia.com>");
|
|
MODULE_LICENSE("GPL v2");
|