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, reg: 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, reg: base + I8255_PORTA, val: 0x00); |
62 | if (err) |
63 | return err; |
64 | err = regmap_write(map, reg: base + I8255_PORTB, val: 0x00); |
65 | if (err) |
66 | return err; |
67 | return regmap_write(map, reg: base + I8255_PORTC, val: 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(offset: 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(map: config->map, base: 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(ptr: devm_gpio_regmap_register(dev, config: &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" ); |
142 | |