summaryrefslogtreecommitdiff
path: root/drivers/staging/cg2900/devices-cg2900.c
blob: 9fb17c791d95f9091a2966e92ee68c8393596db3 (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
288
289
290
291
292
293
/*
 * Copyright (C) ST-Ericsson SA 2011
 * Authors:
 * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson.
 * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
 * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
 * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
 * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
 * License terms:  GNU General Public License (GPL), version 2
 *
 * Board specific device support for the Linux Bluetooth HCI H:4 Driver
 * for ST-Ericsson connectivity controller.
 */

#define NAME			"devices-cg2900"
#define pr_fmt(fmt)		NAME ": " fmt "\n"

#include <asm/byteorder.h>
#include <asm-generic/errno-base.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/types.h>
#include <plat/pincfg.h>
#include <asm/mach-types.h>
#include <mach/id.h>
#include <linux/mfd/ab8500.h>
#include <linux/regulator/consumer.h>

#include "cg2900.h"
#include "devices-cg2900.h"

#define BT_VS_POWER_SWITCH_OFF		0xFD40

#define H4_HEADER_LENGTH		0x01
#define BT_HEADER_LENGTH		0x03

#define STLC2690_HCI_REV		0x0600
#define CG2900_PG1_HCI_REV		0x0101
#define CG2900_PG2_HCI_REV		0x0200
#define CG2900_PG1_SPECIAL_HCI_REV	0x0700

struct vs_power_sw_off_cmd {
	__le16	op_code;
	u8	len;
	u8	gpio_0_7_pull_up;
	u8	gpio_8_15_pull_up;
	u8	gpio_16_20_pull_up;
	u8	gpio_0_7_pull_down;
	u8	gpio_8_15_pull_down;
	u8	gpio_16_20_pull_down;
} __packed;

static struct sk_buff *dcg2900_get_power_switch_off_cmd
				(struct cg2900_chip_dev *dev, u16 *op_code)
{
	struct sk_buff *skb;
	struct vs_power_sw_off_cmd *cmd;
	struct dcg2900_info *info;
	int i;

	/* If connected chip does not support the command return NULL */
	if (CG2900_PG1_SPECIAL_HCI_REV != dev->chip.hci_revision &&
	    CG2900_PG1_HCI_REV != dev->chip.hci_revision &&
	    CG2900_PG2_HCI_REV != dev->chip.hci_revision)
		return NULL;

	dev_dbg(dev->dev, "Generating PowerSwitchOff command\n");

	info = dev->b_data;

	skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL);
	if (!skb) {
		dev_err(dev->dev, "Could not allocate skb\n");
		return NULL;
	}

	skb_reserve(skb, H4_HEADER_LENGTH);
	cmd = (struct vs_power_sw_off_cmd *)skb_put(skb, sizeof(*cmd));
	cmd->op_code = cpu_to_le16(BT_VS_POWER_SWITCH_OFF);
	cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH;
	/*
	 * Enter system specific GPIO settings here:
	 * Section data[3-5] is GPIO pull-up selection
	 * Section data[6-8] is GPIO pull-down selection
	 * Each section is a bitfield where
	 * - byte 0 bit 0 is GPIO 0
	 * - byte 0 bit 1 is GPIO 1
	 * - up to
	 * - byte 2 bit 4 which is GPIO 20
	 * where each bit means:
	 * - 0: No pull-up / no pull-down
	 * - 1: Pull-up / pull-down
	 * All GPIOs are set as input.
	 */
	if (!info->sleep_gpio_set) {
		struct cg2900_platform_data *pf_data;

		pf_data = dev_get_platdata(dev->dev);
		for (i = 0; i < 8; i++) {
			if (pf_data->gpio_sleep[i] == CG2900_PULL_UP)
				info->gpio_0_7_pull_up |= (1 << i);
			else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN)
				info->gpio_0_7_pull_down |= (1 << i);
		}
		for (i = 8; i < 16; i++) {
			if (pf_data->gpio_sleep[i] == CG2900_PULL_UP)
				info->gpio_8_15_pull_up |= (1 << (i - 8));
			else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN)
				info->gpio_8_15_pull_down |= (1 << (i - 8));
		}
		for (i = 16; i < 21; i++) {
			if (pf_data->gpio_sleep[i] == CG2900_PULL_UP)
				info->gpio_16_20_pull_up |= (1 << (i - 16));
			else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN)
				info->gpio_16_20_pull_down |= (1 << (i - 16));
		}
		info->sleep_gpio_set = true;
	}
	cmd->gpio_0_7_pull_up = info->gpio_0_7_pull_up;
	cmd->gpio_8_15_pull_up = info->gpio_8_15_pull_up;
	cmd->gpio_16_20_pull_up = info->gpio_16_20_pull_up;
	cmd->gpio_0_7_pull_down = info->gpio_0_7_pull_down;
	cmd->gpio_8_15_pull_down = info->gpio_8_15_pull_down;
	cmd->gpio_16_20_pull_down = info->gpio_16_20_pull_down;


	if (op_code)
		*op_code = BT_VS_POWER_SWITCH_OFF;

	return skb;
}

static int dcg2900_init(struct cg2900_chip_dev *dev)
{
	int err = 0;
	struct dcg2900_info *info;
	struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev);

	/* First retrieve and save the resources */
	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info) {
		dev_err(dev->dev, "Could not allocate dcg2900_info\n");
		return -ENOMEM;
	}

	info->gbf_gpio = -1;
	info->pmuen_gpio = -1;
	info->bt_gpio = -1;

	if (!dev->pdev->num_resources) {
		dev_dbg(dev->dev, "No resources available\n");
		goto finished;
	}
	if (cpu_is_u8500())
		err = dcg2900_setup(dev, info);
	else
		err = dcg2900_u5500_setup(dev, info);

	err = dcg2900_setup(dev, info);
	if (err)
		goto err_handling;

	/*
	 * Enable the power on snowball
	 */
	if (machine_is_snowball()) {
		/* Take the regulator */
		if (pdata->regulator_id) {
			info->regulator_wlan = regulator_get(dev->dev,
					pdata->regulator_id);
			if (IS_ERR(info->regulator_wlan)) {
				err = PTR_ERR(info->regulator_wlan);
				dev_warn(dev->dev,
					"%s: Failed to get regulator '%s'\n",
					__func__, pdata->regulator_id);
				info->regulator_wlan = NULL;
				goto err_handling_free_gpio_bt;
			}
			/* Enable it also */
			err = regulator_enable(info->regulator_wlan);
			if (err < 0) {
				dev_warn(dev->dev, "%s: regulator_enable failed\n",
						__func__);
				goto err_handling_put_reg;
			}
		} else {
			dev_warn(dev->dev, "%s: no regulator defined for snowball.\n",
					__func__);
		}
	}

finished:
	dev->b_data = info;
	return 0;
err_handling_put_reg:
	regulator_put(info->regulator_wlan);
err_handling_free_gpio_bt:
	gpio_free(info->bt_gpio);
err_handling_free_gpio_gbf:
	gpio_free(info->gbf_gpio);
err_handling:
	kfree(info);
	return err;
}

static void dcg2900_exit(struct cg2900_chip_dev *dev)
{
	struct dcg2900_info *info = dev->b_data;

	if (machine_is_snowball()) {
		/* Turn off power if we have any */
		if (info->regulator_wlan) {
			regulator_disable(info->regulator_wlan);
			regulator_put(info->regulator_wlan);
		}
	}

	dcg2900_disable_chip(dev);
	if (info->bt_gpio != -1)
		gpio_free(info->bt_gpio);
	if (info->pmuen_gpio != -1)
		gpio_free(info->pmuen_gpio);
	if (info->gbf_gpio != -1)
		gpio_free(info->gbf_gpio);
	kfree(info);
	dev->b_data = NULL;
}

static int dcg2900_disable_uart(struct cg2900_chip_dev *dev)
{
	int err;
	struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev);

	/*
	 * Without this delay we get interrupt on CTS immediately
	 * due to some turbulences on this line.
	 */
	mdelay(4);

	/* Disable UART functions. */
	err = nmk_config_pins(pdata->uart.uart_disabled,
			      pdata->uart.n_uart_gpios);
	if (err)
		goto error;

	return 0;

error:
	(void)nmk_config_pins(pdata->uart.uart_enabled,
			      pdata->uart.n_uart_gpios);
	dev_err(dev->dev, "Cannot set interrupt (%d)\n", err);
	return err;
}

static int dcg2900_enable_uart(struct cg2900_chip_dev *dev)
{
	int err;
	struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev);

	/* Restore UART settings. */
	err = nmk_config_pins(pdata->uart.uart_enabled,
			      pdata->uart.n_uart_gpios);
	if (err)
		dev_err(dev->dev, "Unable to enable UART (%d)\n", err);

	return err;
}

void dcg2900_init_platdata(struct cg2900_platform_data *data)
{
	data->init = dcg2900_init;
	data->exit = dcg2900_exit;

	if (cpu_is_u8500()) {
		data->enable_chip = dcg2900_enable_chip;
		data->disable_chip = dcg2900_disable_chip;
	} else {
		data->enable_chip = dcg2900_u5500_enable_chip;
		data->disable_chip = dcg2900_u5500_disable_chip;
	}

	data->get_power_switch_off_cmd = dcg2900_get_power_switch_off_cmd;

	data->uart.enable_uart = dcg2900_enable_uart;
	data->uart.disable_uart = dcg2900_disable_uart;
}