summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpio-i8255.c
blob: 64ab80fc4a1e5c881352d7a3b01b0ae8ea71c72e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// SPDX-License-Identifier: GPL-2.0
/*
 * Intel 8255 Programmable Peripheral Interface
 * Copyright (C) 2022 William Breathitt Gray
 */
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/gpio/regmap.h>
#include <linux/module.h>
#include <linux/regmap.h>

#include "gpio-i8255.h"

#define I8255_NGPIO 24
#define I8255_NGPIO_PER_REG 8
#define I8255_CONTROL_PORTC_LOWER_DIRECTION BIT(0)
#define I8255_CONTROL_PORTB_DIRECTION BIT(1)
#define I8255_CONTROL_PORTC_UPPER_DIRECTION BIT(3)
#define I8255_CONTROL_PORTA_DIRECTION BIT(4)
#define I8255_CONTROL_MODE_SET BIT(7)
#define I8255_PORTA 0x0
#define I8255_PORTB 0x1
#define I8255_PORTC 0x2
#define I8255_CONTROL 0x3
#define I8255_REG_DAT_BASE I8255_PORTA
#define I8255_REG_DIR_IN_BASE I8255_CONTROL

static int i8255_direction_mask(const unsigned int offset)
{
	const unsigned int stride = offset / I8255_NGPIO_PER_REG;
	const unsigned int line = offset % I8255_NGPIO_PER_REG;

	switch (stride) {
	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 (line >= 4)
			return I8255_CONTROL_PORTC_UPPER_DIRECTION;
		return I8255_CONTROL_PORTC_LOWER_DIRECTION;
	default:
		/* Should never reach this path */
		return 0;
	}
}

static int i8255_ppi_init(struct regmap *const map, const unsigned int base)
{
	int err;

	/* Configure all ports to MODE 0 output mode */
	err = regmap_write(map, base + I8255_CONTROL, I8255_CONTROL_MODE_SET);
	if (err)
		return err;

	/* Initialize all GPIO to output 0 */
	err = regmap_write(map, base + I8255_PORTA, 0x00);
	if (err)
		return err;
	err = regmap_write(map, base + I8255_PORTB, 0x00);
	if (err)
		return err;
	return regmap_write(map, base + I8255_PORTC, 0x00);
}

static int i8255_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
				unsigned int offset, unsigned int *reg,
				unsigned int *mask)
{
	const unsigned int ppi = offset / I8255_NGPIO;
	const unsigned int ppi_offset = offset % I8255_NGPIO;
	const unsigned int stride = ppi_offset / I8255_NGPIO_PER_REG;
	const unsigned int line = ppi_offset % I8255_NGPIO_PER_REG;

	switch (base) {
	case I8255_REG_DAT_BASE:
		*reg = base + stride + ppi * 4;
		*mask = BIT(line);
		return 0;
	case I8255_REG_DIR_IN_BASE:
		*reg = base + ppi * 4;
		*mask = i8255_direction_mask(ppi_offset);
		return 0;
	default:
		/* Should never reach this path */
		return -EINVAL;
	}
}

/**
 * devm_i8255_regmap_register - Register an i8255 GPIO controller
 * @dev:	device that is registering this i8255 GPIO device
 * @config:	configuration for i8255_regmap_config
 *
 * Registers an Intel 8255 Programmable Peripheral Interface GPIO controller.
 * Returns 0 on success and negative error number on failure.
 */
int devm_i8255_regmap_register(struct device *const dev,
			       const struct i8255_regmap_config *const config)
{
	struct gpio_regmap_config gpio_config = {0};
	unsigned long i;
	int err;

	if (!config->parent)
		return -EINVAL;

	if (!config->map)
		return -EINVAL;

	if (!config->num_ppi)
		return -EINVAL;

	for (i = 0; i < config->num_ppi; i++) {
		err = i8255_ppi_init(config->map, i * 4);
		if (err)
			return err;
	}

	gpio_config.parent = config->parent;
	gpio_config.regmap = config->map;
	gpio_config.ngpio = I8255_NGPIO * config->num_ppi;
	gpio_config.names = config->names;
	gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE);
	gpio_config.reg_set_base = GPIO_REGMAP_ADDR(I8255_REG_DAT_BASE);
	gpio_config.reg_dir_in_base = GPIO_REGMAP_ADDR(I8255_REG_DIR_IN_BASE);
	gpio_config.ngpio_per_reg = I8255_NGPIO_PER_REG;
	gpio_config.irq_domain = config->domain;
	gpio_config.reg_mask_xlate = i8255_reg_mask_xlate;

	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
}
EXPORT_SYMBOL_NS_GPL(devm_i8255_regmap_register, I8255);

MODULE_AUTHOR("William Breathitt Gray");
MODULE_DESCRIPTION("Intel 8255 Programmable Peripheral Interface");
MODULE_LICENSE("GPL");