aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-spear/padmux.c
blob: 555eec6dc1cbfd14a4e750205217132d63cbb9f5 (plain) (blame)
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
/*
 * arch/arm/plat-spear/include/plat/padmux.c
 *
 * SPEAr platform specific gpio pads muxing source file
 *
 * Copyright (C) 2009 ST Microelectronics
 * Viresh Kumar<viresh.kumar@st.com>
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2. This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <plat/padmux.h>

/*
 * struct pmx: pmx definition structure
 *
 * base: base address of configuration registers
 * mode_reg: mode configurations
 * mux_reg: muxing configurations
 * active_mode: pointer to current active mode
 */
struct pmx {
	u32 base;
	struct pmx_reg mode_reg;
	struct pmx_reg mux_reg;
	struct pmx_mode *active_mode;
};

static struct pmx *pmx;

/**
 * pmx_mode_set - Enables an multiplexing mode
 * @mode - pointer to pmx mode
 *
 * It will set mode of operation in hardware.
 * Returns -ve on Err otherwise 0
 */
static int pmx_mode_set(struct pmx_mode *mode)
{
	u32 val;

	if (!mode->name)
		return -EFAULT;

	pmx->active_mode = mode;

	val = readl(pmx->base + pmx->mode_reg.offset);
	val &= ~pmx->mode_reg.mask;
	val |= mode->mask & pmx->mode_reg.mask;
	writel(val, pmx->base + pmx->mode_reg.offset);

	return 0;
}

/**
 * pmx_devs_enable - Enables list of devices
 * @devs - pointer to pmx device array
 * @count - number of devices to enable
 *
 * It will enable pads for all required peripherals once and only once.
 * If peripheral is not supported by current mode then request is rejected.
 * Conflicts between peripherals are not handled and peripherals will be
 * enabled in the order they are present in pmx_dev array.
 * In case of conflicts last peripheral enabled will be present.
 * Returns -ve on Err otherwise 0
 */
static int pmx_devs_enable(struct pmx_dev **devs, u8 count)
{
	u32 val, i, mask;

	if (!count)
		return -EINVAL;

	val = readl(pmx->base + pmx->mux_reg.offset);
	for (i = 0; i < count; i++) {
		u8 j = 0;

		if (!devs[i]->name || !devs[i]->modes) {
			printk(KERN_ERR "padmux: dev name or modes is null\n");
			continue;
		}
		/* check if peripheral exists in active mode */
		if (pmx->active_mode) {
			bool found = false;
			for (j = 0; j < devs[i]->mode_count; j++) {
				if (devs[i]->modes[j].ids &
						pmx->active_mode->id) {
					found = true;
					break;
				}
			}
			if (found == false) {
				printk(KERN_ERR "%s device not available in %s"\
						"mode\n", devs[i]->name,
						pmx->active_mode->name);
				continue;
			}
		}

		/* enable peripheral */
		mask = devs[i]->modes[j].mask & pmx->mux_reg.mask;
		if (devs[i]->enb_on_reset)
			val &= ~mask;
		else
			val |= mask;

		devs[i]->is_active = true;
	}
	writel(val, pmx->base + pmx->mux_reg.offset);
	kfree(pmx);

	/* this will ensure that multiplexing can't be changed now */
	pmx = (struct pmx *)-1;

	return 0;
}

/**
 * pmx_register - registers a platform requesting pad mux feature
 * @driver - pointer to driver structure containing driver specific parameters
 *
 * Also this must be called only once. This will allocate memory for pmx
 * structure, will call pmx_mode_set, will call pmx_devs_enable.
 * Returns -ve on Err otherwise 0
 */
int pmx_register(struct pmx_driver *driver)
{
	int ret = 0;

	if (pmx)
		return -EPERM;
	if (!driver->base || !driver->devs)
		return -EFAULT;

	pmx = kzalloc(sizeof(*pmx), GFP_KERNEL);
	if (!pmx)
		return -ENOMEM;

	pmx->base = (u32)driver->base;
	pmx->mode_reg.offset = driver->mode_reg.offset;
	pmx->mode_reg.mask = driver->mode_reg.mask;
	pmx->mux_reg.offset = driver->mux_reg.offset;
	pmx->mux_reg.mask = driver->mux_reg.mask;

	/* choose mode to enable */
	if (driver->mode) {
		ret = pmx_mode_set(driver->mode);
		if (ret)
			goto pmx_fail;
	}
	ret = pmx_devs_enable(driver->devs, driver->devs_count);
	if (ret)
		goto pmx_fail;

	return 0;

pmx_fail:
	return ret;
}