Exposes consumer functions providing support for Intel 8255 Programmable Peripheral Interface devices. A CONFIG_GPIO_I8255 Kconfig option is introduced; modules wanting access to these functions should select this Kconfig option. Tested-by: Fred Eckert <Frede@xxxxxxxxxxxx> Cc: John Hentges <jhentges@xxxxxxxxxxx> Cc: Jay Dolan <jay.dolan@xxxxxxxxxxx> Signed-off-by: William Breathitt Gray <william.gray@xxxxxxxxxx> --- MAINTAINERS | 6 + drivers/gpio/Kconfig | 3 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-i8255.c | 249 +++++++++++++++++++++++++++++++++++++ include/linux/gpio/i8255.h | 34 +++++ 5 files changed, 293 insertions(+) create mode 100644 drivers/gpio/gpio-i8255.c create mode 100644 include/linux/gpio/i8255.h diff --git a/MAINTAINERS b/MAINTAINERS index a6d3bd9d2a8d..c4ae792a8a43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9799,6 +9799,12 @@ L: linux-fbdev@xxxxxxxxxxxxxxx S: Maintained F: drivers/video/fbdev/i810/ +INTEL 8255 GPIO DRIVER +M: William Breathitt Gray <vilhelm.gray@xxxxxxxxx> +L: linux-gpio@xxxxxxxxxxxxxxx +S: Maintained +F: drivers/gpio/gpio-i8255.c + INTEL ASoC DRIVERS M: Cezary Rojewski <cezary.rojewski@xxxxxxxxx> M: Pierre-Louis Bossart <pierre-louis.bossart@xxxxxxxxxxxxxxx> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b01961999ced..5cbe93330213 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -829,6 +829,9 @@ endmenu menu "Port-mapped I/O GPIO drivers" depends on X86 # Unconditional I/O space access +config GPIO_I8255 + tristate + config GPIO_104_DIO_48E tristate "ACCES 104-DIO-48E GPIO support" depends on PC104 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 14352f6dfe8e..06057e127949 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o +obj-$(CONFIG_GPIO_I8255) += gpio-i8255.o obj-$(CONFIG_GPIO_ICH) += gpio-ich.o obj-$(CONFIG_GPIO_IDT3243X) += gpio-idt3243x.o obj-$(CONFIG_GPIO_IOP) += gpio-iop.o diff --git a/drivers/gpio/gpio-i8255.c b/drivers/gpio/gpio-i8255.c new file mode 100644 index 000000000000..ef43312015f4 --- /dev/null +++ b/drivers/gpio/gpio-i8255.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel 8255 Programmable Peripheral Interface + * Copyright (C) 2022 William Breathitt Gray + */ +#include <linux/bitmap.h> +#include <linux/compiler_types.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/gpio/i8255.h> +#include <linux/io.h> +#include <linux/module.h> + +#define I8255_CONTROL_PORTCLOWER_DIRECTION BIT(0) +#define I8255_CONTROL_PORTB_DIRECTION BIT(1) +#define I8255_CONTROL_PORTCUPPER_DIRECTION BIT(3) +#define I8255_CONTROL_PORTA_DIRECTION BIT(4) +#define I8255_CONTROL_MODE_SET BIT(7) +#define I8255_PORTA 0 +#define I8255_PORTB 1 +#define I8255_PORTC 2 + +static int i8255_get_port(const struct i8255 __iomem *const ppi, + const unsigned long io_port, const unsigned long mask) +{ + const unsigned long group = io_port / 3; + const unsigned long ppi_port = io_port % 3; + + return ioread8(&ppi[group].port[ppi_port]) & mask; +} + +static u8 i8255_direction_mask(const unsigned long offset) +{ + const unsigned long io_port = offset / 8; + const unsigned long ppi_port = io_port % 3; + + switch (ppi_port) { + case I8255_PORTA: + return I8255_CONTROL_PORTA_DIRECTION; + case I8255_PORTB: + return I8255_CONTROL_PORTB_DIRECTION; + case I8255_PORTC: + /* Port C can be configured by nibble */ + if (offset % 8 > 3) + return I8255_CONTROL_PORTCUPPER_DIRECTION; + return I8255_CONTROL_PORTCLOWER_DIRECTION; + default: + /* Should never reach this path */ + return 0; + } +} + +static void i8255_set_port(struct i8255 __iomem *const ppi, + const unsigned long io_port, + const unsigned long io_mask, + const unsigned long bit_mask) +{ + const unsigned long group = io_port / 3; + const unsigned long ppi_port = io_port % 3; + unsigned long out_state; + + out_state = ioread8(&ppi[group].port[ppi_port]); + out_state &= ~io_mask; + out_state |= bit_mask; + + iowrite8(out_state, &ppi[group].port[ppi_port]); +} + +/** + * i8255_direction_input - configure signal offset as input + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @control_state: control register states of the respective PPI groups + * @offset: signal offset to configure as input + * + * Configures a signal @offset as input for the respective Intel 8255 + * Programmable Peripheral Interface (@ppi) groups. The @control_state values + * are updated to reflect the new configuration. + */ +void i8255_direction_input(struct i8255 __iomem *const ppi, + u8 *const control_state, const unsigned long offset) +{ + const unsigned long io_port = offset / 8; + const unsigned long group = io_port / 3; + + control_state[group] |= I8255_CONTROL_MODE_SET; + control_state[group] |= i8255_direction_mask(offset); + + iowrite8(control_state[group], &ppi[group].control); +} +EXPORT_SYMBOL_GPL(i8255_direction_input); + +/** + * i8255_direction_output - configure signal offset as output + * @control_state: control register states of the respective PPI groups + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @offset: signal offset to configure as output + * @value: signal value to output + * + * Configures a signal @offset as output for the respective Intel 8255 + * Programmable Peripheral Interface (@ppi) groups and sets the respective + * signal output to the desired @value. The @control_state values are updated to + * reflect the new configuration. + */ +void i8255_direction_output(struct i8255 __iomem *const ppi, + u8 *const control_state, const unsigned long offset, + const unsigned long value) +{ + const unsigned long io_port = offset / 8; + const unsigned long group = io_port / 3; + + control_state[group] |= I8255_CONTROL_MODE_SET; + control_state[group] &= ~i8255_direction_mask(offset); + + iowrite8(control_state[group], &ppi[group].control); + i8255_set(ppi, offset, value); +} +EXPORT_SYMBOL_GPL(i8255_direction_output); + +/** + * i8255_get - get signal value at signal offset + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @offset: offset of signal to get + * + * Returns the signal value (0=low, 1=high) for the signal at @offset for the + * respective Intel 8255 Programmable Peripheral Interface (@ppi) groups. + */ +int i8255_get(const struct i8255 __iomem *const ppi, const unsigned long offset) +{ + const unsigned long io_port = offset / 8; + const unsigned long offset_mask = BIT(offset % 8); + + return !!i8255_get_port(ppi, io_port, offset_mask); +} +EXPORT_SYMBOL_GPL(i8255_get); + +/** + * i8255_get_direction - get the I/O direction for a signal offset + * @control_state: control register states of the respective PPI groups + * @offset: offset of signal to get direction + * + * Returns the signal direction (0=output, 1=input) for the signal at @offset. + * groups. + */ +int i8255_get_direction(const u8 *const control_state, + const unsigned long offset) +{ + const unsigned long io_port = offset / 8; + const unsigned long group = io_port / 3; + + return !!(control_state[group] & i8255_direction_mask(offset)); +} +EXPORT_SYMBOL_GPL(i8255_get_direction); + +/** + * i8255_get_multiple - get multiple signal values at multiple signal offsets + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @mask: mask of signals to get + * @bits: bitmap to store signal values + * @ngpio: number of GPIO signals of the respective PPI groups + * + * Stores in @bits the values (0=low, 1=high) for the signals defined by @mask + * for the respective Intel 8255 Programmable Peripheral Interface (@ppi) + * groups. + */ +void i8255_get_multiple(const struct i8255 __iomem *const ppi, + const unsigned long *const mask, + unsigned long *const bits, const unsigned long ngpio) +{ + unsigned long offset; + unsigned long gpio_mask; + unsigned long io_port; + unsigned long port_state; + + bitmap_zero(bits, ngpio); + + for_each_set_clump8(offset, gpio_mask, mask, ngpio) { + io_port = offset / 8; + port_state = i8255_get_port(ppi, io_port, gpio_mask); + + bitmap_set_value8(bits, port_state, offset); + } +} +EXPORT_SYMBOL_GPL(i8255_get_multiple); + +/** + * i8255_mode0_output - configure all PPI ports to MODE 0 output mode + * @ppi: Intel 8255 Programmable Peripheral Interface group + * + * Configures all Intel 8255 Programmable Peripheral Interface (@ppi) ports to + * MODE 0 (Basic Input/Output) output mode. + */ +void i8255_mode0_output(struct i8255 __iomem *const ppi) +{ + iowrite8(I8255_CONTROL_MODE_SET, &ppi->control); +} +EXPORT_SYMBOL_GPL(i8255_mode0_output); + +/** + * i8255_set - set signal value at signal offset + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @offset: offset of signal to set + * @value: value of signal to set + * + * Assigns output @value for the signal at @offset for the respective Intel 8255 + * Programmable Peripheral Interface (@ppi) groups. + */ +void i8255_set(struct i8255 __iomem *const ppi, const unsigned long offset, + const unsigned long value) +{ + const unsigned long io_port = offset / 8; + const unsigned long port_offset = offset % 8; + const unsigned long offset_mask = BIT(port_offset); + const unsigned long bit_mask = value << port_offset; + + i8255_set_port(ppi, io_port, offset_mask, bit_mask); +} +EXPORT_SYMBOL_GPL(i8255_set); + +/** + * i8255_set_multiple - set signal values at multiple signal offsets + * @ppi: Intel 8255 Programmable Peripheral Interface groups + * @mask: mask of signals to set + * @bits: bitmap of signal output values + * @ngpio: number of GPIO signals of the respective PPI groups + * + * Assigns output values defined by @bits for the signals defined by @mask for + * the respective Intel 8255 Programmable Peripheral Interface (@ppi) groups. + */ +void i8255_set_multiple(struct i8255 __iomem *const ppi, + const unsigned long *const mask, + const unsigned long *const bits, + const unsigned long ngpio) +{ + unsigned long offset; + unsigned long gpio_mask; + unsigned long bit_mask; + unsigned long io_port; + + for_each_set_clump8(offset, gpio_mask, mask, ngpio) { + bit_mask = bitmap_get_value8(bits, offset) & gpio_mask; + io_port = offset / 8; + i8255_set_port(ppi, io_port, gpio_mask, bit_mask); + } +} +EXPORT_SYMBOL_GPL(i8255_set_multiple); + +MODULE_AUTHOR("William Breathitt Gray"); +MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/gpio/i8255.h b/include/linux/gpio/i8255.h new file mode 100644 index 000000000000..7ddcf7fcd1dd --- /dev/null +++ b/include/linux/gpio/i8255.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2022 William Breathitt Gray */ +#ifndef _I8255_H_ +#define _I8255_H_ + +#include <linux/compiler_types.h> +#include <linux/types.h> + +/** + * struct i8255 - Intel 8255 register structure + * @port: Port A, B, and C + * @control: Control register + */ +struct i8255 { + u8 port[3]; + u8 control; +}; + +void i8255_direction_input(struct i8255 __iomem *ppi, u8 *control_state, + unsigned long offset); +void i8255_direction_output(struct i8255 __iomem *ppi, u8 *control_state, + unsigned long offset, unsigned long value); +int i8255_get(const struct i8255 __iomem *ppi, unsigned long offset); +int i8255_get_direction(const u8 *control_state, unsigned long offset); +void i8255_get_multiple(const struct i8255 __iomem *ppi, + const unsigned long *mask, unsigned long *bits, + unsigned long ngpio); +void i8255_mode0_output(struct i8255 __iomem *const ppi); +void i8255_set(struct i8255 __iomem *ppi, unsigned long offset, + unsigned long value); +void i8255_set_multiple(struct i8255 __iomem *ppi, const unsigned long *mask, + const unsigned long *bits, unsigned long ngpio); + +#endif /* _I8255_H_ */ -- 2.36.1