Commit | Line | Data |
---|---|---|
fb38af4a WBG |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Intel 8255 Programmable Peripheral Interface | |
4 | * Copyright (C) 2022 William Breathitt Gray | |
5 | */ | |
6ecb741e | 6 | #include <linux/bits.h> |
0b7c490d | 7 | #include <linux/device.h> |
fb38af4a WBG |
8 | #include <linux/err.h> |
9 | #include <linux/export.h> | |
0b7c490d | 10 | #include <linux/gpio/regmap.h> |
fb38af4a | 11 | #include <linux/module.h> |
0b7c490d | 12 | #include <linux/regmap.h> |
fb38af4a WBG |
13 | |
14 | #include "gpio-i8255.h" | |
15 | ||
0b7c490d WBG |
16 | #define I8255_NGPIO 24 |
17 | #define I8255_NGPIO_PER_REG 8 | |
fb38af4a WBG |
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) | |
0b7c490d WBG |
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 | |
fb38af4a | 29 | |
0b7c490d | 30 | static int i8255_direction_mask(const unsigned int offset) |
fb38af4a | 31 | { |
0b7c490d WBG |
32 | const unsigned int stride = offset / I8255_NGPIO_PER_REG; |
33 | const unsigned int line = offset % I8255_NGPIO_PER_REG; | |
fb38af4a | 34 | |
0b7c490d | 35 | switch (stride) { |
fb38af4a WBG |
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 */ | |
0b7c490d | 42 | if (line >= 4) |
fb38af4a WBG |
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 | ||
0b7c490d WBG |
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 | ||
0b7c490d WBG |
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 | ||
fb38af4a WBG |
139 | MODULE_AUTHOR("William Breathitt Gray"); |
140 | MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface"); | |
141 | MODULE_LICENSE("GPL"); |