summaryrefslogtreecommitdiffstats
path: root/drivers/uwb/i1480/dfu/phy.c
blob: 3b1a87de8e63b058e8053265650be66dcff70bac (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
/*
 * Intel Wireless UWB Link 1480
 * PHY parameters upload
 *
 * Copyright (C) 2005-2006 Intel Corporation
 * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 *
 * Code for uploading the PHY parameters to the PHY through the UWB
 * Radio Control interface.
 *
 * We just send the data through the MPI interface using HWA-like
 * commands and then reset the PHY to make sure it is ok.
 */
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/usb/wusb.h>
#include "i1480-dfu.h"


/**
 * Write a value array to an address of the MPI interface
 *
 * @i1480:	Device descriptor
 * @data:	Data array to write
 * @size:	Size of the data array
 * @returns:	0 if ok, < 0 errno code on error.
 *
 * The data array is organized into pairs:
 *
 * ADDRESS VALUE
 *
 * ADDRESS is BE 16 bit unsigned, VALUE 8 bit unsigned. Size thus has
 * to be a multiple of three.
 */
static
int i1480_mpi_write(struct i1480 *i1480, const void *data, size_t size)
{
	int result;
	struct i1480_cmd_mpi_write *cmd = i1480->cmd_buf;
	struct i1480_evt_confirm *reply = i1480->evt_buf;

	BUG_ON(size > 480);
	result = -ENOMEM;
	cmd->rccb.bCommandType = i1480_CET_VS1;
	cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_WRITE);
	cmd->size = cpu_to_le16(size);
	memcpy(cmd->data, data, size);
	reply->rceb.bEventType = i1480_CET_VS1;
	reply->rceb.wEvent = i1480_CMD_MPI_WRITE;
	result = i1480_cmd(i1480, "MPI-WRITE", sizeof(*cmd) + size, sizeof(*reply));
	if (result < 0)
		goto out;
	if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
		dev_err(i1480->dev, "MPI-WRITE: command execution failed: %d\n",
			reply->bResultCode);
		result = -EIO;
	}
out:
	return result;
}


/**
 * Read a value array to from an address of the MPI interface
 *
 * @i1480:	Device descriptor
 * @data:	where to place the read array
 * @srcaddr:	Where to read from
 * @size:	Size of the data read array
 * @returns:	0 if ok, < 0 errno code on error.
 *
 * The command data array is organized into pairs ADDR0 ADDR1..., and
 * the returned data in ADDR0 VALUE0 ADDR1 VALUE1...
 *
 * We generate the command array to be a sequential read and then
 * rearrange the result.
 *
 * We use the i1480->cmd_buf for the command, i1480->evt_buf for the reply.
 *
 * As the reply has to fit in 512 bytes (i1480->evt_buffer), the max amount
 * of values we can read is (512 - sizeof(*reply)) / 3
 */
static
int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size)
{
	int result;
	struct i1480_cmd_mpi_read *cmd = i1480->cmd_buf;
	struct i1480_evt_mpi_read *reply = i1480->evt_buf;
	unsigned cnt;

	memset(i1480->cmd_buf, 0x69, 512);
	memset(i1480->evt_buf, 0x69, 512);

	BUG_ON(size > (i1480->buf_size - sizeof(*reply)) / 3);
	result = -ENOMEM;
	cmd->rccb.bCommandType = i1480_CET_VS1;
	cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_READ);
	cmd->size = cpu_to_le16(3*size);
	for (cnt = 0; cnt < size; cnt++) {
		cmd->data[cnt].page = (srcaddr + cnt) >> 8;
		cmd->data[cnt].offset = (srcaddr + cnt) & 0xff;
	}
	reply->rceb.bEventType = i1480_CET_VS1;
	reply->rceb.wEvent = i1480_CMD_MPI_READ;
	result = i1480_cmd(i1480, "MPI-READ", sizeof(*cmd) + 2*size,
			sizeof(*reply) + 3*size);
	if (result < 0)
		goto out;
	if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
		dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n",
			reply->bResultCode);
		result = -EIO;
	}
	for (cnt = 0; cnt < size; cnt++) {
		if (reply->data[cnt].page != (srcaddr + cnt) >> 8)
			dev_err(i1480->dev, "MPI-READ: page inconsistency at "
				"index %u: expected 0x%02x, got 0x%02x\n", cnt,
				(srcaddr + cnt) >> 8, reply->data[cnt].page);
		if (reply->data[cnt].offset != ((srcaddr + cnt) & 0x00ff))
			dev_err(i1480->dev, "MPI-READ: offset inconsistency at "
				"index %u: expected 0x%02x, got 0x%02x\n", cnt,
				(srcaddr + cnt) & 0x00ff,
				reply->data[cnt].offset);
		data[cnt] = reply->data[cnt].value;
	}
	result = 0;
out:
	return result;
}


/**
 * Upload a PHY firmware, wait for it to start
 *
 * @i1480:     Device instance
 * @fw_name: Name of the file that contains the firmware
 *
 * We assume the MAC fw is up and running. This means we can use the
 * MPI interface to write the PHY firmware. Once done, we issue an
 * MBOA Reset, which will force the MAC to reset and reinitialize the
 * PHY. If that works, we are ready to go.
 *
 * Max packet size for the MPI write is 512, so the max buffer is 480
 * (which gives us 160 byte triads of MSB, LSB and VAL for the data).
 */
int i1480_phy_fw_upload(struct i1480 *i1480)
{
	int result;
	const struct firmware *fw;
	const char *data_itr, *data_top;
	const size_t MAX_BLK_SIZE = 480;	/* 160 triads */
	size_t data_size;
	u8 phy_stat;

	result = request_firmware(&fw, i1480->phy_fw_name, i1480->dev);
	if (result < 0)
		goto out;
	/* Loop writing data in chunks as big as possible until done. */
	for (data_itr = fw->data, data_top = data_itr + fw->size;
	     data_itr < data_top; data_itr += MAX_BLK_SIZE) {
		data_size = min(MAX_BLK_SIZE, (size_t) (data_top - data_itr));
		result = i1480_mpi_write(i1480, data_itr, data_size);
		if (result < 0)
			goto error_mpi_write;
	}
	/* Read MPI page 0, offset 6; if 0, PHY was initialized correctly. */
	result = i1480_mpi_read(i1480, &phy_stat, 0x0006, 1);
	if (result < 0) {
		dev_err(i1480->dev, "PHY: can't get status: %d\n", result);
		goto error_mpi_status;
	}
	if (phy_stat != 0) {
		result = -ENODEV;
		dev_info(i1480->dev, "error, PHY not ready: %u\n", phy_stat);
		goto error_phy_status;
	}
	dev_info(i1480->dev, "PHY fw '%s': uploaded\n", i1480->phy_fw_name);
error_phy_status:
error_mpi_status:
error_mpi_write:
	release_firmware(fw);
	if (result < 0)
		dev_err(i1480->dev, "PHY fw '%s': failed to upload (%d), "
			"power cycle device\n", i1480->phy_fw_name, result);
out:
	return result;
}