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/bitmap.h> 7 #include <linux/err.h> 8 #include <linux/export.h> 9 #include <linux/io.h> 10 #include <linux/module.h> 11 #include <linux/spinlock.h> 12 #include <linux/types.h> 13 14 #include "gpio-i8255.h" 15 16 #define I8255_CONTROL_PORTC_LOWER_DIRECTION BIT(0) 17 #define I8255_CONTROL_PORTB_DIRECTION BIT(1) 18 #define I8255_CONTROL_PORTC_UPPER_DIRECTION BIT(3) 19 #define I8255_CONTROL_PORTA_DIRECTION BIT(4) 20 #define I8255_CONTROL_MODE_SET BIT(7) 21 #define I8255_PORTA 0 22 #define I8255_PORTB 1 23 #define I8255_PORTC 2 24 25 static int i8255_get_port(struct i8255 __iomem *const ppi, 26 const unsigned long io_port, const unsigned long mask) 27 { 28 const unsigned long bank = io_port / 3; 29 const unsigned long ppi_port = io_port % 3; 30 31 return ioread8(&ppi[bank].port[ppi_port]) & mask; 32 } 33 34 static u8 i8255_direction_mask(const unsigned long offset) 35 { 36 const unsigned long port_offset = offset % 8; 37 const unsigned long io_port = offset / 8; 38 const unsigned long ppi_port = io_port % 3; 39 40 switch (ppi_port) { 41 case I8255_PORTA: 42 return I8255_CONTROL_PORTA_DIRECTION; 43 case I8255_PORTB: 44 return I8255_CONTROL_PORTB_DIRECTION; 45 case I8255_PORTC: 46 /* Port C can be configured by nibble */ 47 if (port_offset >= 4) 48 return I8255_CONTROL_PORTC_UPPER_DIRECTION; 49 return I8255_CONTROL_PORTC_LOWER_DIRECTION; 50 default: 51 /* Should never reach this path */ 52 return 0; 53 } 54 } 55 56 static void i8255_set_port(struct i8255 __iomem *const ppi, 57 struct i8255_state *const state, 58 const unsigned long io_port, 59 const unsigned long mask, const unsigned long bits) 60 { 61 const unsigned long bank = io_port / 3; 62 const unsigned long ppi_port = io_port % 3; 63 unsigned long flags; 64 unsigned long out_state; 65 66 spin_lock_irqsave(&state[bank].lock, flags); 67 68 out_state = ioread8(&ppi[bank].port[ppi_port]); 69 out_state = (out_state & ~mask) | (bits & mask); 70 iowrite8(out_state, &ppi[bank].port[ppi_port]); 71 72 spin_unlock_irqrestore(&state[bank].lock, flags); 73 } 74 75 /** 76 * i8255_direction_input - configure signal offset as input 77 * @ppi: Intel 8255 Programmable Peripheral Interface banks 78 * @state: devices states of the respective PPI banks 79 * @offset: signal offset to configure as input 80 * 81 * Configures a signal @offset as input for the respective Intel 8255 82 * Programmable Peripheral Interface (@ppi) banks. The @state control_state 83 * values are updated to reflect the new configuration. 84 */ 85 void i8255_direction_input(struct i8255 __iomem *const ppi, 86 struct i8255_state *const state, 87 const unsigned long offset) 88 { 89 const unsigned long io_port = offset / 8; 90 const unsigned long bank = io_port / 3; 91 unsigned long flags; 92 93 spin_lock_irqsave(&state[bank].lock, flags); 94 95 state[bank].control_state |= I8255_CONTROL_MODE_SET; 96 state[bank].control_state |= i8255_direction_mask(offset); 97 98 iowrite8(state[bank].control_state, &ppi[bank].control); 99 100 spin_unlock_irqrestore(&state[bank].lock, flags); 101 } 102 EXPORT_SYMBOL_NS_GPL(i8255_direction_input, I8255); 103 104 /** 105 * i8255_direction_output - configure signal offset as output 106 * @ppi: Intel 8255 Programmable Peripheral Interface banks 107 * @state: devices states of the respective PPI banks 108 * @offset: signal offset to configure as output 109 * @value: signal value to output 110 * 111 * Configures a signal @offset as output for the respective Intel 8255 112 * Programmable Peripheral Interface (@ppi) banks and sets the respective signal 113 * output to the desired @value. The @state control_state values are updated to 114 * reflect the new configuration. 115 */ 116 void i8255_direction_output(struct i8255 __iomem *const ppi, 117 struct i8255_state *const state, 118 const unsigned long offset, 119 const unsigned long value) 120 { 121 const unsigned long io_port = offset / 8; 122 const unsigned long bank = io_port / 3; 123 unsigned long flags; 124 125 spin_lock_irqsave(&state[bank].lock, flags); 126 127 state[bank].control_state |= I8255_CONTROL_MODE_SET; 128 state[bank].control_state &= ~i8255_direction_mask(offset); 129 130 iowrite8(state[bank].control_state, &ppi[bank].control); 131 132 spin_unlock_irqrestore(&state[bank].lock, flags); 133 134 i8255_set(ppi, state, offset, value); 135 } 136 EXPORT_SYMBOL_NS_GPL(i8255_direction_output, I8255); 137 138 /** 139 * i8255_get - get signal value at signal offset 140 * @ppi: Intel 8255 Programmable Peripheral Interface banks 141 * @offset: offset of signal to get 142 * 143 * Returns the signal value (0=low, 1=high) for the signal at @offset for the 144 * respective Intel 8255 Programmable Peripheral Interface (@ppi) banks. 145 */ 146 int i8255_get(struct i8255 __iomem *const ppi, const unsigned long offset) 147 { 148 const unsigned long io_port = offset / 8; 149 const unsigned long offset_mask = BIT(offset % 8); 150 151 return !!i8255_get_port(ppi, io_port, offset_mask); 152 } 153 EXPORT_SYMBOL_NS_GPL(i8255_get, I8255); 154 155 /** 156 * i8255_get_direction - get the I/O direction for a signal offset 157 * @state: devices states of the respective PPI banks 158 * @offset: offset of signal to get direction 159 * 160 * Returns the signal direction (0=output, 1=input) for the signal at @offset. 161 */ 162 int i8255_get_direction(const struct i8255_state *const state, 163 const unsigned long offset) 164 { 165 const unsigned long io_port = offset / 8; 166 const unsigned long bank = io_port / 3; 167 168 return !!(state[bank].control_state & i8255_direction_mask(offset)); 169 } 170 EXPORT_SYMBOL_NS_GPL(i8255_get_direction, I8255); 171 172 /** 173 * i8255_get_multiple - get multiple signal values at multiple signal offsets 174 * @ppi: Intel 8255 Programmable Peripheral Interface banks 175 * @mask: mask of signals to get 176 * @bits: bitmap to store signal values 177 * @ngpio: number of GPIO signals of the respective PPI banks 178 * 179 * Stores in @bits the values (0=low, 1=high) for the signals defined by @mask 180 * for the respective Intel 8255 Programmable Peripheral Interface (@ppi) banks. 181 */ 182 void i8255_get_multiple(struct i8255 __iomem *const ppi, 183 const unsigned long *const mask, 184 unsigned long *const bits, const unsigned long ngpio) 185 { 186 unsigned long offset; 187 unsigned long port_mask; 188 unsigned long io_port; 189 unsigned long port_state; 190 191 bitmap_zero(bits, ngpio); 192 193 for_each_set_clump8(offset, port_mask, mask, ngpio) { 194 io_port = offset / 8; 195 port_state = i8255_get_port(ppi, io_port, port_mask); 196 197 bitmap_set_value8(bits, port_state, offset); 198 } 199 } 200 EXPORT_SYMBOL_NS_GPL(i8255_get_multiple, I8255); 201 202 /** 203 * i8255_mode0_output - configure all PPI ports to MODE 0 output mode 204 * @ppi: Intel 8255 Programmable Peripheral Interface bank 205 * 206 * Configures all Intel 8255 Programmable Peripheral Interface (@ppi) ports to 207 * MODE 0 (Basic Input/Output) output mode. 208 */ 209 void i8255_mode0_output(struct i8255 __iomem *const ppi) 210 { 211 iowrite8(I8255_CONTROL_MODE_SET, &ppi->control); 212 } 213 EXPORT_SYMBOL_NS_GPL(i8255_mode0_output, I8255); 214 215 /** 216 * i8255_set - set signal value at signal offset 217 * @ppi: Intel 8255 Programmable Peripheral Interface banks 218 * @state: devices states of the respective PPI banks 219 * @offset: offset of signal to set 220 * @value: value of signal to set 221 * 222 * Assigns output @value for the signal at @offset for the respective Intel 8255 223 * Programmable Peripheral Interface (@ppi) banks. 224 */ 225 void i8255_set(struct i8255 __iomem *const ppi, struct i8255_state *const state, 226 const unsigned long offset, const unsigned long value) 227 { 228 const unsigned long io_port = offset / 8; 229 const unsigned long port_offset = offset % 8; 230 const unsigned long mask = BIT(port_offset); 231 const unsigned long bits = value << port_offset; 232 233 i8255_set_port(ppi, state, io_port, mask, bits); 234 } 235 EXPORT_SYMBOL_NS_GPL(i8255_set, I8255); 236 237 /** 238 * i8255_set_multiple - set signal values at multiple signal offsets 239 * @ppi: Intel 8255 Programmable Peripheral Interface banks 240 * @state: devices states of the respective PPI banks 241 * @mask: mask of signals to set 242 * @bits: bitmap of signal output values 243 * @ngpio: number of GPIO signals of the respective PPI banks 244 * 245 * Assigns output values defined by @bits for the signals defined by @mask for 246 * the respective Intel 8255 Programmable Peripheral Interface (@ppi) banks. 247 */ 248 void i8255_set_multiple(struct i8255 __iomem *const ppi, 249 struct i8255_state *const state, 250 const unsigned long *const mask, 251 const unsigned long *const bits, 252 const unsigned long ngpio) 253 { 254 unsigned long offset; 255 unsigned long port_mask; 256 unsigned long io_port; 257 unsigned long value; 258 259 for_each_set_clump8(offset, port_mask, mask, ngpio) { 260 io_port = offset / 8; 261 value = bitmap_get_value8(bits, offset); 262 i8255_set_port(ppi, state, io_port, port_mask, value); 263 } 264 } 265 EXPORT_SYMBOL_NS_GPL(i8255_set_multiple, I8255); 266 267 /** 268 * i8255_state_init - initialize i8255_state structure 269 * @state: devices states of the respective PPI banks 270 * @nbanks: number of Intel 8255 Programmable Peripheral Interface banks 271 * 272 * Initializes the @state of each Intel 8255 Programmable Peripheral Interface 273 * bank for use in i8255 library functions. 274 */ 275 void i8255_state_init(struct i8255_state *const state, 276 const unsigned long nbanks) 277 { 278 unsigned long bank; 279 280 for (bank = 0; bank < nbanks; bank++) 281 spin_lock_init(&state[bank].lock); 282 } 283 EXPORT_SYMBOL_NS_GPL(i8255_state_init, I8255); 284 285 MODULE_AUTHOR("William Breathitt Gray"); 286 MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface"); 287 MODULE_LICENSE("GPL"); 288