xref: /freebsd/sys/dev/spibus/spigen.c (revision 1fcf4de055a112bca942e47dba2c33170cc63b9e)
1d67fe28bSAdrian Chadd /*-
2d67fe28bSAdrian Chadd  * Copyright (c) 2015 Brian Fundakowski Feldman.  All rights reserved.
3d67fe28bSAdrian Chadd  *
4d67fe28bSAdrian Chadd  * Redistribution and use in source and binary forms, with or without
5d67fe28bSAdrian Chadd  * modification, are permitted provided that the following conditions
6d67fe28bSAdrian Chadd  * are met:
7d67fe28bSAdrian Chadd  * 1. Redistributions of source code must retain the above copyright
8d67fe28bSAdrian Chadd  *    notice, this list of conditions and the following disclaimer.
9d67fe28bSAdrian Chadd  * 2. Redistributions in binary form must reproduce the above copyright
10d67fe28bSAdrian Chadd  *    notice, this list of conditions and the following disclaimer in the
11d67fe28bSAdrian Chadd  *    documentation and/or other materials provided with the distribution.
12d67fe28bSAdrian Chadd  *
13d67fe28bSAdrian Chadd  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14d67fe28bSAdrian Chadd  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15d67fe28bSAdrian Chadd  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16d67fe28bSAdrian Chadd  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17d67fe28bSAdrian Chadd  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18d67fe28bSAdrian Chadd  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19d67fe28bSAdrian Chadd  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20d67fe28bSAdrian Chadd  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21d67fe28bSAdrian Chadd  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22d67fe28bSAdrian Chadd  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23d67fe28bSAdrian Chadd  */
24d67fe28bSAdrian Chadd 
25d67fe28bSAdrian Chadd #include <sys/cdefs.h>
26d67fe28bSAdrian Chadd __FBSDID("$FreeBSD$");
27d67fe28bSAdrian Chadd 
28dac458e0SEmmanuel Vadot #include "opt_platform.h"
29*1fcf4de0SIan Lepore #include "opt_spi.h"
30dac458e0SEmmanuel Vadot 
31d67fe28bSAdrian Chadd #include <sys/param.h>
32d67fe28bSAdrian Chadd #include <sys/systm.h>
33d67fe28bSAdrian Chadd #include <sys/bus.h>
34d67fe28bSAdrian Chadd #include <sys/conf.h>
35d67fe28bSAdrian Chadd #include <sys/kernel.h>
36d67fe28bSAdrian Chadd #include <sys/lock.h>
37d67fe28bSAdrian Chadd #include <sys/malloc.h>
38d67fe28bSAdrian Chadd #include <sys/mman.h>
39d67fe28bSAdrian Chadd #include <sys/mutex.h>
40d67fe28bSAdrian Chadd #include <sys/module.h>
41d67fe28bSAdrian Chadd #include <sys/proc.h>
42d67fe28bSAdrian Chadd #include <sys/rwlock.h>
43d67fe28bSAdrian Chadd #include <sys/spigenio.h>
44d67fe28bSAdrian Chadd #include <sys/sysctl.h>
45d67fe28bSAdrian Chadd #include <sys/types.h>
46d67fe28bSAdrian Chadd 
47d67fe28bSAdrian Chadd #include <vm/vm.h>
48d67fe28bSAdrian Chadd #include <vm/vm_extern.h>
49d67fe28bSAdrian Chadd #include <vm/vm_object.h>
50d67fe28bSAdrian Chadd #include <vm/vm_page.h>
51d67fe28bSAdrian Chadd #include <vm/vm_pager.h>
52d67fe28bSAdrian Chadd 
53d67fe28bSAdrian Chadd #include <dev/spibus/spi.h>
54a0e911e0SIan Lepore #include <dev/spibus/spibusvar.h>
55d67fe28bSAdrian Chadd 
563b46d868SIan Lepore #ifdef FDT
573b46d868SIan Lepore #include <dev/ofw/ofw_bus_subr.h>
583b46d868SIan Lepore #endif
593b46d868SIan Lepore 
60d67fe28bSAdrian Chadd #include "spibus_if.h"
61d67fe28bSAdrian Chadd 
6250868fa6SOleksandr Tymoshenko #define	SPIGEN_OPEN		(1 << 0)
6350868fa6SOleksandr Tymoshenko #define	SPIGEN_MMAP_BUSY	(1 << 1)
6450868fa6SOleksandr Tymoshenko 
65d67fe28bSAdrian Chadd struct spigen_softc {
66d67fe28bSAdrian Chadd 	device_t sc_dev;
67d67fe28bSAdrian Chadd 	struct cdev *sc_cdev;
68*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME
69*1fcf4de0SIan Lepore 	struct cdev *sc_adev;           /* alias device */
70*1fcf4de0SIan Lepore #endif
71d67fe28bSAdrian Chadd 	struct mtx sc_mtx;
72d67fe28bSAdrian Chadd 	uint32_t sc_command_length_max; /* cannot change while mmapped */
73d67fe28bSAdrian Chadd 	uint32_t sc_data_length_max;    /* cannot change while mmapped */
74d67fe28bSAdrian Chadd 	vm_object_t sc_mmap_buffer;     /* command, then data */
75d67fe28bSAdrian Chadd 	vm_offset_t sc_mmap_kvaddr;
76d67fe28bSAdrian Chadd 	size_t sc_mmap_buffer_size;
77d67fe28bSAdrian Chadd 	int sc_debug;
7850868fa6SOleksandr Tymoshenko 	int sc_flags;
79d67fe28bSAdrian Chadd };
80d67fe28bSAdrian Chadd 
81d67fe28bSAdrian Chadd static int
82d67fe28bSAdrian Chadd spigen_probe(device_t dev)
83d67fe28bSAdrian Chadd {
843b46d868SIan Lepore 	int rv;
8532d74127SOleksandr Tymoshenko 
86bc7b44aeSIan Lepore 	/*
87bc7b44aeSIan Lepore 	 * By default we only bid to attach if specifically added by our parent
88bc7b44aeSIan Lepore 	 * (usually via hint.spigen.#.at=busname).  On FDT systems we bid as the
89bc7b44aeSIan Lepore 	 * default driver based on being configured in the FDT data.
90bc7b44aeSIan Lepore 	 */
913b46d868SIan Lepore 	rv = BUS_PROBE_NOWILDCARD;
92bc7b44aeSIan Lepore 
93bc7b44aeSIan Lepore #ifdef FDT
94bc7b44aeSIan Lepore 	if (ofw_bus_status_okay(dev) &&
95bc7b44aeSIan Lepore 	    ofw_bus_is_compatible(dev, "freebsd,spigen"))
96bc7b44aeSIan Lepore                 rv = BUS_PROBE_DEFAULT;
973b46d868SIan Lepore #endif
98bc7b44aeSIan Lepore 
99d67fe28bSAdrian Chadd 	device_set_desc(dev, "SPI Generic IO");
10032d74127SOleksandr Tymoshenko 
1013b46d868SIan Lepore 	return (rv);
102d67fe28bSAdrian Chadd }
103d67fe28bSAdrian Chadd 
104d67fe28bSAdrian Chadd static int spigen_open(struct cdev *, int, int, struct thread *);
105d67fe28bSAdrian Chadd static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
106d67fe28bSAdrian Chadd static int spigen_close(struct cdev *, int, int, struct thread *);
107d67fe28bSAdrian Chadd static d_mmap_single_t spigen_mmap_single;
108d67fe28bSAdrian Chadd 
109d67fe28bSAdrian Chadd static struct cdevsw spigen_cdevsw = {
110d67fe28bSAdrian Chadd 	.d_version =     D_VERSION,
111d67fe28bSAdrian Chadd 	.d_name =        "spigen",
112d67fe28bSAdrian Chadd 	.d_open =        spigen_open,
113d67fe28bSAdrian Chadd 	.d_ioctl =       spigen_ioctl,
114d67fe28bSAdrian Chadd 	.d_mmap_single = spigen_mmap_single,
115d67fe28bSAdrian Chadd 	.d_close =       spigen_close
116d67fe28bSAdrian Chadd };
117d67fe28bSAdrian Chadd 
118d67fe28bSAdrian Chadd static int
119d67fe28bSAdrian Chadd spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
120d67fe28bSAdrian Chadd {
121d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
122d67fe28bSAdrian Chadd 	uint32_t command_length_max;
123d67fe28bSAdrian Chadd 	int error;
124d67fe28bSAdrian Chadd 
125d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
126d67fe28bSAdrian Chadd 	command_length_max = sc->sc_command_length_max;
127d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
128d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &command_length_max,
129d67fe28bSAdrian Chadd 	    sizeof(command_length_max), req);
130d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
131d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
132d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
133d67fe28bSAdrian Chadd 			error = EBUSY;
134d67fe28bSAdrian Chadd 		else
135d67fe28bSAdrian Chadd 			sc->sc_command_length_max = command_length_max;
136d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
137d67fe28bSAdrian Chadd 	}
138d67fe28bSAdrian Chadd 	return (error);
139d67fe28bSAdrian Chadd }
140d67fe28bSAdrian Chadd 
141d67fe28bSAdrian Chadd static int
142d67fe28bSAdrian Chadd spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
143d67fe28bSAdrian Chadd {
144d67fe28bSAdrian Chadd 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
145d67fe28bSAdrian Chadd 	uint32_t data_length_max;
146d67fe28bSAdrian Chadd 	int error;
147d67fe28bSAdrian Chadd 
148d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
149d67fe28bSAdrian Chadd 	data_length_max = sc->sc_data_length_max;
150d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
151d67fe28bSAdrian Chadd 	error = sysctl_handle_int(oidp, &data_length_max,
152d67fe28bSAdrian Chadd 	    sizeof(data_length_max), req);
153d67fe28bSAdrian Chadd 	if (error == 0 && req->newptr != NULL) {
154d67fe28bSAdrian Chadd 		mtx_lock(&sc->sc_mtx);
155d67fe28bSAdrian Chadd 		if (sc->sc_mmap_buffer != NULL)
156d67fe28bSAdrian Chadd 			error = EBUSY;
157d67fe28bSAdrian Chadd 		else
158d67fe28bSAdrian Chadd 			sc->sc_data_length_max = data_length_max;
159d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
160d67fe28bSAdrian Chadd 	}
161d67fe28bSAdrian Chadd 	return (error);
162d67fe28bSAdrian Chadd }
163d67fe28bSAdrian Chadd 
164d67fe28bSAdrian Chadd static void
165d67fe28bSAdrian Chadd spigen_sysctl_init(struct spigen_softc *sc)
166d67fe28bSAdrian Chadd {
167d67fe28bSAdrian Chadd 	struct sysctl_ctx_list *ctx;
168d67fe28bSAdrian Chadd 	struct sysctl_oid *tree_node;
169d67fe28bSAdrian Chadd 	struct sysctl_oid_list *tree;
170d67fe28bSAdrian Chadd 
171d67fe28bSAdrian Chadd 	/*
172d67fe28bSAdrian Chadd 	 * Add system sysctl tree/handlers.
173d67fe28bSAdrian Chadd 	 */
174d67fe28bSAdrian Chadd 	ctx = device_get_sysctl_ctx(sc->sc_dev);
175d67fe28bSAdrian Chadd 	tree_node = device_get_sysctl_tree(sc->sc_dev);
176d67fe28bSAdrian Chadd 	tree = SYSCTL_CHILDREN(tree_node);
177d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
178d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
179d67fe28bSAdrian Chadd 	    spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
180d67fe28bSAdrian Chadd 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
181d67fe28bSAdrian Chadd 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
182d67fe28bSAdrian Chadd 	    spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
183d67fe28bSAdrian Chadd 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
184d67fe28bSAdrian Chadd 	    &sc->sc_debug, 0, "debug flags");
185d67fe28bSAdrian Chadd 
186d67fe28bSAdrian Chadd }
187d67fe28bSAdrian Chadd 
188d67fe28bSAdrian Chadd static int
189d67fe28bSAdrian Chadd spigen_attach(device_t dev)
190d67fe28bSAdrian Chadd {
191d67fe28bSAdrian Chadd 	struct spigen_softc *sc;
192d67fe28bSAdrian Chadd 	const int unit = device_get_unit(dev);
193*1fcf4de0SIan Lepore 	int cs, res;
194*1fcf4de0SIan Lepore 	struct make_dev_args mda;
195*1fcf4de0SIan Lepore 
196*1fcf4de0SIan Lepore 	spibus_get_cs(dev, &cs);
197*1fcf4de0SIan Lepore 	cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */
198d67fe28bSAdrian Chadd 
199d67fe28bSAdrian Chadd 	sc = device_get_softc(dev);
200d67fe28bSAdrian Chadd 	sc->sc_dev = dev;
201d67fe28bSAdrian Chadd 	sc->sc_command_length_max = PAGE_SIZE;
202d67fe28bSAdrian Chadd 	sc->sc_data_length_max = PAGE_SIZE;
203*1fcf4de0SIan Lepore 
204d67fe28bSAdrian Chadd 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
205*1fcf4de0SIan Lepore 
206*1fcf4de0SIan Lepore 	make_dev_args_init(&mda);
207*1fcf4de0SIan Lepore 	mda.mda_flags = MAKEDEV_WAITOK;
208*1fcf4de0SIan Lepore 	mda.mda_devsw = &spigen_cdevsw;
209*1fcf4de0SIan Lepore 	mda.mda_cr = NULL;
210*1fcf4de0SIan Lepore 	mda.mda_uid = UID_ROOT;
211*1fcf4de0SIan Lepore 	mda.mda_gid = GID_OPERATOR;
212*1fcf4de0SIan Lepore 	mda.mda_mode = 0660;
213*1fcf4de0SIan Lepore 	mda.mda_unit = unit;
214*1fcf4de0SIan Lepore 	mda.mda_si_drv1 = dev;
215*1fcf4de0SIan Lepore 
216*1fcf4de0SIan Lepore 	res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d",
217*1fcf4de0SIan Lepore 	    device_get_unit(device_get_parent(dev)), cs);
218*1fcf4de0SIan Lepore 	if (res) {
219*1fcf4de0SIan Lepore 		return res;
220*1fcf4de0SIan Lepore 	}
221*1fcf4de0SIan Lepore 
222*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME
223*1fcf4de0SIan Lepore 	res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit);
224*1fcf4de0SIan Lepore 	if (res) {
225*1fcf4de0SIan Lepore 		if (sc->sc_cdev) {
226*1fcf4de0SIan Lepore 			destroy_dev(sc->sc_cdev);
227*1fcf4de0SIan Lepore 			sc->sc_cdev = NULL;
228*1fcf4de0SIan Lepore 		}
229*1fcf4de0SIan Lepore 		return res;
230*1fcf4de0SIan Lepore 	}
231*1fcf4de0SIan Lepore #endif
232*1fcf4de0SIan Lepore 
233d67fe28bSAdrian Chadd 	spigen_sysctl_init(sc);
234d67fe28bSAdrian Chadd 
235d67fe28bSAdrian Chadd 	return (0);
236d67fe28bSAdrian Chadd }
237d67fe28bSAdrian Chadd 
238d67fe28bSAdrian Chadd static int
23950868fa6SOleksandr Tymoshenko spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
240d67fe28bSAdrian Chadd {
24150868fa6SOleksandr Tymoshenko 	int error;
24250868fa6SOleksandr Tymoshenko 	device_t dev;
24350868fa6SOleksandr Tymoshenko 	struct spigen_softc *sc;
244d67fe28bSAdrian Chadd 
24550868fa6SOleksandr Tymoshenko 	error = 0;
24650868fa6SOleksandr Tymoshenko 	dev = cdev->si_drv1;
24750868fa6SOleksandr Tymoshenko 	sc = device_get_softc(dev);
24850868fa6SOleksandr Tymoshenko 
24950868fa6SOleksandr Tymoshenko 	mtx_lock(&sc->sc_mtx);
25050868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_OPEN)
25150868fa6SOleksandr Tymoshenko 		error = EBUSY;
25250868fa6SOleksandr Tymoshenko 	else
25350868fa6SOleksandr Tymoshenko 		sc->sc_flags |= SPIGEN_OPEN;
25450868fa6SOleksandr Tymoshenko 	mtx_unlock(&sc->sc_mtx);
25550868fa6SOleksandr Tymoshenko 
25650868fa6SOleksandr Tymoshenko 	return (error);
257d67fe28bSAdrian Chadd }
258d67fe28bSAdrian Chadd 
259d67fe28bSAdrian Chadd static int
260d67fe28bSAdrian Chadd spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
261d67fe28bSAdrian Chadd {
262d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
263d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
264d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
265d67fe28bSAdrian Chadd 	int error = 0;
266d67fe28bSAdrian Chadd 
267d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
2683c43a826SOleksandr Tymoshenko 	if (st->st_command.iov_len == 0)
269d67fe28bSAdrian Chadd 		error = EINVAL;
270d67fe28bSAdrian Chadd 	else if (st->st_command.iov_len > sc->sc_command_length_max ||
271d67fe28bSAdrian Chadd 	    st->st_data.iov_len > sc->sc_data_length_max)
272d67fe28bSAdrian Chadd 		error = ENOMEM;
273d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
274d67fe28bSAdrian Chadd 	if (error)
275d67fe28bSAdrian Chadd 		return (error);
276d67fe28bSAdrian Chadd 
277d67fe28bSAdrian Chadd #if 0
278d67fe28bSAdrian Chadd 	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
279d67fe28bSAdrian Chadd 	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
280d67fe28bSAdrian Chadd #endif
281d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
282d67fe28bSAdrian Chadd 	    M_DEVBUF, M_WAITOK);
2833c43a826SOleksandr Tymoshenko 	if (st->st_data.iov_len > 0) {
284d67fe28bSAdrian Chadd 		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
285d67fe28bSAdrian Chadd 		    M_DEVBUF, M_WAITOK);
2863c43a826SOleksandr Tymoshenko 	}
2873c43a826SOleksandr Tymoshenko 	else
2883c43a826SOleksandr Tymoshenko 		transfer.tx_data = transfer.rx_data = NULL;
289d67fe28bSAdrian Chadd 
290d67fe28bSAdrian Chadd 	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
291d67fe28bSAdrian Chadd 	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
2923c43a826SOleksandr Tymoshenko 	if ((error == 0) && (st->st_data.iov_len > 0))
293d67fe28bSAdrian Chadd 		error = copyin(st->st_data.iov_base, transfer.tx_data,
294d67fe28bSAdrian Chadd 		    transfer.tx_data_sz = transfer.rx_data_sz =
295d67fe28bSAdrian Chadd 		                          st->st_data.iov_len);
296d67fe28bSAdrian Chadd 	if (error == 0)
297d67fe28bSAdrian Chadd 		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
298d67fe28bSAdrian Chadd 	if (error == 0) {
299d67fe28bSAdrian Chadd 		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
300d67fe28bSAdrian Chadd 		    transfer.rx_cmd_sz);
3013c43a826SOleksandr Tymoshenko 		if ((error == 0) && (st->st_data.iov_len > 0))
302d67fe28bSAdrian Chadd 			error = copyout(transfer.rx_data, st->st_data.iov_base,
303d67fe28bSAdrian Chadd 			    transfer.rx_data_sz);
304d67fe28bSAdrian Chadd 	}
305d67fe28bSAdrian Chadd 
306d67fe28bSAdrian Chadd 	free(transfer.tx_cmd, M_DEVBUF);
307d67fe28bSAdrian Chadd 	free(transfer.tx_data, M_DEVBUF);
308d67fe28bSAdrian Chadd 	return (error);
309d67fe28bSAdrian Chadd }
310d67fe28bSAdrian Chadd 
311d67fe28bSAdrian Chadd static int
312d67fe28bSAdrian Chadd spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
313d67fe28bSAdrian Chadd {
314d67fe28bSAdrian Chadd 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
315d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
316d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
317d67fe28bSAdrian Chadd 	int error = 0;
318d67fe28bSAdrian Chadd 
319d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
32050868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_MMAP_BUSY)
321d67fe28bSAdrian Chadd 		error = EBUSY;
322d67fe28bSAdrian Chadd 	else if (stm->stm_command_length > sc->sc_command_length_max ||
323d67fe28bSAdrian Chadd 	    stm->stm_data_length > sc->sc_data_length_max)
324d67fe28bSAdrian Chadd 		error = E2BIG;
325d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer == NULL)
326d67fe28bSAdrian Chadd 		error = EINVAL;
327d67fe28bSAdrian Chadd 	else if (sc->sc_mmap_buffer_size <
328d67fe28bSAdrian Chadd 	    stm->stm_command_length + stm->stm_data_length)
329d67fe28bSAdrian Chadd 		error = ENOMEM;
330d67fe28bSAdrian Chadd 	if (error == 0)
33150868fa6SOleksandr Tymoshenko 		sc->sc_flags |= SPIGEN_MMAP_BUSY;
332d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
333d67fe28bSAdrian Chadd 	if (error)
334d67fe28bSAdrian Chadd 		return (error);
335d67fe28bSAdrian Chadd 
336d67fe28bSAdrian Chadd 	transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
337d67fe28bSAdrian Chadd 	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
338d67fe28bSAdrian Chadd 	transfer.tx_data = transfer.rx_data =
339d67fe28bSAdrian Chadd 	    (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
340d67fe28bSAdrian Chadd 	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
341d67fe28bSAdrian Chadd 	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
342d67fe28bSAdrian Chadd 
343d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
34450868fa6SOleksandr Tymoshenko 	KASSERT((sc->sc_flags & SPIGEN_MMAP_BUSY), ("mmap no longer marked busy"));
34550868fa6SOleksandr Tymoshenko 	sc->sc_flags &= ~(SPIGEN_MMAP_BUSY);
346d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
347d67fe28bSAdrian Chadd 	return (error);
348d67fe28bSAdrian Chadd }
349d67fe28bSAdrian Chadd 
350d67fe28bSAdrian Chadd static int
351d67fe28bSAdrian Chadd spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
352d67fe28bSAdrian Chadd     struct thread *td)
353d67fe28bSAdrian Chadd {
354d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
355d67fe28bSAdrian Chadd 	int error;
356d67fe28bSAdrian Chadd 
357d67fe28bSAdrian Chadd 	switch (cmd) {
358d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER:
359d67fe28bSAdrian Chadd 		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
360d67fe28bSAdrian Chadd 		break;
361d67fe28bSAdrian Chadd 	case SPIGENIOC_TRANSFER_MMAPPED:
362d67fe28bSAdrian Chadd 		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
363d67fe28bSAdrian Chadd 		break;
364d67fe28bSAdrian Chadd 	case SPIGENIOC_GET_CLOCK_SPEED:
365197d784bSIan Lepore 		error = spibus_get_clock(dev, (uint32_t *)data);
366d67fe28bSAdrian Chadd 		break;
367d67fe28bSAdrian Chadd 	case SPIGENIOC_SET_CLOCK_SPEED:
368a0e911e0SIan Lepore 		error = spibus_set_clock(dev, *(uint32_t *)data);
369a0e911e0SIan Lepore 		break;
370a0e911e0SIan Lepore 	case SPIGENIOC_GET_SPI_MODE:
371197d784bSIan Lepore 		error = spibus_get_mode(dev, (uint32_t *)data);
372a0e911e0SIan Lepore 		break;
373a0e911e0SIan Lepore 	case SPIGENIOC_SET_SPI_MODE:
374a0e911e0SIan Lepore 		error = spibus_set_mode(dev, *(uint32_t *)data);
375d67fe28bSAdrian Chadd 		break;
376d67fe28bSAdrian Chadd 	default:
377a0e911e0SIan Lepore 		error = ENOTTY;
378a0e911e0SIan Lepore 		break;
379d67fe28bSAdrian Chadd 	}
380d67fe28bSAdrian Chadd 	return (error);
381d67fe28bSAdrian Chadd }
382d67fe28bSAdrian Chadd 
383d67fe28bSAdrian Chadd static int
384d67fe28bSAdrian Chadd spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
385d67fe28bSAdrian Chadd     vm_size_t size, struct vm_object **object, int nprot)
386d67fe28bSAdrian Chadd {
387d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
388d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
389d67fe28bSAdrian Chadd 	vm_page_t *m;
390d67fe28bSAdrian Chadd 	size_t n, pages;
391d67fe28bSAdrian Chadd 
392d67fe28bSAdrian Chadd 	if (size == 0 ||
393d67fe28bSAdrian Chadd 	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
394d67fe28bSAdrian Chadd 	    != (PROT_READ | PROT_WRITE))
395d67fe28bSAdrian Chadd 		return (EINVAL);
396d67fe28bSAdrian Chadd 	size = roundup2(size, PAGE_SIZE);
397d67fe28bSAdrian Chadd 	pages = size / PAGE_SIZE;
398d67fe28bSAdrian Chadd 
399d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
400d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
401d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
402d67fe28bSAdrian Chadd 		return (EBUSY);
403d67fe28bSAdrian Chadd 	} else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
404d67fe28bSAdrian Chadd 		mtx_unlock(&sc->sc_mtx);
405d67fe28bSAdrian Chadd 		return (E2BIG);
406d67fe28bSAdrian Chadd 	}
407d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer_size = size;
408d67fe28bSAdrian Chadd 	*offset = 0;
409d67fe28bSAdrian Chadd 	sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
410d67fe28bSAdrian Chadd 	    nprot, *offset, curthread->td_ucred);
411d67fe28bSAdrian Chadd 	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
412d67fe28bSAdrian Chadd 	VM_OBJECT_WLOCK(*object);
413d67fe28bSAdrian Chadd 	vm_object_reference_locked(*object); // kernel and userland both
414d67fe28bSAdrian Chadd 	for (n = 0; n < pages; n++) {
415d67fe28bSAdrian Chadd 		m[n] = vm_page_grab(*object, n,
416d67fe28bSAdrian Chadd 		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
417d67fe28bSAdrian Chadd 		m[n]->valid = VM_PAGE_BITS_ALL;
418d67fe28bSAdrian Chadd 	}
419d67fe28bSAdrian Chadd 	VM_OBJECT_WUNLOCK(*object);
420d67fe28bSAdrian Chadd 	sc->sc_mmap_kvaddr = kva_alloc(size);
421d67fe28bSAdrian Chadd 	pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
422d67fe28bSAdrian Chadd 	free(m, M_TEMP);
423d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
424d67fe28bSAdrian Chadd 
425d67fe28bSAdrian Chadd 	if (*object == NULL)
426d67fe28bSAdrian Chadd 		 return (EINVAL);
427d67fe28bSAdrian Chadd 	return (0);
428d67fe28bSAdrian Chadd }
429d67fe28bSAdrian Chadd 
430d67fe28bSAdrian Chadd static int
431d67fe28bSAdrian Chadd spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
432d67fe28bSAdrian Chadd {
433d67fe28bSAdrian Chadd 	device_t dev = cdev->si_drv1;
434d67fe28bSAdrian Chadd 	struct spigen_softc *sc = device_get_softc(dev);
435d67fe28bSAdrian Chadd 
436d67fe28bSAdrian Chadd 	mtx_lock(&sc->sc_mtx);
437d67fe28bSAdrian Chadd 	if (sc->sc_mmap_buffer != NULL) {
438d67fe28bSAdrian Chadd 		pmap_qremove(sc->sc_mmap_kvaddr,
439d67fe28bSAdrian Chadd 		    sc->sc_mmap_buffer_size / PAGE_SIZE);
440d67fe28bSAdrian Chadd 		kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
441d67fe28bSAdrian Chadd 		sc->sc_mmap_kvaddr = 0;
442d67fe28bSAdrian Chadd 		vm_object_deallocate(sc->sc_mmap_buffer);
443d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer = NULL;
444d67fe28bSAdrian Chadd 		sc->sc_mmap_buffer_size = 0;
445d67fe28bSAdrian Chadd 	}
44650868fa6SOleksandr Tymoshenko 	sc->sc_flags &= ~(SPIGEN_OPEN);
447d67fe28bSAdrian Chadd 	mtx_unlock(&sc->sc_mtx);
448d67fe28bSAdrian Chadd 	return (0);
449d67fe28bSAdrian Chadd }
450d67fe28bSAdrian Chadd 
451d67fe28bSAdrian Chadd static int
452d67fe28bSAdrian Chadd spigen_detach(device_t dev)
453d67fe28bSAdrian Chadd {
45450868fa6SOleksandr Tymoshenko 	struct spigen_softc *sc;
455d67fe28bSAdrian Chadd 
45650868fa6SOleksandr Tymoshenko 	sc = device_get_softc(dev);
45750868fa6SOleksandr Tymoshenko 
45850868fa6SOleksandr Tymoshenko 	mtx_lock(&sc->sc_mtx);
45950868fa6SOleksandr Tymoshenko 	if (sc->sc_flags & SPIGEN_OPEN) {
46050868fa6SOleksandr Tymoshenko 		mtx_unlock(&sc->sc_mtx);
46150868fa6SOleksandr Tymoshenko 		return (EBUSY);
46250868fa6SOleksandr Tymoshenko 	}
46350868fa6SOleksandr Tymoshenko 	mtx_unlock(&sc->sc_mtx);
46450868fa6SOleksandr Tymoshenko 
46550868fa6SOleksandr Tymoshenko 	mtx_destroy(&sc->sc_mtx);
46650868fa6SOleksandr Tymoshenko 
467*1fcf4de0SIan Lepore #ifdef SPIGEN_LEGACY_CDEVNAME
468*1fcf4de0SIan Lepore 	if (sc->sc_adev)
469*1fcf4de0SIan Lepore 		destroy_dev(sc->sc_adev);
470*1fcf4de0SIan Lepore #endif
471*1fcf4de0SIan Lepore 
47250868fa6SOleksandr Tymoshenko 	if (sc->sc_cdev)
47350868fa6SOleksandr Tymoshenko 		destroy_dev(sc->sc_cdev);
47450868fa6SOleksandr Tymoshenko 
47550868fa6SOleksandr Tymoshenko 	return (0);
476d67fe28bSAdrian Chadd }
477d67fe28bSAdrian Chadd 
478d67fe28bSAdrian Chadd static devclass_t spigen_devclass;
479d67fe28bSAdrian Chadd 
480d67fe28bSAdrian Chadd static device_method_t spigen_methods[] = {
481d67fe28bSAdrian Chadd 	/* Device interface */
482d67fe28bSAdrian Chadd 	DEVMETHOD(device_probe,		spigen_probe),
483d67fe28bSAdrian Chadd 	DEVMETHOD(device_attach,	spigen_attach),
484d67fe28bSAdrian Chadd 	DEVMETHOD(device_detach,	spigen_detach),
485d67fe28bSAdrian Chadd 
486d67fe28bSAdrian Chadd 	{ 0, 0 }
487d67fe28bSAdrian Chadd };
488d67fe28bSAdrian Chadd 
489d67fe28bSAdrian Chadd static driver_t spigen_driver = {
490d67fe28bSAdrian Chadd 	"spigen",
491d67fe28bSAdrian Chadd 	spigen_methods,
492d67fe28bSAdrian Chadd 	sizeof(struct spigen_softc),
493d67fe28bSAdrian Chadd };
494d67fe28bSAdrian Chadd 
495d67fe28bSAdrian Chadd DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
496cdfebb9cSIan Lepore MODULE_DEPEND(spigen, spibus, 1, 1, 1);
497