| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * GPIO support for Cirrus Logic Madera codecs | |
| * | |
| * Copyright (C) 2015-2018 Cirrus Logic | |
| */ | |
| #include <linux/gpio/driver.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/mfd/madera/core.h> | |
| #include <linux/mfd/madera/pdata.h> | |
| #include <linux/mfd/madera/registers.h> | |
| struct madera_gpio { | |
| struct madera *madera; | |
| /* storage space for the gpio_chip we're using */ | |
| struct gpio_chip gpio_chip; | |
| }; | |
| static int madera_gpio_get_direction(struct gpio_chip *chip, | |
| unsigned int offset) | |
| { | |
| struct madera_gpio *madera_gpio = gpiochip_get_data(chip); | |
| struct madera *madera = madera_gpio->madera; | |
| unsigned int reg_offset = 2 * offset; | |
| unsigned int val; | |
| int ret; | |
| ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_2 + reg_offset, | |
| &val); | |
| if (ret < 0) | |
| return ret; | |
| if (val & MADERA_GP1_DIR_MASK) | |
| return GPIO_LINE_DIRECTION_IN; | |
| return GPIO_LINE_DIRECTION_OUT; | |
| } | |
| static int madera_gpio_direction_in(struct gpio_chip *chip, unsigned int offset) | |
| { | |
| struct madera_gpio *madera_gpio = gpiochip_get_data(chip); | |
| struct madera *madera = madera_gpio->madera; | |
| unsigned int reg_offset = 2 * offset; | |
| return regmap_update_bits(madera->regmap, | |
| MADERA_GPIO1_CTRL_2 + reg_offset, | |
| MADERA_GP1_DIR_MASK, MADERA_GP1_DIR); | |
| } | |
| static int madera_gpio_get(struct gpio_chip *chip, unsigned int offset) | |
| { | |
| struct madera_gpio *madera_gpio = gpiochip_get_data(chip); | |
| struct madera *madera = madera_gpio->madera; | |
| unsigned int reg_offset = 2 * offset; | |
| unsigned int val; | |
| int ret; | |
| ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_1 + reg_offset, | |
| &val); | |
| if (ret < 0) | |
| return ret; | |
| return !!(val & MADERA_GP1_LVL_MASK); | |
| } | |
| static int madera_gpio_direction_out(struct gpio_chip *chip, | |
| unsigned int offset, int value) | |
| { | |
| struct madera_gpio *madera_gpio = gpiochip_get_data(chip); | |
| struct madera *madera = madera_gpio->madera; | |
| unsigned int reg_offset = 2 * offset; | |
| unsigned int reg_val = value ? MADERA_GP1_LVL : 0; | |
| int ret; | |
| ret = regmap_update_bits(madera->regmap, | |
| MADERA_GPIO1_CTRL_2 + reg_offset, | |
| MADERA_GP1_DIR_MASK, 0); | |
| if (ret < 0) | |
| return ret; | |
| return regmap_update_bits(madera->regmap, | |
| MADERA_GPIO1_CTRL_1 + reg_offset, | |
| MADERA_GP1_LVL_MASK, reg_val); | |
| } | |
| static int madera_gpio_set(struct gpio_chip *chip, unsigned int offset, | |
| int value) | |
| { | |
| struct madera_gpio *madera_gpio = gpiochip_get_data(chip); | |
| struct madera *madera = madera_gpio->madera; | |
| unsigned int reg_offset = 2 * offset; | |
| unsigned int reg_val = value ? MADERA_GP1_LVL : 0; | |
| return regmap_update_bits(madera->regmap, | |
| MADERA_GPIO1_CTRL_1 + reg_offset, | |
| MADERA_GP1_LVL_MASK, reg_val); | |
| } | |
| static const struct gpio_chip madera_gpio_chip = { | |
| .label = "madera", | |
| .owner = THIS_MODULE, | |
| .request = gpiochip_generic_request, | |
| .free = gpiochip_generic_free, | |
| .get_direction = madera_gpio_get_direction, | |
| .direction_input = madera_gpio_direction_in, | |
| .get = madera_gpio_get, | |
| .direction_output = madera_gpio_direction_out, | |
| .set = madera_gpio_set, | |
| .set_config = gpiochip_generic_config, | |
| .can_sleep = true, | |
| }; | |
| static int madera_gpio_probe(struct platform_device *pdev) | |
| { | |
| struct madera *madera = dev_get_drvdata(pdev->dev.parent); | |
| struct madera_pdata *pdata = &madera->pdata; | |
| struct madera_gpio *madera_gpio; | |
| int ret; | |
| madera_gpio = devm_kzalloc(&pdev->dev, sizeof(*madera_gpio), | |
| GFP_KERNEL); | |
| if (!madera_gpio) | |
| return -ENOMEM; | |
| madera_gpio->madera = madera; | |
| /* Construct suitable gpio_chip from the template in madera_gpio_chip */ | |
| madera_gpio->gpio_chip = madera_gpio_chip; | |
| madera_gpio->gpio_chip.parent = pdev->dev.parent; | |
| switch (madera->type) { | |
| case CS47L15: | |
| madera_gpio->gpio_chip.ngpio = CS47L15_NUM_GPIOS; | |
| break; | |
| case CS47L35: | |
| madera_gpio->gpio_chip.ngpio = CS47L35_NUM_GPIOS; | |
| break; | |
| case CS47L85: | |
| case WM1840: | |
| madera_gpio->gpio_chip.ngpio = CS47L85_NUM_GPIOS; | |
| break; | |
| case CS47L90: | |
| case CS47L91: | |
| madera_gpio->gpio_chip.ngpio = CS47L90_NUM_GPIOS; | |
| break; | |
| case CS42L92: | |
| case CS47L92: | |
| case CS47L93: | |
| madera_gpio->gpio_chip.ngpio = CS47L92_NUM_GPIOS; | |
| break; | |
| default: | |
| dev_err(&pdev->dev, "Unknown chip variant %d\n", madera->type); | |
| return -EINVAL; | |
| } | |
| /* We want to be usable on systems that don't use devicetree or acpi */ | |
| if (pdata->gpio_base) | |
| madera_gpio->gpio_chip.base = pdata->gpio_base; | |
| else | |
| madera_gpio->gpio_chip.base = -1; | |
| ret = devm_gpiochip_add_data(&pdev->dev, | |
| &madera_gpio->gpio_chip, | |
| madera_gpio); | |
| if (ret < 0) { | |
| dev_dbg(&pdev->dev, "Could not register gpiochip, %d\n", ret); | |
| return ret; | |
| } | |
| /* | |
| * This is part of a composite MFD device which can only be used with | |
| * the corresponding pinctrl driver. On all supported silicon the GPIO | |
| * to pinctrl mapping is fixed in the silicon, so we register it | |
| * explicitly instead of requiring a redundant gpio-ranges in the | |
| * devicetree. | |
| * In any case we also want to work on systems that don't use devicetree | |
| * or acpi. | |
| */ | |
| ret = gpiochip_add_pin_range(&madera_gpio->gpio_chip, "madera-pinctrl", | |
| 0, 0, madera_gpio->gpio_chip.ngpio); | |
| if (ret) { | |
| dev_dbg(&pdev->dev, "Failed to add pin range (%d)\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static struct platform_driver madera_gpio_driver = { | |
| .driver = { | |
| .name = "madera-gpio", | |
| }, | |
| .probe = madera_gpio_probe, | |
| }; | |
| module_platform_driver(madera_gpio_driver); | |
| MODULE_SOFTDEP("pre: pinctrl-madera"); | |
| MODULE_DESCRIPTION("GPIO interface for Madera codecs"); | |
| MODULE_AUTHOR("Nariman Poushin <nariman@opensource.cirrus.com>"); | |
| MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>"); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_ALIAS("platform:madera-gpio"); |