| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Intel 8255 Programmable Peripheral Interface |
| 4 | * Copyright (C) 2022 William Breathitt Gray |
| 5 | */ |
| 6 | #include <linux/bits.h> |
| 7 | #include <linux/device.h> |
| 8 | #include <linux/err.h> |
| 9 | #include <linux/export.h> |
| 10 | #include <linux/gpio/regmap.h> |
| 11 | #include <linux/module.h> |
| 12 | #include <linux/regmap.h> |
| 13 | |
| 14 | #include "gpio-i8255.h" |
| 15 | |
| 16 | #define I8255_NGPIO 24 |
| 17 | #define I8255_NGPIO_PER_REG 8 |
| 18 | #define I8255_CONTROL_PORTC_LOWER_DIRECTION BIT(0) |
| 19 | #define I8255_CONTROL_PORTB_DIRECTION BIT(1) |
| 20 | #define I8255_CONTROL_PORTC_UPPER_DIRECTION BIT(3) |
| 21 | #define I8255_CONTROL_PORTA_DIRECTION BIT(4) |
| 22 | #define I8255_CONTROL_MODE_SET BIT(7) |
| 23 | #define I8255_PORTA 0x0 |
| 24 | #define I8255_PORTB 0x1 |
| 25 | #define I8255_PORTC 0x2 |
| 26 | #define I8255_CONTROL 0x3 |
| 27 | #define I8255_REG_DAT_BASE I8255_PORTA |
| 28 | #define I8255_REG_DIR_IN_BASE I8255_CONTROL |
| 29 | |
| 30 | static int i8255_direction_mask(const unsigned int offset) |
| 31 | { |
| 32 | const unsigned int stride = offset / I8255_NGPIO_PER_REG; |
| 33 | const unsigned int line = offset % I8255_NGPIO_PER_REG; |
| 34 | |
| 35 | switch (stride) { |
| 36 | case I8255_PORTA: |
| 37 | return I8255_CONTROL_PORTA_DIRECTION; |
| 38 | case I8255_PORTB: |
| 39 | return I8255_CONTROL_PORTB_DIRECTION; |
| 40 | case I8255_PORTC: |
| 41 | /* Port C can be configured by nibble */ |
| 42 | if (line >= 4) |
| 43 | return I8255_CONTROL_PORTC_UPPER_DIRECTION; |
| 44 | return I8255_CONTROL_PORTC_LOWER_DIRECTION; |
| 45 | default: |
| 46 | /* Should never reach this path */ |
| 47 | return 0; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | static int i8255_ppi_init(struct regmap *const map, const unsigned int base) |
| 52 | { |
| 53 | int err; |
| 54 | |
| 55 | /* Configure all ports to MODE 0 output mode */ |
| 56 | err = regmap_write(map, base + I8255_CONTROL, I8255_CONTROL_MODE_SET); |
| 57 | if (err) |
| 58 | return err; |
| 59 | |
| 60 | /* Initialize all GPIO to output 0 */ |
| 61 | err = regmap_write(map, base + I8255_PORTA, 0x00); |
| 62 | if (err) |
| 63 | return err; |
| 64 | err = regmap_write(map, base + I8255_PORTB, 0x00); |
| 65 | if (err) |
| 66 | return err; |
| 67 | return regmap_write(map, base + I8255_PORTC, 0x00); |
| 68 | } |
| 69 | |
| 70 | static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base, |
| 71 | unsigned int offset, unsigned int *reg, |
| 72 | unsigned int *mask) |
| 73 | { |
| 74 | const unsigned int ppi = offset / I8255_NGPIO; |
| 75 | const unsigned int ppi_offset = offset % I8255_NGPIO; |
| 76 | const unsigned int stride = ppi_offset / I8255_NGPIO_PER_REG; |
| 77 | const unsigned int line = ppi_offset % I8255_NGPIO_PER_REG; |
| 78 | |
| 79 | switch (base) { |
| 80 | case I8255_REG_DAT_BASE: |
| 81 | *reg = base + stride + ppi * 4; |
| 82 | *mask = BIT(line); |
| 83 | return 0; |
| 84 | case I8255_REG_DIR_IN_BASE: |
| 85 | *reg = base + ppi * 4; |
| 86 | *mask = i8255_direction_mask(ppi_offset); |
| 87 | return 0; |
| 88 | default: |
| 89 | /* Should never reach this path */ |
| 90 | return -EINVAL; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * devm_i8255_regmap_register - Register an i8255 GPIO controller |
| 96 | * @dev: device that is registering this i8255 GPIO device |
| 97 | * @config: configuration for i8255_regmap_config |
| 98 | * |
| 99 | * Registers an Intel 8255 Programmable Peripheral Interface GPIO controller. |
| 100 | * Returns 0 on success and negative error number on failure. |
| 101 | */ |
| 102 | int devm_i8255_regmap_register(struct device *const dev, |
| 103 | const struct i8255_regmap_config *const config) |
| 104 | { |
| 105 | struct gpio_regmap_config gpio_config = {0}; |
| 106 | unsigned long i; |
| 107 | int err; |
| 108 | |
| 109 | if (!config->parent) |
| 110 | return -EINVAL; |
| 111 | |
| 112 | if (!config->map) |
| 113 | return -EINVAL; |
| 114 | |
| 115 | if (!config->num_ppi) |
| 116 | return -EINVAL; |
| 117 | |
| 118 | for (i = 0; i < config->num_ppi; i++) { |
| 119 | err = i8255_ppi_init(config->map, i * 4); |
| 120 | if (err) |
| 121 | return err; |
| 122 | } |
| 123 | |
| 124 | gpio_config.parent = config->parent; |
| 125 | gpio_config.regmap = config->map; |
| 126 | gpio_config.ngpio = I8255_NGPIO * config->num_ppi; |
| 127 | gpio_config.names = config->names; |
| 128 | gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE); |
| 129 | gpio_config.reg_set_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE); |
| 130 | gpio_config.reg_dir_in_base = GPIO_REGMAP_ADDR(I8255_REG_DIR_IN_BASE); |
| 131 | gpio_config.ngpio_per_reg = I8255_NGPIO_PER_REG; |
| 132 | gpio_config.irq_domain = config->domain; |
| 133 | gpio_config.reg_mask_xlate = i8255_reg_mask_xlate; |
| 134 | |
| 135 | return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config)); |
| 136 | } |
| 137 | EXPORT_SYMBOL_NS_GPL(devm_i8255_regmap_register, I8255); |
| 138 | |
| 139 | MODULE_AUTHOR("William Breathitt Gray"); |
| 140 | MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface"); |
| 141 | MODULE_LICENSE("GPL"); |