summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/rt5677-spi.c
blob: 3505aafbade49d16d9ba4a3da17c14f03c897577 (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
/*
 * rt5677-spi.c  --  RT5677 ALSA SoC audio codec driver
 *
 * Copyright 2013 Realtek Semiconductor Corp.
 * Author: Oder Chiou <oder_chiou@realtek.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.
 */

#include <linux/module.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_qos.h>
#include <linux/sysfs.h>
#include <linux/clk.h>
#include <linux/firmware.h>

#include "rt5677-spi.h"

#define RT5677_SPI_BURST_LEN	240
#define RT5677_SPI_HEADER	5
#define RT5677_SPI_FREQ		6000000

/* The AddressPhase and DataPhase of SPI commands are MSB first on the wire.
 * DataPhase word size of 16-bit commands is 2 bytes.
 * DataPhase word size of 32-bit commands is 4 bytes.
 * DataPhase word size of burst commands is 8 bytes.
 * The DSP CPU is little-endian.
 */
#define RT5677_SPI_WRITE_BURST	0x5
#define RT5677_SPI_READ_BURST	0x4
#define RT5677_SPI_WRITE_32	0x3
#define RT5677_SPI_READ_32	0x2
#define RT5677_SPI_WRITE_16	0x1
#define RT5677_SPI_READ_16	0x0

static struct spi_device *g_spi;
static DEFINE_MUTEX(spi_mutex);

/* Select a suitable transfer command for the next transfer to ensure
 * the transfer address is always naturally aligned while minimizing
 * the total number of transfers required.
 *
 * 3 transfer commands are available:
 * RT5677_SPI_READ/WRITE_16:	Transfer 2 bytes
 * RT5677_SPI_READ/WRITE_32:	Transfer 4 bytes
 * RT5677_SPI_READ/WRITE_BURST:	Transfer any multiples of 8 bytes
 *
 * For example, reading 260 bytes at 0x60030002 uses the following commands:
 * 0x60030002 RT5677_SPI_READ_16	2 bytes
 * 0x60030004 RT5677_SPI_READ_32	4 bytes
 * 0x60030008 RT5677_SPI_READ_BURST	240 bytes
 * 0x600300F8 RT5677_SPI_READ_BURST	8 bytes
 * 0x60030100 RT5677_SPI_READ_32	4 bytes
 * 0x60030104 RT5677_SPI_READ_16	2 bytes
 *
 * Input:
 * @read: true for read commands; false for write commands
 * @align: alignment of the next transfer address
 * @remain: number of bytes remaining to transfer
 *
 * Output:
 * @len: number of bytes to transfer with the selected command
 * Returns the selected command
 */
static u8 rt5677_spi_select_cmd(bool read, u32 align, u32 remain, u32 *len)
{
	u8 cmd;

	if (align == 2 || align == 6 || remain == 2) {
		cmd = RT5677_SPI_READ_16;
		*len = 2;
	} else if (align == 4 || remain <= 6) {
		cmd = RT5677_SPI_READ_32;
		*len = 4;
	} else {
		cmd = RT5677_SPI_READ_BURST;
		*len = min_t(u32, remain & ~7, RT5677_SPI_BURST_LEN);
	}
	return read ? cmd : cmd + 1;
}

/* Copy dstlen bytes from src to dst, while reversing byte order for each word.
 * If srclen < dstlen, zeros are padded.
 */
static void rt5677_spi_reverse(u8 *dst, u32 dstlen, const u8 *src, u32 srclen)
{
	u32 w, i, si;
	u32 word_size = min_t(u32, dstlen, 8);

	for (w = 0; w < dstlen; w += word_size) {
		for (i = 0; i < word_size; i++) {
			si = w + word_size - i - 1;
			dst[w + i] = si < srclen ? src[si] : 0;
		}
	}
}

/* Read DSP address space using SPI. addr and len have to be 2-byte aligned. */
int rt5677_spi_read(u32 addr, void *rxbuf, size_t len)
{
	u32 offset;
	int status = 0;
	struct spi_transfer t[2];
	struct spi_message m;
	/* +4 bytes is for the DummyPhase following the AddressPhase */
	u8 header[RT5677_SPI_HEADER + 4];
	u8 body[RT5677_SPI_BURST_LEN];
	u8 spi_cmd;
	u8 *cb = rxbuf;

	if (!g_spi)
		return -ENODEV;

	if ((addr & 1) || (len & 1)) {
		dev_err(&g_spi->dev, "Bad read align 0x%x(%zu)\n", addr, len);
		return -EACCES;
	}

	memset(t, 0, sizeof(t));
	t[0].tx_buf = header;
	t[0].len = sizeof(header);
	t[0].speed_hz = RT5677_SPI_FREQ;
	t[1].rx_buf = body;
	t[1].speed_hz = RT5677_SPI_FREQ;
	spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t));

	for (offset = 0; offset < len; offset += t[1].len) {
		spi_cmd = rt5677_spi_select_cmd(true, (addr + offset) & 7,
				len - offset, &t[1].len);

		/* Construct SPI message header */
		header[0] = spi_cmd;
		header[1] = ((addr + offset) & 0xff000000) >> 24;
		header[2] = ((addr + offset) & 0x00ff0000) >> 16;
		header[3] = ((addr + offset) & 0x0000ff00) >> 8;
		header[4] = ((addr + offset) & 0x000000ff) >> 0;

		mutex_lock(&spi_mutex);
		status |= spi_sync(g_spi, &m);
		mutex_unlock(&spi_mutex);

		/* Copy data back to caller buffer */
		rt5677_spi_reverse(cb + offset, t[1].len, body, t[1].len);
	}
	return status;
}
EXPORT_SYMBOL_GPL(rt5677_spi_read);

/* Write DSP address space using SPI. addr has to be 2-byte aligned.
 * If len is not 2-byte aligned, an extra byte of zero is written at the end
 * as padding.
 */
int rt5677_spi_write(u32 addr, const void *txbuf, size_t len)
{
	u32 offset, len_with_pad = len;
	int status = 0;
	struct spi_transfer t;
	struct spi_message m;
	/* +1 byte is for the DummyPhase following the DataPhase */
	u8 buf[RT5677_SPI_HEADER + RT5677_SPI_BURST_LEN + 1];
	u8 *body = buf + RT5677_SPI_HEADER;
	u8 spi_cmd;
	const u8 *cb = txbuf;

	if (!g_spi)
		return -ENODEV;

	if (addr & 1) {
		dev_err(&g_spi->dev, "Bad write align 0x%x(%zu)\n", addr, len);
		return -EACCES;
	}

	if (len & 1)
		len_with_pad = len + 1;

	memset(&t, 0, sizeof(t));
	t.tx_buf = buf;
	t.speed_hz = RT5677_SPI_FREQ;
	spi_message_init_with_transfers(&m, &t, 1);

	for (offset = 0; offset < len_with_pad;) {
		spi_cmd = rt5677_spi_select_cmd(false, (addr + offset) & 7,
				len_with_pad - offset, &t.len);

		/* Construct SPI message header */
		buf[0] = spi_cmd;
		buf[1] = ((addr + offset) & 0xff000000) >> 24;
		buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
		buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
		buf[4] = ((addr + offset) & 0x000000ff) >> 0;

		/* Fetch data from caller buffer */
		rt5677_spi_reverse(body, t.len, cb + offset, len - offset);
		offset += t.len;
		t.len += RT5677_SPI_HEADER + 1;

		mutex_lock(&spi_mutex);
		status |= spi_sync(g_spi, &m);
		mutex_unlock(&spi_mutex);
	}
	return status;
}
EXPORT_SYMBOL_GPL(rt5677_spi_write);

int rt5677_spi_write_firmware(u32 addr, const struct firmware *fw)
{
	return rt5677_spi_write(addr, fw->data, fw->size);
}
EXPORT_SYMBOL_GPL(rt5677_spi_write_firmware);

static int rt5677_spi_probe(struct spi_device *spi)
{
	g_spi = spi;
	return 0;
}

static struct spi_driver rt5677_spi_driver = {
	.driver = {
		.name = "rt5677",
		.owner = THIS_MODULE,
	},
	.probe = rt5677_spi_probe,
};
module_spi_driver(rt5677_spi_driver);

MODULE_DESCRIPTION("ASoC RT5677 SPI driver");
MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>");
MODULE_LICENSE("GPL v2");
OpenPOWER on IntegriCloud