summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-i8255.c
blob: 9b97db418df18bbdbb20f5501b179034f368ca07 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// SPDX-License-Identifier: GPL-2.0
/*
 * Intel 8255 Programmable Peripheral Interface
 * Copyright (C) 2022 William Breathitt Gray
 */
#include <linux/bitmap.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/types.h>

#include "gpio-i8255.h"

#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 0
#define I8255_PORTB 1
#define I8255_PORTC 2

static int i8255_get_port(struct i8255 __iomem *const ppi,
			  const unsigned long io_port, const unsigned long mask)
{
	const unsigned long bank = io_port / 3;
	const unsigned long ppi_port = io_port % 3;

	return ioread8(&ppi[bank].port[ppi_port]) & mask;
}

static u8 i8255_direction_mask(const unsigned long offset)
{
	const unsigned long port_offset = offset % 8;
	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 (port_offset >= 4)
			return I8255_CONTROL_PORTC_UPPER_DIRECTION;
		return I8255_CONTROL_PORTC_LOWER_DIRECTION;
	default:
		/* Should never reach this path */
		return 0;
	}
}

static void i8255_set_port(struct i8255 __iomem *const ppi,
			   struct i8255_state *const state,
			   const unsigned long io_port,
			   const unsigned long mask, const unsigned long bits)
{
	const unsigned long bank = io_port / 3;
	const unsigned long ppi_port = io_port % 3;
	unsigned long flags;
	unsigned long out_state;

	spin_lock_irqsave(&state[bank].lock, flags);

	out_state = ioread8(&ppi[bank].port[ppi_port]);
	out_state = (out_state & ~mask) | (bits & mask);
	iowrite8(out_state, &ppi[bank].port[ppi_port]);

	spin_unlock_irqrestore(&state[bank].lock, flags);
}

/**
 * i8255_direction_input - configure signal offset as input
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @state:	devices states of the respective PPI banks
 * @offset:	signal offset to configure as input
 *
 * Configures a signal @offset as input for the respective Intel 8255
 * Programmable Peripheral Interface (@ppi) banks. The @state control_state
 * values are updated to reflect the new configuration.
 */
void i8255_direction_input(struct i8255 __iomem *const ppi,
			   struct i8255_state *const state,
			   const unsigned long offset)
{
	const unsigned long io_port = offset / 8;
	const unsigned long bank = io_port / 3;
	unsigned long flags;

	spin_lock_irqsave(&state[bank].lock, flags);

	state[bank].control_state |= I8255_CONTROL_MODE_SET;
	state[bank].control_state |= i8255_direction_mask(offset);

	iowrite8(state[bank].control_state, &ppi[bank].control);

	spin_unlock_irqrestore(&state[bank].lock, flags);
}
EXPORT_SYMBOL_NS_GPL(i8255_direction_input, I8255);

/**
 * i8255_direction_output - configure signal offset as output
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @state:	devices states of the respective PPI banks
 * @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) banks and sets the respective signal
 * output to the desired @value. The @state control_state values are updated to
 * reflect the new configuration.
 */
void i8255_direction_output(struct i8255 __iomem *const ppi,
			    struct i8255_state *const state,
			    const unsigned long offset,
			    const unsigned long value)
{
	const unsigned long io_port = offset / 8;
	const unsigned long bank = io_port / 3;
	unsigned long flags;

	spin_lock_irqsave(&state[bank].lock, flags);

	state[bank].control_state |= I8255_CONTROL_MODE_SET;
	state[bank].control_state &= ~i8255_direction_mask(offset);

	iowrite8(state[bank].control_state, &ppi[bank].control);

	spin_unlock_irqrestore(&state[bank].lock, flags);

	i8255_set(ppi, state, offset, value);
}
EXPORT_SYMBOL_NS_GPL(i8255_direction_output, I8255);

/**
 * i8255_get - get signal value at signal offset
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @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) banks.
 */
int i8255_get(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_NS_GPL(i8255_get, I8255);

/**
 * i8255_get_direction - get the I/O direction for a signal offset
 * @state:	devices states of the respective PPI banks
 * @offset:	offset of signal to get direction
 *
 * Returns the signal direction (0=output, 1=input) for the signal at @offset.
 */
int i8255_get_direction(const struct i8255_state *const state,
			const unsigned long offset)
{
	const unsigned long io_port = offset / 8;
	const unsigned long bank = io_port / 3;

	return !!(state[bank].control_state & i8255_direction_mask(offset));
}
EXPORT_SYMBOL_NS_GPL(i8255_get_direction, I8255);

/**
 * i8255_get_multiple - get multiple signal values at multiple signal offsets
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @mask:	mask of signals to get
 * @bits:	bitmap to store signal values
 * @ngpio:	number of GPIO signals of the respective PPI banks
 *
 * Stores in @bits the values (0=low, 1=high) for the signals defined by @mask
 * for the respective Intel 8255 Programmable Peripheral Interface (@ppi) banks.
 */
void i8255_get_multiple(struct i8255 __iomem *const ppi,
			const unsigned long *const mask,
			unsigned long *const bits, const unsigned long ngpio)
{
	unsigned long offset;
	unsigned long port_mask;
	unsigned long io_port;
	unsigned long port_state;

	bitmap_zero(bits, ngpio);

	for_each_set_clump8(offset, port_mask, mask, ngpio) {
		io_port = offset / 8;
		port_state = i8255_get_port(ppi, io_port, port_mask);

		bitmap_set_value8(bits, port_state, offset);
	}
}
EXPORT_SYMBOL_NS_GPL(i8255_get_multiple, I8255);

/**
 * i8255_mode0_output - configure all PPI ports to MODE 0 output mode
 * @ppi:	Intel 8255 Programmable Peripheral Interface bank
 *
 * 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_NS_GPL(i8255_mode0_output, I8255);

/**
 * i8255_set - set signal value at signal offset
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @state:	devices states of the respective PPI banks
 * @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) banks.
 */
void i8255_set(struct i8255 __iomem *const ppi, struct i8255_state *const state,
	       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 mask = BIT(port_offset);
	const unsigned long bits = value << port_offset;

	i8255_set_port(ppi, state, io_port, mask, bits);
}
EXPORT_SYMBOL_NS_GPL(i8255_set, I8255);

/**
 * i8255_set_multiple - set signal values at multiple signal offsets
 * @ppi:	Intel 8255 Programmable Peripheral Interface banks
 * @state:	devices states of the respective PPI banks
 * @mask:	mask of signals to set
 * @bits:	bitmap of signal output values
 * @ngpio:	number of GPIO signals of the respective PPI banks
 *
 * Assigns output values defined by @bits for the signals defined by @mask for
 * the respective Intel 8255 Programmable Peripheral Interface (@ppi) banks.
 */
void i8255_set_multiple(struct i8255 __iomem *const ppi,
			struct i8255_state *const state,
			const unsigned long *const mask,
			const unsigned long *const bits,
			const unsigned long ngpio)
{
	unsigned long offset;
	unsigned long port_mask;
	unsigned long io_port;
	unsigned long value;

	for_each_set_clump8(offset, port_mask, mask, ngpio) {
		io_port = offset / 8;
		value = bitmap_get_value8(bits, offset);
		i8255_set_port(ppi, state, io_port, port_mask, value);
	}
}
EXPORT_SYMBOL_NS_GPL(i8255_set_multiple, I8255);

/**
 * i8255_state_init - initialize i8255_state structure
 * @state:	devices states of the respective PPI banks
 * @nbanks:	number of Intel 8255 Programmable Peripheral Interface banks
 *
 * Initializes the @state of each Intel 8255 Programmable Peripheral Interface
 * bank for use in i8255 library functions.
 */
void i8255_state_init(struct i8255_state *const state,
		      const unsigned long nbanks)
{
	unsigned long bank;

	for (bank = 0; bank < nbanks; bank++)
		spin_lock_init(&state[bank].lock);
}
EXPORT_SYMBOL_NS_GPL(i8255_state_init, I8255);

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