summaryrefslogtreecommitdiff
path: root/drivers/staging/cg2900/mfd/cg2900_lib.c
blob: 84f7eb5eb62c05a7fcc834faff22ddd0e20bc2fc (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
/*
 * Copyright (C) ST-Ericsson SA 2010
 * Authors:
 * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson.
 * License terms:  GNU General Public License (GPL), version 2
 *
 * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
 */
#define NAME					"cg2900_lib"
#define pr_fmt(fmt)				NAME ": " fmt "\n"

#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/types.h>

#include "cg2900.h"
#include "cg2900_chip.h"
#include "cg2900_core.h"
#include "cg2900_lib.h"

/*
 * Max length in bytes for line buffer used to parse settings and patch file.
 * Must be max length of name plus characters used to define chip version.
 */
#define LINE_BUFFER_LENGTH			(NAME_MAX + 30)
#define LOGGER_HEADER_SIZE			1
/**
 * cg2900_tx_to_chip() - Transmit buffer to the transport.
 * @user:	User data for BT command channel.
 * @logger:	User data for logger channel.
 * @skb:	Data packet.
 *
 * The transmit_skb_to_chip() function transmit buffer to the transport.
 * If enabled, copy the transmitted data to the HCI logger as well.
 */
void cg2900_tx_to_chip(struct cg2900_user_data *user,
		       struct cg2900_user_data *logger, struct sk_buff *skb)
{
	int err;
	struct cg2900_chip_dev *chip_dev;

	dev_dbg(user->dev, "cg2900_tx_to_chip %d bytes.\n", skb->len);

	if (logger)
		cg2900_send_to_hci_logger(logger, skb, LOGGER_DIRECTION_TX);

	chip_dev = cg2900_get_prv(user);
	err = chip_dev->t_cb.write(chip_dev, skb);
	if (err) {
		dev_err(user->dev, "cg2900_tx_to_chip: Transport write failed "
			"(%d)\n", err);
		kfree_skb(skb);
	}
}
EXPORT_SYMBOL_GPL(cg2900_tx_to_chip);

/**
 * cg2900_tx_no_user() - Transmit buffer to the transport.
 * @dev:	Current chip to transmit to.
 * @skb:	Data packet.
 *
 * This function transmits buffer to the transport when no user exist (system
 * startup for example).
 */
void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb)
{
	int err;

	dev_dbg(dev->dev, "cg2900_tx_no_user %d bytes.\n", skb->len);

	err = dev->t_cb.write(dev, skb);
	if (err) {
		dev_err(dev->dev, "cg2900_tx_no_user: Transport write failed "
			"(%d)\n", err);
		kfree_skb(skb);
	}
}
EXPORT_SYMBOL_GPL(cg2900_tx_no_user);

/**
 * create_and_send_bt_cmd() - Copy and send sk_buffer.
 * @user:	User data for current channel.
 * @logger:	User data for logger channel.
 * @data:	Data to send.
 * @length:	Length in bytes of data.
 *
 * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data
 * to it, and send the sk_buffer to controller.
 */
void cg2900_send_bt_cmd(struct cg2900_user_data *user,
			struct cg2900_user_data *logger,
			void *data, int length)
{
	struct sk_buff *skb;

	skb = user->alloc_skb(length, GFP_ATOMIC);
	if (!skb) {
		dev_err(user->dev, "cg2900_send_bt_cmd: Couldn't alloc "
			"sk_buff with length %d\n", length);
		return;
	}

	memcpy(skb_put(skb, length), data, length);
	skb_push(skb, HCI_H4_SIZE);
	skb->data[0] = HCI_BT_CMD_H4_CHANNEL;

	cg2900_tx_to_chip(user, logger, skb);
}
EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd);

/**
 * cg2900_send_bt_cmd_no_user() - Copy and send sk_buffer with no assigned user.
 * @dev:	Current chip to transmit to.
 * @data:	Data to send.
 * @length:	Length in bytes of data.
 *
 * The cg2900_send_bt_cmd_no_user() function allocate sk_buffer, copy supplied
 * data to it, and send the sk_buffer to controller.
 */
void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data,
				int length)
{
	struct sk_buff *skb;

	skb = alloc_skb(length + HCI_H4_SIZE, GFP_KERNEL);
	if (!skb) {
		dev_err(dev->dev, "cg2900_send_bt_cmd_no_user: Couldn't alloc "
			"sk_buff with length %d\n", length);
		return;
	}

	skb_reserve(skb, HCI_H4_SIZE);
	memcpy(skb_put(skb, length), data, length);
	skb_push(skb, HCI_H4_SIZE);
	skb->data[0] = HCI_BT_CMD_H4_CHANNEL;

	cg2900_tx_no_user(dev, skb);
}
EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd_no_user);

/**
 * create_work_item() - Create work item and add it to the work queue.
 * @wq:		Work queue.
 * @work_func:	Work function.
 * @user_data:	Arbitrary data set by user.
 *
 * The create_work_item() function creates work item and add it to
 * the work queue.
 * Note that work is allocated by kmalloc and work must be freed when work
 * function is started.
 */
void cg2900_create_work_item(struct workqueue_struct *wq, work_func_t work_func,
			     void *user_data)
{
	struct cg2900_work *new_work;
	int err;

	new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
	if (!new_work) {
		pr_err("Failed to alloc memory for new_work");
		return;
	}

	INIT_WORK(&new_work->work, work_func);
	new_work->user_data = user_data;

	err = queue_work(wq, &new_work->work);
	if (!err) {
		pr_err("Failed to queue work_struct because it's already "
		       "in the queue");
		kfree(new_work);
	}
}
EXPORT_SYMBOL_GPL(cg2900_create_work_item);

/**
 * read_and_send_file_part() - Transmit a part of the supplied file.
 * @user:	User data for current channel.
 * @logger:	User data for logger channel.
 * @info:	File information.
 *
 * The cg2900_read_and_send_file_part() function transmit a part of the supplied
 * file to the controller.
 *
 * Returns:
 *   0 if there is no more data in the file.
 *   >0 for number of bytes sent.
 *   -ENOMEM if skb allocation failed.
 */
int cg2900_read_and_send_file_part(struct cg2900_user_data *user,
				   struct cg2900_user_data *logger,
				   struct cg2900_file_info *info)
{
	int bytes_to_copy;
	struct sk_buff *skb;
	struct bt_vs_write_file_block_cmd *cmd;
	int plen;

	/*
	 * Calculate number of bytes to copy;
	 * either max bytes for HCI packet or number of bytes left in file
	 */
	bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE,
			    (int)(info->fw_file->size - info->file_offset));

	if (bytes_to_copy <= 0) {
		/* Nothing more to read in file. */
		dev_dbg(user->dev, "File download finished\n");
		info->chunk_id = 0;
		info->file_offset = 0;
		return 0;
	}

	/* There is more data to send */
	plen = sizeof(*cmd) + bytes_to_copy;
	skb = user->alloc_skb(plen, GFP_KERNEL);
	if (!skb) {
		dev_err(user->dev, "Couldn't allocate sk_buffer\n");
		return -ENOMEM;
	}

	skb_put(skb, plen);

	cmd = (struct bt_vs_write_file_block_cmd *)skb->data;
	cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_WRITE_FILE_BLOCK);
	cmd->plen = BT_PARAM_LEN(plen);
	cmd->id = info->chunk_id;
	info->chunk_id++;

	/* Copy the data from offset position */
	memcpy(cmd->data,
	       &(info->fw_file->data[info->file_offset]),
	       bytes_to_copy);

	/* Increase offset with number of bytes copied */
	info->file_offset += bytes_to_copy;

	skb_push(skb, CG2900_SKB_RESERVE);
	skb->data[0] = HCI_BT_CMD_H4_CHANNEL;

	cg2900_tx_to_chip(user, logger, skb);

	return bytes_to_copy;
}
EXPORT_SYMBOL_GPL(cg2900_read_and_send_file_part);

void cg2900_send_to_hci_logger(struct cg2900_user_data *logger,
							struct sk_buff *skb,
							u8 direction)
{
	struct sk_buff *skb_log;
	u8 *p;

	/*
	 * Alloc a new sk_buff and copy the data into it. Then send it to
	 * the HCI logger.
	 */
	skb_log = alloc_skb(skb->len + LOGGER_HEADER_SIZE, GFP_NOWAIT);
	if (!skb_log) {
		pr_err("cg2900_send_to_hci_logger:\
				 Couldn't allocate skb_log\n");
		return;
	}
	/* Reserve 1 byte for direction.*/
	skb_reserve(skb_log, LOGGER_HEADER_SIZE);

	memcpy(skb_put(skb_log, skb->len), skb->data, skb->len);
	p = skb_push(skb_log, LOGGER_HEADER_SIZE);
	*p = (u8) direction;

	if (logger->read_cb)
		logger->read_cb(logger, skb_log);
	else
		kfree_skb(skb_log);

	return;
}
EXPORT_SYMBOL_GPL(cg2900_send_to_hci_logger);

MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Linux CG2900 Library functions");