1300713Sadrian/*-
2300713Sadrian * Copyright (c) 2015 Brian Fundakowski Feldman.  All rights reserved.
3300713Sadrian *
4300713Sadrian * Redistribution and use in source and binary forms, with or without
5300713Sadrian * modification, are permitted provided that the following conditions
6300713Sadrian * are met:
7300713Sadrian * 1. Redistributions of source code must retain the above copyright
8300713Sadrian *    notice, this list of conditions and the following disclaimer.
9300713Sadrian * 2. Redistributions in binary form must reproduce the above copyright
10300713Sadrian *    notice, this list of conditions and the following disclaimer in the
11300713Sadrian *    documentation and/or other materials provided with the distribution.
12300713Sadrian *
13300713Sadrian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14300713Sadrian * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15300713Sadrian * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16300713Sadrian * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17300713Sadrian * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18300713Sadrian * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19300713Sadrian * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20300713Sadrian * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21300713Sadrian * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22300713Sadrian * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23300713Sadrian */
24300713Sadrian
25300713Sadrian#include <sys/cdefs.h>
26300713Sadrian__FBSDID("$FreeBSD: stable/11/sys/dev/spibus/spigen.c 346547 2019-04-22 13:45:08Z ian $");
27300713Sadrian
28329272Sgonzo#include "opt_platform.h"
29346517Sian#include "opt_spi.h"
30329272Sgonzo
31300713Sadrian#include <sys/param.h>
32300713Sadrian#include <sys/systm.h>
33300713Sadrian#include <sys/bus.h>
34300713Sadrian#include <sys/conf.h>
35300713Sadrian#include <sys/kernel.h>
36300713Sadrian#include <sys/lock.h>
37300713Sadrian#include <sys/malloc.h>
38300713Sadrian#include <sys/mman.h>
39300713Sadrian#include <sys/mutex.h>
40300713Sadrian#include <sys/module.h>
41300713Sadrian#include <sys/proc.h>
42300713Sadrian#include <sys/rwlock.h>
43300713Sadrian#include <sys/spigenio.h>
44300713Sadrian#include <sys/types.h>
45300713Sadrian
46300713Sadrian#include <vm/vm.h>
47300713Sadrian#include <vm/vm_extern.h>
48300713Sadrian#include <vm/vm_object.h>
49300713Sadrian#include <vm/vm_page.h>
50300713Sadrian#include <vm/vm_pager.h>
51300713Sadrian
52300713Sadrian#include <dev/spibus/spi.h>
53332942Sian#include <dev/spibus/spibusvar.h>
54300713Sadrian
55332942Sian#ifdef FDT
56332942Sian#include <dev/ofw/ofw_bus_subr.h>
57346547Sian
58346547Sianstatic struct ofw_compat_data compat_data[] = {
59346547Sian	{"freebsd,spigen", true},
60346547Sian	{NULL,             false}
61346547Sian};
62346547Sian
63332942Sian#endif
64332942Sian
65300713Sadrian#include "spibus_if.h"
66300713Sadrian
67300713Sadrianstruct spigen_softc {
68300713Sadrian	device_t sc_dev;
69300713Sadrian	struct cdev *sc_cdev;
70346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME
71346517Sian	struct cdev *sc_adev;           /* alias device */
72346517Sian#endif
73300713Sadrian	struct mtx sc_mtx;
74300713Sadrian};
75300713Sadrian
76346547Sianstruct spigen_mmap {
77346547Sian	vm_object_t bufobj;
78346547Sian	vm_offset_t kvaddr;
79346547Sian	size_t      bufsize;
80346547Sian};
81346547Sian
82300713Sadrianstatic int
83300713Sadrianspigen_probe(device_t dev)
84300713Sadrian{
85332942Sian	int rv;
86311342Sgonzo
87332942Sian	/*
88332942Sian	 * By default we only bid to attach if specifically added by our parent
89332942Sian	 * (usually via hint.spigen.#.at=busname).  On FDT systems we bid as the
90332942Sian	 * default driver based on being configured in the FDT data.
91332942Sian	 */
92332942Sian	rv = BUS_PROBE_NOWILDCARD;
93332942Sian
94332942Sian#ifdef FDT
95332942Sian	if (ofw_bus_status_okay(dev) &&
96346547Sian	    ofw_bus_search_compatible(dev, compat_data)->ocd_data)
97332942Sian                rv = BUS_PROBE_DEFAULT;
98332942Sian#endif
99332942Sian
100300713Sadrian	device_set_desc(dev, "SPI Generic IO");
101311342Sgonzo
102332942Sian	return (rv);
103300713Sadrian}
104300713Sadrian
105300713Sadrianstatic int spigen_open(struct cdev *, int, int, struct thread *);
106300713Sadrianstatic int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
107300713Sadrianstatic int spigen_close(struct cdev *, int, int, struct thread *);
108300713Sadrianstatic d_mmap_single_t spigen_mmap_single;
109300713Sadrian
110300713Sadrianstatic struct cdevsw spigen_cdevsw = {
111300713Sadrian	.d_version =     D_VERSION,
112300713Sadrian	.d_name =        "spigen",
113300713Sadrian	.d_open =        spigen_open,
114300713Sadrian	.d_ioctl =       spigen_ioctl,
115300713Sadrian	.d_mmap_single = spigen_mmap_single,
116300713Sadrian	.d_close =       spigen_close
117300713Sadrian};
118300713Sadrian
119300713Sadrianstatic int
120300713Sadrianspigen_attach(device_t dev)
121300713Sadrian{
122300713Sadrian	struct spigen_softc *sc;
123300713Sadrian	const int unit = device_get_unit(dev);
124346517Sian	int cs, res;
125346517Sian	struct make_dev_args mda;
126300713Sadrian
127346517Sian	spibus_get_cs(dev, &cs);
128346517Sian	cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */
129346517Sian
130300713Sadrian	sc = device_get_softc(dev);
131300713Sadrian	sc->sc_dev = dev;
132346517Sian
133300713Sadrian	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
134346517Sian
135346517Sian	make_dev_args_init(&mda);
136346517Sian	mda.mda_flags = MAKEDEV_WAITOK;
137346517Sian	mda.mda_devsw = &spigen_cdevsw;
138346517Sian	mda.mda_cr = NULL;
139346517Sian	mda.mda_uid = UID_ROOT;
140346517Sian	mda.mda_gid = GID_OPERATOR;
141346517Sian	mda.mda_mode = 0660;
142346517Sian	mda.mda_unit = unit;
143346517Sian	mda.mda_si_drv1 = dev;
144346517Sian
145346517Sian	res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d",
146346517Sian	    device_get_unit(device_get_parent(dev)), cs);
147346517Sian	if (res) {
148346517Sian		return res;
149346517Sian	}
150346517Sian
151346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME
152346517Sian	res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit);
153346517Sian	if (res) {
154346517Sian		if (sc->sc_cdev) {
155346517Sian			destroy_dev(sc->sc_cdev);
156346517Sian			sc->sc_cdev = NULL;
157346517Sian		}
158346517Sian		return res;
159346517Sian	}
160346517Sian#endif
161346517Sian
162300713Sadrian	return (0);
163300713Sadrian}
164300713Sadrian
165300713Sadrianstatic int
166329278Sgonzospigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
167300713Sadrian{
168329278Sgonzo	device_t dev;
169329278Sgonzo	struct spigen_softc *sc;
170300713Sadrian
171329278Sgonzo	dev = cdev->si_drv1;
172329278Sgonzo	sc = device_get_softc(dev);
173329278Sgonzo
174329278Sgonzo	mtx_lock(&sc->sc_mtx);
175346547Sian	device_busy(sc->sc_dev);
176329278Sgonzo	mtx_unlock(&sc->sc_mtx);
177329278Sgonzo
178346547Sian	return (0);
179300713Sadrian}
180300713Sadrian
181300713Sadrianstatic int
182300713Sadrianspigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
183300713Sadrian{
184300713Sadrian	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
185300713Sadrian	device_t dev = cdev->si_drv1;
186300713Sadrian	int error = 0;
187300713Sadrian
188300713Sadrian#if 0
189300713Sadrian	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
190300713Sadrian	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
191300713Sadrian#endif
192346547Sian
193346547Sian	if (st->st_command.iov_len == 0)
194346547Sian		return (EINVAL);
195346547Sian
196300713Sadrian	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
197300713Sadrian	    M_DEVBUF, M_WAITOK);
198311342Sgonzo	if (st->st_data.iov_len > 0) {
199311342Sgonzo		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
200311342Sgonzo		    M_DEVBUF, M_WAITOK);
201300713Sadrian	}
202311342Sgonzo	else
203311342Sgonzo		transfer.tx_data = transfer.rx_data = NULL;
204300713Sadrian
205300713Sadrian	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
206300713Sadrian	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
207311342Sgonzo	if ((error == 0) && (st->st_data.iov_len > 0))
208300713Sadrian		error = copyin(st->st_data.iov_base, transfer.tx_data,
209300713Sadrian		    transfer.tx_data_sz = transfer.rx_data_sz =
210300713Sadrian		                          st->st_data.iov_len);
211300713Sadrian	if (error == 0)
212300713Sadrian		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
213300713Sadrian	if (error == 0) {
214300713Sadrian		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
215300713Sadrian		    transfer.rx_cmd_sz);
216311342Sgonzo		if ((error == 0) && (st->st_data.iov_len > 0))
217300713Sadrian			error = copyout(transfer.rx_data, st->st_data.iov_base,
218300713Sadrian			    transfer.rx_data_sz);
219300713Sadrian	}
220300713Sadrian
221300713Sadrian	free(transfer.tx_cmd, M_DEVBUF);
222300713Sadrian	free(transfer.tx_data, M_DEVBUF);
223300713Sadrian	return (error);
224300713Sadrian}
225300713Sadrian
226300713Sadrianstatic int
227300713Sadrianspigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
228300713Sadrian{
229300713Sadrian	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
230300713Sadrian	device_t dev = cdev->si_drv1;
231346547Sian	struct spigen_mmap *mmap;
232346547Sian	int error;
233300713Sadrian
234346547Sian	if ((error = devfs_get_cdevpriv((void **)&mmap)) != 0)
235300713Sadrian		return (error);
236346547Sian
237346547Sian	if (mmap->bufsize < stm->stm_command_length + stm->stm_data_length)
238346547Sian		return (E2BIG);
239346547Sian
240346547Sian	transfer.tx_cmd = transfer.rx_cmd = (void *)((uintptr_t)mmap->kvaddr);
241300713Sadrian	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
242300713Sadrian	transfer.tx_data = transfer.rx_data =
243346547Sian	    (void *)((uintptr_t)mmap->kvaddr + stm->stm_command_length);
244300713Sadrian	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
245300713Sadrian	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
246300713Sadrian
247300713Sadrian	return (error);
248300713Sadrian}
249300713Sadrian
250300713Sadrianstatic int
251300713Sadrianspigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
252300713Sadrian    struct thread *td)
253300713Sadrian{
254300713Sadrian	device_t dev = cdev->si_drv1;
255300713Sadrian	int error;
256300713Sadrian
257300713Sadrian	switch (cmd) {
258300713Sadrian	case SPIGENIOC_TRANSFER:
259300713Sadrian		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
260300713Sadrian		break;
261300713Sadrian	case SPIGENIOC_TRANSFER_MMAPPED:
262300713Sadrian		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
263300713Sadrian		break;
264300713Sadrian	case SPIGENIOC_GET_CLOCK_SPEED:
265332942Sian		error = spibus_get_clock(dev, (uint32_t *)data);
266300713Sadrian		break;
267300713Sadrian	case SPIGENIOC_SET_CLOCK_SPEED:
268332942Sian		error = spibus_set_clock(dev, *(uint32_t *)data);
269300713Sadrian		break;
270332942Sian	case SPIGENIOC_GET_SPI_MODE:
271332942Sian		error = spibus_get_mode(dev, (uint32_t *)data);
272332942Sian		break;
273332942Sian	case SPIGENIOC_SET_SPI_MODE:
274332942Sian		error = spibus_set_mode(dev, *(uint32_t *)data);
275332942Sian		break;
276300713Sadrian	default:
277332942Sian		error = ENOTTY;
278332942Sian		break;
279300713Sadrian	}
280300713Sadrian	return (error);
281300713Sadrian}
282300713Sadrian
283346547Sianstatic void
284346547Sianspigen_mmap_cleanup(void *arg)
285346547Sian{
286346547Sian	struct spigen_mmap *mmap = arg;
287346547Sian
288346547Sian	if (mmap->kvaddr != 0)
289346547Sian		pmap_qremove(mmap->kvaddr, mmap->bufsize / PAGE_SIZE);
290346547Sian	if (mmap->bufobj != NULL)
291346547Sian		vm_object_deallocate(mmap->bufobj);
292346547Sian	free(mmap, M_DEVBUF);
293346547Sian}
294346547Sian
295300713Sadrianstatic int
296300713Sadrianspigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
297300713Sadrian    vm_size_t size, struct vm_object **object, int nprot)
298300713Sadrian{
299346547Sian	struct spigen_mmap *mmap;
300300713Sadrian	vm_page_t *m;
301300713Sadrian	size_t n, pages;
302346547Sian	int error;
303300713Sadrian
304300713Sadrian	if (size == 0 ||
305300713Sadrian	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
306300713Sadrian	    != (PROT_READ | PROT_WRITE))
307300713Sadrian		return (EINVAL);
308300713Sadrian	size = roundup2(size, PAGE_SIZE);
309300713Sadrian	pages = size / PAGE_SIZE;
310300713Sadrian
311346547Sian	if (devfs_get_cdevpriv((void **)&mmap) == 0)
312300713Sadrian		return (EBUSY);
313346547Sian
314346547Sian	mmap = malloc(sizeof(*mmap), M_DEVBUF, M_ZERO | M_WAITOK);
315346547Sian	if ((mmap->kvaddr = kva_alloc(size)) == 0) {
316346547Sian		spigen_mmap_cleanup(mmap);
317346547Sian		return (ENOMEM);
318300713Sadrian	}
319346547Sian	mmap->bufsize = size;
320346547Sian	mmap->bufobj = vm_pager_allocate(OBJT_PHYS, 0, size, nprot, 0,
321346547Sian	    curthread->td_ucred);
322346547Sian
323300713Sadrian	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
324346547Sian	VM_OBJECT_WLOCK(mmap->bufobj);
325346547Sian	vm_object_reference_locked(mmap->bufobj); // kernel and userland both
326300713Sadrian	for (n = 0; n < pages; n++) {
327346547Sian		m[n] = vm_page_grab(mmap->bufobj, n,
328300713Sadrian		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
329300713Sadrian		m[n]->valid = VM_PAGE_BITS_ALL;
330300713Sadrian	}
331346547Sian	VM_OBJECT_WUNLOCK(mmap->bufobj);
332346547Sian	pmap_qenter(mmap->kvaddr, m, pages);
333300713Sadrian	free(m, M_TEMP);
334300713Sadrian
335346547Sian	if ((error = devfs_set_cdevpriv(mmap, spigen_mmap_cleanup)) != 0) {
336346547Sian		/* Two threads were racing through this code; we lost. */
337346547Sian		spigen_mmap_cleanup(mmap);
338346547Sian		return (error);
339346547Sian	}
340346547Sian	*offset = 0;
341346547Sian	*object = mmap->bufobj;
342346547Sian
343300713Sadrian	return (0);
344300713Sadrian}
345300713Sadrian
346300713Sadrianstatic int
347300713Sadrianspigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
348300713Sadrian{
349300713Sadrian	device_t dev = cdev->si_drv1;
350300713Sadrian	struct spigen_softc *sc = device_get_softc(dev);
351300713Sadrian
352300713Sadrian	mtx_lock(&sc->sc_mtx);
353346547Sian	device_unbusy(sc->sc_dev);
354300713Sadrian	mtx_unlock(&sc->sc_mtx);
355300713Sadrian	return (0);
356300713Sadrian}
357300713Sadrian
358300713Sadrianstatic int
359300713Sadrianspigen_detach(device_t dev)
360300713Sadrian{
361329278Sgonzo	struct spigen_softc *sc;
362300713Sadrian
363329278Sgonzo	sc = device_get_softc(dev);
364329278Sgonzo
365346517Sian#ifdef SPIGEN_LEGACY_CDEVNAME
366346517Sian	if (sc->sc_adev)
367346517Sian		destroy_dev(sc->sc_adev);
368346517Sian#endif
369346517Sian
370346517Sian	if (sc->sc_cdev)
371346517Sian		destroy_dev(sc->sc_cdev);
372329278Sgonzo
373346547Sian	mtx_destroy(&sc->sc_mtx);
374346547Sian
375329278Sgonzo	return (0);
376300713Sadrian}
377300713Sadrian
378300713Sadrianstatic devclass_t spigen_devclass;
379300713Sadrian
380300713Sadrianstatic device_method_t spigen_methods[] = {
381300713Sadrian	/* Device interface */
382300713Sadrian	DEVMETHOD(device_probe,		spigen_probe),
383300713Sadrian	DEVMETHOD(device_attach,	spigen_attach),
384300713Sadrian	DEVMETHOD(device_detach,	spigen_detach),
385300713Sadrian
386300713Sadrian	{ 0, 0 }
387300713Sadrian};
388300713Sadrian
389300713Sadrianstatic driver_t spigen_driver = {
390300713Sadrian	"spigen",
391300713Sadrian	spigen_methods,
392300713Sadrian	sizeof(struct spigen_softc),
393300713Sadrian};
394300713Sadrian
395300713SadrianDRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
396332942SianMODULE_DEPEND(spigen, spibus, 1, 1, 1);
397346547Sian#ifdef FDT
398346547SianSIMPLEBUS_PNP_INFO(compat_data);
399346547Sian#endif
400