| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * CY8C95X0 20/40/60 pin I2C GPIO port expander with interrupt support |
| * |
| * Copyright (C) 2022 9elements GmbH |
| * Authors: Patrick Rudolph <patrick.rudolph@9elements.com> |
| * Naresh Solanki <Naresh.Solanki@9elements.com> |
| */ |
| |
| #include <linux/acpi.h> |
| #include <linux/bitmap.h> |
| #include <linux/dmi.h> |
| #include <linux/gpio/driver.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/property.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/seq_file.h> |
| |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/pinctrl/pinconf-generic.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.h> |
| |
| /* Fast access registers */ |
| #define CY8C95X0_INPUT 0x00 |
| #define CY8C95X0_OUTPUT 0x08 |
| #define CY8C95X0_INTSTATUS 0x10 |
| |
| #define CY8C95X0_INPUT_(x) (CY8C95X0_INPUT + (x)) |
| #define CY8C95X0_OUTPUT_(x) (CY8C95X0_OUTPUT + (x)) |
| #define CY8C95X0_INTSTATUS_(x) (CY8C95X0_INTSTATUS + (x)) |
| |
| /* Port Select configures the port */ |
| #define CY8C95X0_PORTSEL 0x18 |
| /* Port settings, write PORTSEL first */ |
| #define CY8C95X0_INTMASK 0x19 |
| #define CY8C95X0_PWMSEL 0x1A |
| #define CY8C95X0_INVERT 0x1B |
| #define CY8C95X0_DIRECTION 0x1C |
| /* Drive mode register change state on writing '1' */ |
| #define CY8C95X0_DRV_PU 0x1D |
| #define CY8C95X0_DRV_PD 0x1E |
| #define CY8C95X0_DRV_ODH 0x1F |
| #define CY8C95X0_DRV_ODL 0x20 |
| #define CY8C95X0_DRV_PP_FAST 0x21 |
| #define CY8C95X0_DRV_PP_SLOW 0x22 |
| #define CY8C95X0_DRV_HIZ 0x23 |
| #define CY8C95X0_DEVID 0x2E |
| #define CY8C95X0_WATCHDOG 0x2F |
| #define CY8C95X0_COMMAND 0x30 |
| |
| #define CY8C95X0_PIN_TO_OFFSET(x) (((x) >= 20) ? ((x) + 4) : (x)) |
| |
| #define CY8C95X0_MUX_REGMAP_TO_PORT(x) ((x) / MUXED_STRIDE) |
| #define CY8C95X0_MUX_REGMAP_TO_REG(x) (((x) % MUXED_STRIDE) + CY8C95X0_INTMASK) |
| #define CY8C95X0_MUX_REGMAP_TO_OFFSET(x, p) ((x) - CY8C95X0_INTMASK + (p) * MUXED_STRIDE) |
| |
| static const struct i2c_device_id cy8c95x0_id[] = { |
| { "cy8c9520", 20, }, |
| { "cy8c9540", 40, }, |
| { "cy8c9560", 60, }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, cy8c95x0_id); |
| |
| #define OF_CY8C95X(__nrgpio) ((void *)(__nrgpio)) |
| |
| static const struct of_device_id cy8c95x0_dt_ids[] = { |
| { .compatible = "cypress,cy8c9520", .data = OF_CY8C95X(20), }, |
| { .compatible = "cypress,cy8c9540", .data = OF_CY8C95X(40), }, |
| { .compatible = "cypress,cy8c9560", .data = OF_CY8C95X(60), }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, cy8c95x0_dt_ids); |
| |
| static const struct acpi_gpio_params cy8c95x0_irq_gpios = { 0, 0, true }; |
| |
| static const struct acpi_gpio_mapping cy8c95x0_acpi_irq_gpios[] = { |
| { "irq-gpios", &cy8c95x0_irq_gpios, 1, ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER }, |
| { } |
| }; |
| |
| static int cy8c95x0_acpi_get_irq(struct device *dev) |
| { |
| int ret; |
| |
| ret = devm_acpi_dev_add_driver_gpios(dev, cy8c95x0_acpi_irq_gpios); |
| if (ret) |
| dev_warn(dev, "can't add GPIO ACPI mapping\n"); |
| |
| ret = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), "irq-gpios", 0); |
| if (ret < 0) |
| return ret; |
| |
| dev_info(dev, "ACPI interrupt quirk (IRQ %d)\n", ret); |
| return ret; |
| } |
| |
| static const struct dmi_system_id cy8c95x0_dmi_acpi_irq_info[] = { |
| { |
| /* |
| * On Intel Galileo Gen 1 board the IRQ pin is provided |
| * as an absolute number instead of being relative. |
| * Since first controller (gpio-sch.c) and second |
| * (gpio-dwapb.c) are at the fixed bases, we may safely |
| * refer to the number in the global space to get an IRQ |
| * out of it. |
| */ |
| .matches = { |
| DMI_EXACT_MATCH(DMI_BOARD_NAME, "Galileo"), |
| }, |
| }, |
| {} |
| }; |
| |
| #define MAX_BANK 8 |
| #define BANK_SZ 8 |
| #define MAX_LINE (MAX_BANK * BANK_SZ) |
| #define MUXED_STRIDE 16 |
| #define CY8C95X0_GPIO_MASK GENMASK(7, 0) |
| |
| /** |
| * struct cy8c95x0_pinctrl - driver data |
| * @regmap: Device's regmap. Only direct access registers. |
| * @muxed_regmap: Regmap for all muxed registers. |
| * @irq_lock: IRQ bus lock |
| * @i2c_lock: Mutex for the device internal mux register |
| * @irq_mask: I/O bits affected by interrupts |
| * @irq_trig_raise: I/O bits affected by raising voltage level |
| * @irq_trig_fall: I/O bits affected by falling voltage level |
| * @irq_trig_low: I/O bits affected by a low voltage level |
| * @irq_trig_high: I/O bits affected by a high voltage level |
| * @push_pull: I/O bits configured as push pull driver |
| * @shiftmask: Mask used to compensate for Gport2 width |
| * @nport: Number of Gports in this chip |
| * @gpio_chip: gpiolib chip |
| * @driver_data: private driver data |
| * @regulator: Pointer to the regulator for the IC |
| * @dev: struct device |
| * @pctldev: pin controller device |
| * @pinctrl_desc: pin controller description |
| * @name: Chip controller name |
| * @tpin: Total number of pins |
| * @gpio_reset: GPIO line handler that can reset the IC |
| */ |
| struct cy8c95x0_pinctrl { |
| struct regmap *regmap; |
| struct regmap *muxed_regmap; |
| struct mutex irq_lock; |
| struct mutex i2c_lock; |
| DECLARE_BITMAP(irq_mask, MAX_LINE); |
| DECLARE_BITMAP(irq_trig_raise, MAX_LINE); |
| DECLARE_BITMAP(irq_trig_fall, MAX_LINE); |
| DECLARE_BITMAP(irq_trig_low, MAX_LINE); |
| DECLARE_BITMAP(irq_trig_high, MAX_LINE); |
| DECLARE_BITMAP(push_pull, MAX_LINE); |
| DECLARE_BITMAP(shiftmask, MAX_LINE); |
| int nport; |
| struct gpio_chip gpio_chip; |
| unsigned long driver_data; |
| struct regulator *regulator; |
| struct device *dev; |
| struct pinctrl_dev *pctldev; |
| struct pinctrl_desc pinctrl_desc; |
| char name[32]; |
| unsigned int tpin; |
| struct gpio_desc *gpio_reset; |
| }; |
| |
| static const struct pinctrl_pin_desc cy8c9560_pins[] = { |
| PINCTRL_PIN(0, "gp00"), |
| PINCTRL_PIN(1, "gp01"), |
| PINCTRL_PIN(2, "gp02"), |
| PINCTRL_PIN(3, "gp03"), |
| PINCTRL_PIN(4, "gp04"), |
| PINCTRL_PIN(5, "gp05"), |
| PINCTRL_PIN(6, "gp06"), |
| PINCTRL_PIN(7, "gp07"), |
| |
| PINCTRL_PIN(8, "gp10"), |
| PINCTRL_PIN(9, "gp11"), |
| PINCTRL_PIN(10, "gp12"), |
| PINCTRL_PIN(11, "gp13"), |
| PINCTRL_PIN(12, "gp14"), |
| PINCTRL_PIN(13, "gp15"), |
| PINCTRL_PIN(14, "gp16"), |
| PINCTRL_PIN(15, "gp17"), |
| |
| PINCTRL_PIN(16, "gp20"), |
| PINCTRL_PIN(17, "gp21"), |
| PINCTRL_PIN(18, "gp22"), |
| PINCTRL_PIN(19, "gp23"), |
| |
| PINCTRL_PIN(20, "gp30"), |
| PINCTRL_PIN(21, "gp31"), |
| PINCTRL_PIN(22, "gp32"), |
| PINCTRL_PIN(23, "gp33"), |
| PINCTRL_PIN(24, "gp34"), |
| PINCTRL_PIN(25, "gp35"), |
| PINCTRL_PIN(26, "gp36"), |
| PINCTRL_PIN(27, "gp37"), |
| |
| PINCTRL_PIN(28, "gp40"), |
| PINCTRL_PIN(29, "gp41"), |
| PINCTRL_PIN(30, "gp42"), |
| PINCTRL_PIN(31, "gp43"), |
| PINCTRL_PIN(32, "gp44"), |
| PINCTRL_PIN(33, "gp45"), |
| PINCTRL_PIN(34, "gp46"), |
| PINCTRL_PIN(35, "gp47"), |
| |
| PINCTRL_PIN(36, "gp50"), |
| PINCTRL_PIN(37, "gp51"), |
| PINCTRL_PIN(38, "gp52"), |
| PINCTRL_PIN(39, "gp53"), |
| PINCTRL_PIN(40, "gp54"), |
| PINCTRL_PIN(41, "gp55"), |
| PINCTRL_PIN(42, "gp56"), |
| PINCTRL_PIN(43, "gp57"), |
| |
| PINCTRL_PIN(44, "gp60"), |
| PINCTRL_PIN(45, "gp61"), |
| PINCTRL_PIN(46, "gp62"), |
| PINCTRL_PIN(47, "gp63"), |
| PINCTRL_PIN(48, "gp64"), |
| PINCTRL_PIN(49, "gp65"), |
| PINCTRL_PIN(50, "gp66"), |
| PINCTRL_PIN(51, "gp67"), |
| |
| PINCTRL_PIN(52, "gp70"), |
| PINCTRL_PIN(53, "gp71"), |
| PINCTRL_PIN(54, "gp72"), |
| PINCTRL_PIN(55, "gp73"), |
| PINCTRL_PIN(56, "gp74"), |
| PINCTRL_PIN(57, "gp75"), |
| PINCTRL_PIN(58, "gp76"), |
| PINCTRL_PIN(59, "gp77"), |
| }; |
| |
| static const char * const cy8c95x0_groups[] = { |
| "gp00", |
| "gp01", |
| "gp02", |
| "gp03", |
| "gp04", |
| "gp05", |
| "gp06", |
| "gp07", |
| |
| "gp10", |
| "gp11", |
| "gp12", |
| "gp13", |
| "gp14", |
| "gp15", |
| "gp16", |
| "gp17", |
| |
| "gp20", |
| "gp21", |
| "gp22", |
| "gp23", |
| |
| "gp30", |
| "gp31", |
| "gp32", |
| "gp33", |
| "gp34", |
| "gp35", |
| "gp36", |
| "gp37", |
| |
| "gp40", |
| "gp41", |
| "gp42", |
| "gp43", |
| "gp44", |
| "gp45", |
| "gp46", |
| "gp47", |
| |
| "gp50", |
| "gp51", |
| "gp52", |
| "gp53", |
| "gp54", |
| "gp55", |
| "gp56", |
| "gp57", |
| |
| "gp60", |
| "gp61", |
| "gp62", |
| "gp63", |
| "gp64", |
| "gp65", |
| "gp66", |
| "gp67", |
| |
| "gp70", |
| "gp71", |
| "gp72", |
| "gp73", |
| "gp74", |
| "gp75", |
| "gp76", |
| "gp77", |
| }; |
| |
| static int cy8c95x0_pinmux_direction(struct cy8c95x0_pinctrl *chip, |
| unsigned int pin, bool input); |
| |
| static inline u8 cypress_get_port(struct cy8c95x0_pinctrl *chip, unsigned int pin) |
| { |
| /* Account for GPORT2 which only has 4 bits */ |
| return CY8C95X0_PIN_TO_OFFSET(pin) / BANK_SZ; |
| } |
| |
| static int cypress_get_pin_mask(struct cy8c95x0_pinctrl *chip, unsigned int pin) |
| { |
| /* Account for GPORT2 which only has 4 bits */ |
| return BIT(CY8C95X0_PIN_TO_OFFSET(pin) % BANK_SZ); |
| } |
| |
| static bool cy8c95x0_readable_register(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case 0x24 ... 0x27: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| static bool cy8c95x0_writeable_register(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_INPUT_(0) ... CY8C95X0_INPUT_(7): |
| return false; |
| case CY8C95X0_DEVID: |
| return false; |
| case 0x24 ... 0x27: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| static bool cy8c95x0_volatile_register(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_INPUT_(0) ... CY8C95X0_INPUT_(7): |
| case CY8C95X0_INTSTATUS_(0) ... CY8C95X0_INTSTATUS_(7): |
| case CY8C95X0_INTMASK: |
| case CY8C95X0_INVERT: |
| case CY8C95X0_PWMSEL: |
| case CY8C95X0_DIRECTION: |
| case CY8C95X0_DRV_PU: |
| case CY8C95X0_DRV_PD: |
| case CY8C95X0_DRV_ODH: |
| case CY8C95X0_DRV_ODL: |
| case CY8C95X0_DRV_PP_FAST: |
| case CY8C95X0_DRV_PP_SLOW: |
| case CY8C95X0_DRV_HIZ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool cy8c95x0_precious_register(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_INTSTATUS_(0) ... CY8C95X0_INTSTATUS_(7): |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool cy8c95x0_muxed_register(unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_INTMASK: |
| case CY8C95X0_PWMSEL: |
| case CY8C95X0_INVERT: |
| case CY8C95X0_DIRECTION: |
| case CY8C95X0_DRV_PU: |
| case CY8C95X0_DRV_PD: |
| case CY8C95X0_DRV_ODH: |
| case CY8C95X0_DRV_ODL: |
| case CY8C95X0_DRV_PP_FAST: |
| case CY8C95X0_DRV_PP_SLOW: |
| case CY8C95X0_DRV_HIZ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool cy8c95x0_wc_register(unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_DRV_PU: |
| case CY8C95X0_DRV_PD: |
| case CY8C95X0_DRV_ODH: |
| case CY8C95X0_DRV_ODL: |
| case CY8C95X0_DRV_PP_FAST: |
| case CY8C95X0_DRV_PP_SLOW: |
| case CY8C95X0_DRV_HIZ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool cy8c95x0_quick_path_register(unsigned int reg) |
| { |
| switch (reg) { |
| case CY8C95X0_INPUT_(0) ... CY8C95X0_INPUT_(7): |
| case CY8C95X0_INTSTATUS_(0) ... CY8C95X0_INTSTATUS_(7): |
| case CY8C95X0_OUTPUT_(0) ... CY8C95X0_OUTPUT_(7): |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static const struct reg_default cy8c95x0_reg_defaults[] = { |
| { CY8C95X0_OUTPUT_(0), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(1), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(2), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(3), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(4), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(5), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(6), GENMASK(7, 0) }, |
| { CY8C95X0_OUTPUT_(7), GENMASK(7, 0) }, |
| { CY8C95X0_PORTSEL, 0 }, |
| { CY8C95X0_PWMSEL, 0 }, |
| }; |
| |
| static int |
| cy8c95x0_mux_reg_read(void *context, unsigned int off, unsigned int *val) |
| { |
| struct cy8c95x0_pinctrl *chip = context; |
| u8 port = CY8C95X0_MUX_REGMAP_TO_PORT(off); |
| int ret, reg = CY8C95X0_MUX_REGMAP_TO_REG(off); |
| |
| mutex_lock(&chip->i2c_lock); |
| /* Select the correct bank */ |
| ret = regmap_write(chip->regmap, CY8C95X0_PORTSEL, port); |
| if (ret < 0) |
| goto out; |
| |
| /* |
| * Read the register through direct access regmap. The target range |
| * is marked volatile. |
| */ |
| ret = regmap_read(chip->regmap, reg, val); |
| out: |
| mutex_unlock(&chip->i2c_lock); |
| |
| return ret; |
| } |
| |
| static int |
| cy8c95x0_mux_reg_write(void *context, unsigned int off, unsigned int val) |
| { |
| struct cy8c95x0_pinctrl *chip = context; |
| u8 port = CY8C95X0_MUX_REGMAP_TO_PORT(off); |
| int ret, reg = CY8C95X0_MUX_REGMAP_TO_REG(off); |
| |
| mutex_lock(&chip->i2c_lock); |
| /* Select the correct bank */ |
| ret = regmap_write(chip->regmap, CY8C95X0_PORTSEL, port); |
| if (ret < 0) |
| goto out; |
| |
| /* |
| * Write the register through direct access regmap. The target range |
| * is marked volatile. |
| */ |
| ret = regmap_write(chip->regmap, reg, val); |
| out: |
| mutex_unlock(&chip->i2c_lock); |
| |
| return ret; |
| } |
| |
| static bool cy8c95x0_mux_accessible_register(struct device *dev, unsigned int off) |
| { |
| struct i2c_client *i2c = to_i2c_client(dev); |
| struct cy8c95x0_pinctrl *chip = i2c_get_clientdata(i2c); |
| u8 port = CY8C95X0_MUX_REGMAP_TO_PORT(off); |
| u8 reg = CY8C95X0_MUX_REGMAP_TO_REG(off); |
| |
| if (port >= chip->nport) |
| return false; |
| |
| return cy8c95x0_muxed_register(reg); |
| } |
| |
| static struct regmap_bus cy8c95x0_regmap_bus = { |
| .reg_read = cy8c95x0_mux_reg_read, |
| .reg_write = cy8c95x0_mux_reg_write, |
| }; |
| |
| /* Regmap for muxed registers CY8C95X0_INTMASK - CY8C95X0_DRV_HIZ */ |
| static const struct regmap_config cy8c95x0_muxed_regmap = { |
| .name = "muxed", |
| .reg_bits = 8, |
| .val_bits = 8, |
| .cache_type = REGCACHE_FLAT, |
| .use_single_read = true, |
| .use_single_write = true, |
| .max_register = MUXED_STRIDE * BANK_SZ, |
| .num_reg_defaults_raw = MUXED_STRIDE * BANK_SZ, |
| .readable_reg = cy8c95x0_mux_accessible_register, |
| .writeable_reg = cy8c95x0_mux_accessible_register, |
| }; |
| |
| /* Direct access regmap */ |
| static const struct regmap_config cy8c95x0_i2c_regmap = { |
| .name = "direct", |
| .reg_bits = 8, |
| .val_bits = 8, |
| |
| .reg_defaults = cy8c95x0_reg_defaults, |
| .num_reg_defaults = ARRAY_SIZE(cy8c95x0_reg_defaults), |
| |
| .readable_reg = cy8c95x0_readable_register, |
| .writeable_reg = cy8c95x0_writeable_register, |
| .volatile_reg = cy8c95x0_volatile_register, |
| .precious_reg = cy8c95x0_precious_register, |
| |
| .cache_type = REGCACHE_FLAT, |
| .max_register = CY8C95X0_COMMAND, |
| }; |
| |
| static inline int cy8c95x0_regmap_update_bits_base(struct cy8c95x0_pinctrl *chip, |
| unsigned int reg, |
| unsigned int port, |
| unsigned int mask, |
| unsigned int val, |
| bool *change, bool async, |
| bool force) |
| { |
| struct regmap *regmap; |
| int ret, off, i, read_val; |
| |
| /* Caller should never modify PORTSEL directly */ |
| if (reg == CY8C95X0_PORTSEL) |
| return -EINVAL; |
| |
| /* Registers behind the PORTSEL mux have their own regmap */ |
| if (cy8c95x0_muxed_register(reg)) { |
| regmap = chip->muxed_regmap; |
| off = CY8C95X0_MUX_REGMAP_TO_OFFSET(reg, port); |
| } else { |
| regmap = chip->regmap; |
| /* Quick path direct access registers honor the port argument */ |
| if (cy8c95x0_quick_path_register(reg)) |
| off = reg + port; |
| else |
| off = reg; |
| } |
| |
| ret = regmap_update_bits_base(regmap, off, mask, val, change, async, force); |
| if (ret < 0) |
| return ret; |
| |
| /* Update the cache when a WC bit is written */ |
| if (cy8c95x0_wc_register(reg) && (mask & val)) { |
| for (i = CY8C95X0_DRV_PU; i <= CY8C95X0_DRV_HIZ; i++) { |
| if (i == reg) |
| continue; |
| off = CY8C95X0_MUX_REGMAP_TO_OFFSET(i, port); |
| |
| ret = regmap_read(regmap, off, &read_val); |
| if (ret < 0) |
| continue; |
| |
| if (!(read_val & mask & val)) |
| continue; |
| |
| regcache_cache_only(regmap, true); |
| regmap_update_bits(regmap, off, mask & val, 0); |
| regcache_cache_only(regmap, false); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * cy8c95x0_regmap_write_bits() - writes a register using the regmap cache |
| * @chip: The pinctrl to work on |
| * @reg: The register to write to. Can be direct access or muxed register. |
| * MUST NOT be the PORTSEL register. |
| * @port: The port to be used for muxed registers or quick path direct access |
| * registers. Otherwise unused. |
| * @mask: Bitmask to change |
| * @val: New value for bitmask |
| * |
| * This function handles the register writes to the direct access registers and |
| * the muxed registers while caching all register accesses, internally handling |
| * the correct state of the PORTSEL register and protecting the access to muxed |
| * registers. |
| * The caller must only use this function to change registers behind the PORTSEL mux. |
| * |
| * Return: 0 for successful request, else a corresponding error value |
| */ |
| static int cy8c95x0_regmap_write_bits(struct cy8c95x0_pinctrl *chip, unsigned int reg, |
| unsigned int port, unsigned int mask, unsigned int val) |
| { |
| return cy8c95x0_regmap_update_bits_base(chip, reg, port, mask, val, NULL, false, true); |
| } |
| |
| /** |
| * cy8c95x0_regmap_update_bits() - updates a register using the regmap cache |
| * @chip: The pinctrl to work on |
| * @reg: The register to write to. Can be direct access or muxed register. |
| * MUST NOT be the PORTSEL register. |
| * @port: The port to be used for muxed registers or quick path direct access |
| * registers. Otherwise unused. |
| * @mask: Bitmask to change |
| * @val: New value for bitmask |
| * |
| * This function handles the register updates to the direct access registers and |
| * the muxed registers while caching all register accesses, internally handling |
| * the correct state of the PORTSEL register and protecting the access to muxed |
| * registers. |
| * The caller must only use this function to change registers behind the PORTSEL mux. |
| * |
| * Return: 0 for successful request, else a corresponding error value |
| */ |
| static int cy8c95x0_regmap_update_bits(struct cy8c95x0_pinctrl *chip, unsigned int reg, |
| unsigned int port, unsigned int mask, unsigned int val) |
| { |
| return cy8c95x0_regmap_update_bits_base(chip, reg, port, mask, val, NULL, false, false); |
| } |
| |
| /** |
| * cy8c95x0_regmap_read() - reads a register using the regmap cache |
| * @chip: The pinctrl to work on |
| * @reg: The register to read from. Can be direct access or muxed register. |
| * @port: The port to be used for muxed registers or quick path direct access |
| * registers. Otherwise unused. |
| * @read_val: Value read from hardware or cache |
| * |
| * This function handles the register reads from the direct access registers and |
| * the muxed registers while caching all register accesses, internally handling |
| * the correct state of the PORTSEL register and protecting the access to muxed |
| * registers. |
| * The caller must only use this function to read registers behind the PORTSEL mux. |
| * |
| * Return: 0 for successful request, else a corresponding error value |
| */ |
| static int cy8c95x0_regmap_read(struct cy8c95x0_pinctrl *chip, unsigned int reg, |
| unsigned int port, unsigned int *read_val) |
| { |
| struct regmap *regmap; |
| int off; |
| |
| /* Registers behind the PORTSEL mux have their own regmap */ |
| if (cy8c95x0_muxed_register(reg)) { |
| regmap = chip->muxed_regmap; |
| off = CY8C95X0_MUX_REGMAP_TO_OFFSET(reg, port); |
| } else { |
| regmap = chip->regmap; |
| /* Quick path direct access registers honor the port argument */ |
| if (cy8c95x0_quick_path_register(reg)) |
| off = reg + port; |
| else |
| off = reg; |
| } |
| |
| return regmap_read(regmap, off, read_val); |
| } |
| |
| static int cy8c95x0_write_regs_mask(struct cy8c95x0_pinctrl *chip, int reg, |
| unsigned long *val, unsigned long *mask) |
| { |
| DECLARE_BITMAP(tmask, MAX_LINE); |
| DECLARE_BITMAP(tval, MAX_LINE); |
| int write_val; |
| int ret = 0; |
| int i; |
| u8 bits; |
| |
| /* Add the 4 bit gap of Gport2 */ |
| bitmap_andnot(tmask, mask, chip->shiftmask, MAX_LINE); |
| bitmap_shift_left(tmask, tmask, 4, MAX_LINE); |
| bitmap_replace(tmask, tmask, mask, chip->shiftmask, BANK_SZ * 3); |
| |
| bitmap_andnot(tval, val, chip->shiftmask, MAX_LINE); |
| bitmap_shift_left(tval, tval, 4, MAX_LINE); |
| bitmap_replace(tval, tval, val, chip->shiftmask, BANK_SZ * 3); |
| |
| for (i = 0; i < chip->nport; i++) { |
| /* Skip over unused banks */ |
| bits = bitmap_get_value8(tmask, i * BANK_SZ); |
| if (!bits) |
| continue; |
| |
| write_val = bitmap_get_value8(tval, i * BANK_SZ); |
| |
| ret = cy8c95x0_regmap_update_bits(chip, reg, i, bits, write_val); |
| if (ret < 0) |
| goto out; |
| } |
| out: |
| |
| if (ret < 0) |
| dev_err(chip->dev, "failed writing register %d, port %d: err %d\n", reg, i, ret); |
| |
| return ret; |
| } |
| |
| static int cy8c95x0_read_regs_mask(struct cy8c95x0_pinctrl *chip, int reg, |
| unsigned long *val, unsigned long *mask) |
| { |
| DECLARE_BITMAP(tmask, MAX_LINE); |
| DECLARE_BITMAP(tval, MAX_LINE); |
| DECLARE_BITMAP(tmp, MAX_LINE); |
| int read_val; |
| int ret = 0; |
| int i; |
| u8 bits; |
| |
| /* Add the 4 bit gap of Gport2 */ |
| bitmap_andnot(tmask, mask, chip->shiftmask, MAX_LINE); |
| bitmap_shift_left(tmask, tmask, 4, MAX_LINE); |
| bitmap_replace(tmask, tmask, mask, chip->shiftmask, BANK_SZ * 3); |
| |
| bitmap_andnot(tval, val, chip->shiftmask, MAX_LINE); |
| bitmap_shift_left(tval, tval, 4, MAX_LINE); |
| bitmap_replace(tval, tval, val, chip->shiftmask, BANK_SZ * 3); |
| |
| for (i = 0; i < chip->nport; i++) { |
| /* Skip over unused banks */ |
| bits = bitmap_get_value8(tmask, i * BANK_SZ); |
| if (!bits) |
| continue; |
| |
| ret = cy8c95x0_regmap_read(chip, reg, i, &read_val); |
| if (ret < 0) |
| goto out; |
| |
| read_val &= bits; |
| read_val |= bitmap_get_value8(tval, i * BANK_SZ) & ~bits; |
| bitmap_set_value8(tval, read_val, i * BANK_SZ); |
| } |
| |
| /* Fill the 4 bit gap of Gport2 */ |
| bitmap_shift_right(tmp, tval, 4, MAX_LINE); |
| bitmap_replace(val, tmp, tval, chip->shiftmask, MAX_LINE); |
| |
| out: |
| if (ret < 0) |
| dev_err(chip->dev, "failed reading register %d, port %d: err %d\n", reg, i, ret); |
| |
| return ret; |
| } |
| |
| static int cy8c95x0_gpio_direction_input(struct gpio_chip *gc, unsigned int off) |
| { |
| return pinctrl_gpio_direction_input(gc, off); |
| } |
| |
| static int cy8c95x0_gpio_direction_output(struct gpio_chip *gc, |
| unsigned int off, int val) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| int ret; |
| |
| /* Set output level */ |
| ret = cy8c95x0_regmap_write_bits(chip, CY8C95X0_OUTPUT, port, bit, val ? bit : 0); |
| if (ret) |
| return ret; |
| |
| return pinctrl_gpio_direction_output(gc, off); |
| } |
| |
| static int cy8c95x0_gpio_get_value(struct gpio_chip *gc, unsigned int off) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| u32 reg_val; |
| int ret; |
| |
| ret = cy8c95x0_regmap_read(chip, CY8C95X0_INPUT, port, ®_val); |
| if (ret < 0) { |
| /* |
| * NOTE: |
| * Diagnostic already emitted; that's all we should |
| * do unless gpio_*_value_cansleep() calls become different |
| * from their nonsleeping siblings (and report faults). |
| */ |
| return 0; |
| } |
| |
| return !!(reg_val & bit); |
| } |
| |
| static void cy8c95x0_gpio_set_value(struct gpio_chip *gc, unsigned int off, |
| int val) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| |
| cy8c95x0_regmap_write_bits(chip, CY8C95X0_OUTPUT, port, bit, val ? bit : 0); |
| } |
| |
| static int cy8c95x0_gpio_get_direction(struct gpio_chip *gc, unsigned int off) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| u32 reg_val; |
| int ret; |
| |
| ret = cy8c95x0_regmap_read(chip, CY8C95X0_DIRECTION, port, ®_val); |
| if (ret < 0) |
| goto out; |
| |
| if (reg_val & bit) |
| return GPIO_LINE_DIRECTION_IN; |
| |
| return GPIO_LINE_DIRECTION_OUT; |
| out: |
| return ret; |
| } |
| |
| static int cy8c95x0_gpio_get_pincfg(struct cy8c95x0_pinctrl *chip, |
| unsigned int off, |
| unsigned long *config) |
| { |
| enum pin_config_param param = pinconf_to_config_param(*config); |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| unsigned int reg; |
| u32 reg_val; |
| u16 arg = 0; |
| int ret; |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_PULL_UP: |
| reg = CY8C95X0_DRV_PU; |
| break; |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| reg = CY8C95X0_DRV_PD; |
| break; |
| case PIN_CONFIG_BIAS_DISABLE: |
| reg = CY8C95X0_DRV_HIZ; |
| break; |
| case PIN_CONFIG_DRIVE_OPEN_DRAIN: |
| reg = CY8C95X0_DRV_ODL; |
| break; |
| case PIN_CONFIG_DRIVE_OPEN_SOURCE: |
| reg = CY8C95X0_DRV_ODH; |
| break; |
| case PIN_CONFIG_DRIVE_PUSH_PULL: |
| reg = CY8C95X0_DRV_PP_FAST; |
| break; |
| case PIN_CONFIG_INPUT_ENABLE: |
| reg = CY8C95X0_DIRECTION; |
| break; |
| case PIN_CONFIG_MODE_PWM: |
| reg = CY8C95X0_PWMSEL; |
| break; |
| case PIN_CONFIG_OUTPUT: |
| reg = CY8C95X0_OUTPUT; |
| break; |
| case PIN_CONFIG_OUTPUT_ENABLE: |
| reg = CY8C95X0_DIRECTION; |
| break; |
| |
| case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: |
| case PIN_CONFIG_BIAS_BUS_HOLD: |
| case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: |
| case PIN_CONFIG_DRIVE_STRENGTH: |
| case PIN_CONFIG_DRIVE_STRENGTH_UA: |
| case PIN_CONFIG_INPUT_DEBOUNCE: |
| case PIN_CONFIG_INPUT_SCHMITT: |
| case PIN_CONFIG_INPUT_SCHMITT_ENABLE: |
| case PIN_CONFIG_MODE_LOW_POWER: |
| case PIN_CONFIG_PERSIST_STATE: |
| case PIN_CONFIG_POWER_SOURCE: |
| case PIN_CONFIG_SKEW_DELAY: |
| case PIN_CONFIG_SLEEP_HARDWARE_STATE: |
| case PIN_CONFIG_SLEW_RATE: |
| default: |
| ret = -ENOTSUPP; |
| goto out; |
| } |
| /* |
| * Writing 1 to one of the drive mode registers will automatically |
| * clear conflicting set bits in the other drive mode registers. |
| */ |
| ret = cy8c95x0_regmap_read(chip, reg, port, ®_val); |
| if (ret < 0) |
| goto out; |
| |
| if (reg_val & bit) |
| arg = 1; |
| if (param == PIN_CONFIG_OUTPUT_ENABLE) |
| arg = !arg; |
| |
| *config = pinconf_to_config_packed(param, (u16)arg); |
| out: |
| return ret; |
| } |
| |
| static int cy8c95x0_gpio_set_pincfg(struct cy8c95x0_pinctrl *chip, |
| unsigned int off, |
| unsigned long config) |
| { |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| unsigned long param = pinconf_to_config_param(config); |
| unsigned long arg = pinconf_to_config_argument(config); |
| unsigned int reg; |
| int ret; |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_PULL_UP: |
| __clear_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_PU; |
| break; |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| __clear_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_PD; |
| break; |
| case PIN_CONFIG_BIAS_DISABLE: |
| __clear_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_HIZ; |
| break; |
| case PIN_CONFIG_DRIVE_OPEN_DRAIN: |
| __clear_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_ODL; |
| break; |
| case PIN_CONFIG_DRIVE_OPEN_SOURCE: |
| __clear_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_ODH; |
| break; |
| case PIN_CONFIG_DRIVE_PUSH_PULL: |
| __set_bit(off, chip->push_pull); |
| reg = CY8C95X0_DRV_PP_FAST; |
| break; |
| case PIN_CONFIG_MODE_PWM: |
| reg = CY8C95X0_PWMSEL; |
| break; |
| case PIN_CONFIG_OUTPUT_ENABLE: |
| ret = cy8c95x0_pinmux_direction(chip, off, !arg); |
| goto out; |
| case PIN_CONFIG_INPUT_ENABLE: |
| ret = cy8c95x0_pinmux_direction(chip, off, arg); |
| goto out; |
| default: |
| ret = -ENOTSUPP; |
| goto out; |
| } |
| /* |
| * Writing 1 to one of the drive mode registers will automatically |
| * clear conflicting set bits in the other drive mode registers. |
| */ |
| ret = cy8c95x0_regmap_write_bits(chip, reg, port, bit, bit); |
| out: |
| return ret; |
| } |
| |
| static int cy8c95x0_gpio_get_multiple(struct gpio_chip *gc, |
| unsigned long *mask, unsigned long *bits) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| |
| return cy8c95x0_read_regs_mask(chip, CY8C95X0_INPUT, bits, mask); |
| } |
| |
| static void cy8c95x0_gpio_set_multiple(struct gpio_chip *gc, |
| unsigned long *mask, unsigned long *bits) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| |
| cy8c95x0_write_regs_mask(chip, CY8C95X0_OUTPUT, bits, mask); |
| } |
| |
| static int cy8c95x0_add_pin_ranges(struct gpio_chip *gc) |
| { |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| struct device *dev = chip->dev; |
| int ret; |
| |
| ret = gpiochip_add_pin_range(gc, dev_name(dev), 0, 0, chip->tpin); |
| if (ret) |
| dev_err(dev, "failed to add GPIO pin range\n"); |
| |
| return ret; |
| } |
| |
| static int cy8c95x0_setup_gpiochip(struct cy8c95x0_pinctrl *chip) |
| { |
| struct gpio_chip *gc = &chip->gpio_chip; |
| |
| gc->request = gpiochip_generic_request; |
| gc->free = gpiochip_generic_free; |
| gc->direction_input = cy8c95x0_gpio_direction_input; |
| gc->direction_output = cy8c95x0_gpio_direction_output; |
| gc->get = cy8c95x0_gpio_get_value; |
| gc->set = cy8c95x0_gpio_set_value; |
| gc->get_direction = cy8c95x0_gpio_get_direction; |
| gc->get_multiple = cy8c95x0_gpio_get_multiple; |
| gc->set_multiple = cy8c95x0_gpio_set_multiple; |
| gc->set_config = gpiochip_generic_config; |
| gc->can_sleep = true; |
| gc->add_pin_ranges = cy8c95x0_add_pin_ranges; |
| |
| gc->base = -1; |
| gc->ngpio = chip->tpin; |
| |
| gc->parent = chip->dev; |
| gc->owner = THIS_MODULE; |
| gc->names = NULL; |
| |
| gc->label = dev_name(chip->dev); |
| |
| return devm_gpiochip_add_data(chip->dev, gc, chip); |
| } |
| |
| static void cy8c95x0_irq_mask(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| irq_hw_number_t hwirq = irqd_to_hwirq(d); |
| |
| set_bit(hwirq, chip->irq_mask); |
| gpiochip_disable_irq(gc, hwirq); |
| } |
| |
| static void cy8c95x0_irq_unmask(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| irq_hw_number_t hwirq = irqd_to_hwirq(d); |
| |
| gpiochip_enable_irq(gc, hwirq); |
| clear_bit(hwirq, chip->irq_mask); |
| } |
| |
| static void cy8c95x0_irq_bus_lock(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| |
| mutex_lock(&chip->irq_lock); |
| } |
| |
| static void cy8c95x0_irq_bus_sync_unlock(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| DECLARE_BITMAP(ones, MAX_LINE); |
| DECLARE_BITMAP(irq_mask, MAX_LINE); |
| DECLARE_BITMAP(reg_direction, MAX_LINE); |
| |
| bitmap_fill(ones, MAX_LINE); |
| |
| cy8c95x0_write_regs_mask(chip, CY8C95X0_INTMASK, chip->irq_mask, ones); |
| |
| /* Switch direction to input if needed */ |
| cy8c95x0_read_regs_mask(chip, CY8C95X0_DIRECTION, reg_direction, chip->irq_mask); |
| bitmap_or(irq_mask, chip->irq_mask, reg_direction, MAX_LINE); |
| bitmap_complement(irq_mask, irq_mask, MAX_LINE); |
| |
| /* Look for any newly setup interrupt */ |
| cy8c95x0_write_regs_mask(chip, CY8C95X0_DIRECTION, ones, irq_mask); |
| |
| mutex_unlock(&chip->irq_lock); |
| } |
| |
| static int cy8c95x0_irq_set_type(struct irq_data *d, unsigned int type) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| irq_hw_number_t hwirq = irqd_to_hwirq(d); |
| unsigned int trig_type; |
| |
| switch (type) { |
| case IRQ_TYPE_EDGE_RISING: |
| case IRQ_TYPE_EDGE_FALLING: |
| case IRQ_TYPE_EDGE_BOTH: |
| trig_type = type; |
| break; |
| case IRQ_TYPE_LEVEL_HIGH: |
| trig_type = IRQ_TYPE_EDGE_RISING; |
| break; |
| case IRQ_TYPE_LEVEL_LOW: |
| trig_type = IRQ_TYPE_EDGE_FALLING; |
| break; |
| default: |
| dev_err(chip->dev, "irq %d: unsupported type %d\n", d->irq, type); |
| return -EINVAL; |
| } |
| |
| assign_bit(hwirq, chip->irq_trig_fall, trig_type & IRQ_TYPE_EDGE_FALLING); |
| assign_bit(hwirq, chip->irq_trig_raise, trig_type & IRQ_TYPE_EDGE_RISING); |
| assign_bit(hwirq, chip->irq_trig_low, type == IRQ_TYPE_LEVEL_LOW); |
| assign_bit(hwirq, chip->irq_trig_high, type == IRQ_TYPE_LEVEL_HIGH); |
| |
| return 0; |
| } |
| |
| static void cy8c95x0_irq_shutdown(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct cy8c95x0_pinctrl *chip = gpiochip_get_data(gc); |
| irq_hw_number_t hwirq = irqd_to_hwirq(d); |
| |
| clear_bit(hwirq, chip->irq_trig_raise); |
| clear_bit(hwirq, chip->irq_trig_fall); |
| clear_bit(hwirq, chip->irq_trig_low); |
| clear_bit(hwirq, chip->irq_trig_high); |
| } |
| |
| static const struct irq_chip cy8c95x0_irqchip = { |
| .name = "cy8c95x0-irq", |
| .irq_mask = cy8c95x0_irq_mask, |
| .irq_unmask = cy8c95x0_irq_unmask, |
| .irq_bus_lock = cy8c95x0_irq_bus_lock, |
| .irq_bus_sync_unlock = cy8c95x0_irq_bus_sync_unlock, |
| .irq_set_type = cy8c95x0_irq_set_type, |
| .irq_shutdown = cy8c95x0_irq_shutdown, |
| .flags = IRQCHIP_IMMUTABLE, |
| GPIOCHIP_IRQ_RESOURCE_HELPERS, |
| }; |
| |
| static bool cy8c95x0_irq_pending(struct cy8c95x0_pinctrl *chip, unsigned long *pending) |
| { |
| DECLARE_BITMAP(ones, MAX_LINE); |
| DECLARE_BITMAP(cur_stat, MAX_LINE); |
| DECLARE_BITMAP(new_stat, MAX_LINE); |
| DECLARE_BITMAP(trigger, MAX_LINE); |
| |
| bitmap_fill(ones, MAX_LINE); |
| |
| /* Read the current interrupt status from the device */ |
| if (cy8c95x0_read_regs_mask(chip, CY8C95X0_INTSTATUS, trigger, ones)) |
| return false; |
| |
| /* Check latched inputs */ |
| if (cy8c95x0_read_regs_mask(chip, CY8C95X0_INPUT, cur_stat, trigger)) |
| return false; |
| |
| /* Apply filter for rising/falling edge selection */ |
| bitmap_replace(new_stat, chip->irq_trig_fall, chip->irq_trig_raise, |
| cur_stat, MAX_LINE); |
| |
| bitmap_and(pending, new_stat, trigger, MAX_LINE); |
| |
| return !bitmap_empty(pending, MAX_LINE); |
| } |
| |
| static irqreturn_t cy8c95x0_irq_handler(int irq, void *devid) |
| { |
| struct cy8c95x0_pinctrl *chip = devid; |
| struct gpio_chip *gc = &chip->gpio_chip; |
| DECLARE_BITMAP(pending, MAX_LINE); |
| int nested_irq, level; |
| bool ret; |
| |
| ret = cy8c95x0_irq_pending(chip, pending); |
| if (!ret) |
| return IRQ_RETVAL(0); |
| |
| ret = 0; |
| for_each_set_bit(level, pending, MAX_LINE) { |
| /* Already accounted for 4bit gap in GPort2 */ |
| nested_irq = irq_find_mapping(gc->irq.domain, level); |
| |
| if (unlikely(nested_irq <= 0)) { |
| dev_warn_ratelimited(gc->parent, "unmapped interrupt %d\n", level); |
| continue; |
| } |
| |
| if (test_bit(level, chip->irq_trig_low)) |
| while (!cy8c95x0_gpio_get_value(gc, level)) |
| handle_nested_irq(nested_irq); |
| else if (test_bit(level, chip->irq_trig_high)) |
| while (cy8c95x0_gpio_get_value(gc, level)) |
| handle_nested_irq(nested_irq); |
| else |
| handle_nested_irq(nested_irq); |
| |
| ret = 1; |
| } |
| |
| return IRQ_RETVAL(ret); |
| } |
| |
| static int cy8c95x0_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| return chip->tpin; |
| } |
| |
| static const char *cy8c95x0_pinctrl_get_group_name(struct pinctrl_dev *pctldev, |
| unsigned int group) |
| { |
| return cy8c95x0_groups[group]; |
| } |
| |
| static int cy8c95x0_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, |
| unsigned int group, |
| const unsigned int **pins, |
| unsigned int *num_pins) |
| { |
| *pins = &cy8c9560_pins[group].number; |
| *num_pins = 1; |
| return 0; |
| } |
| |
| static const char *cy8c95x0_get_fname(unsigned int selector) |
| { |
| if (selector == 0) |
| return "gpio"; |
| else |
| return "pwm"; |
| } |
| |
| static void cy8c95x0_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, |
| unsigned int pin) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| DECLARE_BITMAP(mask, MAX_LINE); |
| DECLARE_BITMAP(pwm, MAX_LINE); |
| |
| bitmap_zero(mask, MAX_LINE); |
| __set_bit(pin, mask); |
| |
| if (cy8c95x0_read_regs_mask(chip, CY8C95X0_PWMSEL, pwm, mask)) { |
| seq_puts(s, "not available"); |
| return; |
| } |
| |
| seq_printf(s, "MODE:%s", cy8c95x0_get_fname(test_bit(pin, pwm))); |
| } |
| |
| static const struct pinctrl_ops cy8c95x0_pinctrl_ops = { |
| .get_groups_count = cy8c95x0_pinctrl_get_groups_count, |
| .get_group_name = cy8c95x0_pinctrl_get_group_name, |
| .get_group_pins = cy8c95x0_pinctrl_get_group_pins, |
| #ifdef CONFIG_OF |
| .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, |
| .dt_free_map = pinconf_generic_dt_free_map, |
| #endif |
| .pin_dbg_show = cy8c95x0_pin_dbg_show, |
| }; |
| |
| static const char *cy8c95x0_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector) |
| { |
| return cy8c95x0_get_fname(selector); |
| } |
| |
| static int cy8c95x0_get_functions_count(struct pinctrl_dev *pctldev) |
| { |
| return 2; |
| } |
| |
| static int cy8c95x0_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector, |
| const char * const **groups, |
| unsigned int * const num_groups) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| *groups = cy8c95x0_groups; |
| *num_groups = chip->tpin; |
| return 0; |
| } |
| |
| static int cy8c95x0_set_mode(struct cy8c95x0_pinctrl *chip, unsigned int off, bool mode) |
| { |
| u8 port = cypress_get_port(chip, off); |
| u8 bit = cypress_get_pin_mask(chip, off); |
| |
| return cy8c95x0_regmap_write_bits(chip, CY8C95X0_PWMSEL, port, bit, mode ? bit : 0); |
| } |
| |
| static int cy8c95x0_pinmux_mode(struct cy8c95x0_pinctrl *chip, |
| unsigned int selector, unsigned int group) |
| { |
| u8 port = cypress_get_port(chip, group); |
| u8 bit = cypress_get_pin_mask(chip, group); |
| int ret; |
| |
| ret = cy8c95x0_set_mode(chip, group, selector); |
| if (ret < 0) |
| return ret; |
| |
| if (selector == 0) |
| return 0; |
| |
| /* Set direction to output & set output to 1 so that PWM can work */ |
| ret = cy8c95x0_regmap_write_bits(chip, CY8C95X0_DIRECTION, port, bit, bit); |
| if (ret < 0) |
| return ret; |
| |
| return cy8c95x0_regmap_write_bits(chip, CY8C95X0_OUTPUT, port, bit, bit); |
| } |
| |
| static int cy8c95x0_set_mux(struct pinctrl_dev *pctldev, unsigned int selector, |
| unsigned int group) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| return cy8c95x0_pinmux_mode(chip, selector, group); |
| } |
| |
| static int cy8c95x0_gpio_request_enable(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, |
| unsigned int pin) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| return cy8c95x0_set_mode(chip, pin, false); |
| } |
| |
| static int cy8c95x0_pinmux_direction(struct cy8c95x0_pinctrl *chip, |
| unsigned int pin, bool input) |
| { |
| u8 port = cypress_get_port(chip, pin); |
| u8 bit = cypress_get_pin_mask(chip, pin); |
| int ret; |
| |
| ret = cy8c95x0_regmap_write_bits(chip, CY8C95X0_DIRECTION, port, bit, input ? bit : 0); |
| if (ret) |
| return ret; |
| |
| /* |
| * Disable driving the pin by forcing it to HighZ. Only setting |
| * the direction register isn't sufficient in Push-Pull mode. |
| */ |
| if (input && test_bit(pin, chip->push_pull)) { |
| ret = cy8c95x0_regmap_write_bits(chip, CY8C95X0_DRV_HIZ, port, bit, bit); |
| if (ret) |
| return ret; |
| |
| __clear_bit(pin, chip->push_pull); |
| } |
| |
| return 0; |
| } |
| |
| static int cy8c95x0_gpio_set_direction(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, |
| unsigned int pin, bool input) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| return cy8c95x0_pinmux_direction(chip, pin, input); |
| } |
| |
| static const struct pinmux_ops cy8c95x0_pmxops = { |
| .get_functions_count = cy8c95x0_get_functions_count, |
| .get_function_name = cy8c95x0_get_function_name, |
| .get_function_groups = cy8c95x0_get_function_groups, |
| .set_mux = cy8c95x0_set_mux, |
| .gpio_request_enable = cy8c95x0_gpio_request_enable, |
| .gpio_set_direction = cy8c95x0_gpio_set_direction, |
| .strict = true, |
| }; |
| |
| static int cy8c95x0_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, |
| unsigned long *config) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| |
| return cy8c95x0_gpio_get_pincfg(chip, pin, config); |
| } |
| |
| static int cy8c95x0_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, |
| unsigned long *configs, unsigned int num_configs) |
| { |
| struct cy8c95x0_pinctrl *chip = pinctrl_dev_get_drvdata(pctldev); |
| int ret = 0; |
| int i; |
| |
| for (i = 0; i < num_configs; i++) { |
| ret = cy8c95x0_gpio_set_pincfg(chip, pin, configs[i]); |
| if (ret) |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static const struct pinconf_ops cy8c95x0_pinconf_ops = { |
| .pin_config_get = cy8c95x0_pinconf_get, |
| .pin_config_set = cy8c95x0_pinconf_set, |
| .is_generic = true, |
| }; |
| |
| static int cy8c95x0_irq_setup(struct cy8c95x0_pinctrl *chip, int irq) |
| { |
| struct gpio_irq_chip *girq = &chip->gpio_chip.irq; |
| DECLARE_BITMAP(pending_irqs, MAX_LINE); |
| int ret; |
| |
| mutex_init(&chip->irq_lock); |
| |
| bitmap_zero(pending_irqs, MAX_LINE); |
| |
| /* Read IRQ status register to clear all pending interrupts */ |
| ret = cy8c95x0_irq_pending(chip, pending_irqs); |
| if (ret) { |
| dev_err(chip->dev, "failed to clear irq status register\n"); |
| return ret; |
| } |
| |
| /* Mask all interrupts */ |
| bitmap_fill(chip->irq_mask, MAX_LINE); |
| |
| gpio_irq_chip_set_chip(girq, &cy8c95x0_irqchip); |
| |
| /* This will let us handle the parent IRQ in the driver */ |
| girq->parent_handler = NULL; |
| girq->num_parents = 0; |
| girq->parents = NULL; |
| girq->default_type = IRQ_TYPE_NONE; |
| girq->handler = handle_simple_irq; |
| girq->threaded = true; |
| |
| ret = devm_request_threaded_irq(chip->dev, irq, |
| NULL, cy8c95x0_irq_handler, |
| IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_HIGH, |
| dev_name(chip->dev), chip); |
| if (ret) { |
| dev_err(chip->dev, "failed to request irq %d\n", irq); |
| return ret; |
| } |
| dev_info(chip->dev, "Registered threaded IRQ\n"); |
| |
| return 0; |
| } |
| |
| static int cy8c95x0_setup_pinctrl(struct cy8c95x0_pinctrl *chip) |
| { |
| struct pinctrl_desc *pd = &chip->pinctrl_desc; |
| |
| pd->pctlops = &cy8c95x0_pinctrl_ops; |
| pd->confops = &cy8c95x0_pinconf_ops; |
| pd->pmxops = &cy8c95x0_pmxops; |
| pd->name = dev_name(chip->dev); |
| pd->pins = cy8c9560_pins; |
| pd->npins = chip->tpin; |
| pd->owner = THIS_MODULE; |
| |
| chip->pctldev = devm_pinctrl_register(chip->dev, pd, chip); |
| if (IS_ERR(chip->pctldev)) |
| return dev_err_probe(chip->dev, PTR_ERR(chip->pctldev), |
| "can't register controller\n"); |
| |
| return 0; |
| } |
| |
| static int cy8c95x0_detect(struct i2c_client *client, |
| struct i2c_board_info *info) |
| { |
| struct i2c_adapter *adapter = client->adapter; |
| int ret; |
| const char *name; |
| |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) |
| return -ENODEV; |
| |
| ret = i2c_smbus_read_byte_data(client, CY8C95X0_DEVID); |
| if (ret < 0) |
| return ret; |
| switch (ret & GENMASK(7, 4)) { |
| case 0x20: |
| name = cy8c95x0_id[0].name; |
| break; |
| case 0x40: |
| name = cy8c95x0_id[1].name; |
| break; |
| case 0x60: |
| name = cy8c95x0_id[2].name; |
| break; |
| default: |
| return -ENODEV; |
| } |
| |
| dev_info(&client->dev, "Found a %s chip at 0x%02x.\n", name, client->addr); |
| strscpy(info->type, name, I2C_NAME_SIZE); |
| |
| return 0; |
| } |
| |
| static int cy8c95x0_probe(struct i2c_client *client) |
| { |
| struct cy8c95x0_pinctrl *chip; |
| struct regulator *reg; |
| int ret; |
| |
| chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
| if (!chip) |
| return -ENOMEM; |
| |
| chip->dev = &client->dev; |
| |
| /* Set the device type */ |
| chip->driver_data = (uintptr_t)i2c_get_match_data(client); |
| if (!chip->driver_data) |
| return -ENODEV; |
| |
| i2c_set_clientdata(client, chip); |
| |
| chip->tpin = chip->driver_data & CY8C95X0_GPIO_MASK; |
| chip->nport = DIV_ROUND_UP(CY8C95X0_PIN_TO_OFFSET(chip->tpin), BANK_SZ); |
| |
| switch (chip->tpin) { |
| case 20: |
| strscpy(chip->name, cy8c95x0_id[0].name, I2C_NAME_SIZE); |
| break; |
| case 40: |
| strscpy(chip->name, cy8c95x0_id[1].name, I2C_NAME_SIZE); |
| break; |
| case 60: |
| strscpy(chip->name, cy8c95x0_id[2].name, I2C_NAME_SIZE); |
| break; |
| default: |
| return -ENODEV; |
| } |
| |
| reg = devm_regulator_get(&client->dev, "vdd"); |
| if (IS_ERR(reg)) { |
| if (PTR_ERR(reg) == -EPROBE_DEFER) |
| return -EPROBE_DEFER; |
| } else { |
| ret = regulator_enable(reg); |
| if (ret) { |
| dev_err(&client->dev, "failed to enable regulator vdd: %d\n", ret); |
| return ret; |
| } |
| chip->regulator = reg; |
| } |
| |
| /* bring the chip out of reset if reset pin is provided */ |
| chip->gpio_reset = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); |
| if (IS_ERR(chip->gpio_reset)) { |
| ret = dev_err_probe(chip->dev, PTR_ERR(chip->gpio_reset), |
| "Failed to get GPIO 'reset'\n"); |
| goto err_exit; |
| } else if (chip->gpio_reset) { |
| usleep_range(1000, 2000); |
| gpiod_set_value_cansleep(chip->gpio_reset, 0); |
| usleep_range(250000, 300000); |
| |
| gpiod_set_consumer_name(chip->gpio_reset, "CY8C95X0 RESET"); |
| } |
| |
| /* Generic regmap for direct access registers */ |
| chip->regmap = devm_regmap_init_i2c(client, &cy8c95x0_i2c_regmap); |
| if (IS_ERR(chip->regmap)) { |
| ret = PTR_ERR(chip->regmap); |
| goto err_exit; |
| } |
| |
| /* Port specific regmap behind PORTSEL mux */ |
| chip->muxed_regmap = devm_regmap_init(&client->dev, &cy8c95x0_regmap_bus, |
| chip, &cy8c95x0_muxed_regmap); |
| if (IS_ERR(chip->muxed_regmap)) { |
| ret = dev_err_probe(&client->dev, PTR_ERR(chip->muxed_regmap), |
| "Failed to register muxed regmap\n"); |
| goto err_exit; |
| } |
| |
| bitmap_zero(chip->push_pull, MAX_LINE); |
| bitmap_zero(chip->shiftmask, MAX_LINE); |
| bitmap_set(chip->shiftmask, 0, 20); |
| mutex_init(&chip->i2c_lock); |
| |
| if (dmi_first_match(cy8c95x0_dmi_acpi_irq_info)) { |
| ret = cy8c95x0_acpi_get_irq(&client->dev); |
| if (ret > 0) |
| client->irq = ret; |
| } |
| |
| if (client->irq) { |
| ret = cy8c95x0_irq_setup(chip, client->irq); |
| if (ret) |
| goto err_exit; |
| } |
| |
| ret = cy8c95x0_setup_pinctrl(chip); |
| if (ret) |
| goto err_exit; |
| |
| ret = cy8c95x0_setup_gpiochip(chip); |
| if (ret) |
| goto err_exit; |
| |
| return 0; |
| |
| err_exit: |
| if (!IS_ERR_OR_NULL(chip->regulator)) |
| regulator_disable(chip->regulator); |
| return ret; |
| } |
| |
| static void cy8c95x0_remove(struct i2c_client *client) |
| { |
| struct cy8c95x0_pinctrl *chip = i2c_get_clientdata(client); |
| |
| if (!IS_ERR_OR_NULL(chip->regulator)) |
| regulator_disable(chip->regulator); |
| } |
| |
| static const struct acpi_device_id cy8c95x0_acpi_ids[] = { |
| { "INT3490", 40, }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(acpi, cy8c95x0_acpi_ids); |
| |
| static struct i2c_driver cy8c95x0_driver = { |
| .driver = { |
| .name = "cy8c95x0-pinctrl", |
| .of_match_table = cy8c95x0_dt_ids, |
| .acpi_match_table = cy8c95x0_acpi_ids, |
| }, |
| .probe = cy8c95x0_probe, |
| .remove = cy8c95x0_remove, |
| .id_table = cy8c95x0_id, |
| .detect = cy8c95x0_detect, |
| }; |
| module_i2c_driver(cy8c95x0_driver); |
| |
| MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>"); |
| MODULE_AUTHOR("Naresh Solanki <naresh.solanki@9elements.com>"); |
| MODULE_DESCRIPTION("Pinctrl driver for CY8C95X0"); |
| MODULE_LICENSE("GPL"); |