gpio-i8255.c 9.1 KB

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