#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mlcd.c,v 1.18 2014/07/25 08:10:32 dholland Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/conf.h>
#include <dreamcast/dev/maple/maple.h>
#include <dreamcast/dev/maple/mapleconf.h>
#include "ioconf.h"
#define MLCD_MAXACCSIZE 1012
struct mlcd_funcdef {
unsigned unused : 6,
bw : 1,
hv : 1,
ra : 4,
wa : 4,
bb : 8,
pt : 8;
};
struct mlcd_request_write_data {
uint32_t func_code;
uint8_t pt;
uint8_t phase;
uint16_t block;
uint8_t data[MLCD_MAXACCSIZE];
};
#define MLCD_SIZE_REQW(sc) ((sc)->sc_waccsz + 8)
struct mlcd_request_get_media_info {
uint32_t func_code;
uint32_t pt;
};
struct mlcd_media_info {
uint8_t width;
uint8_t height;
uint8_t rsvd[2];
};
struct mlcd_response_media_info {
uint32_t func_code;
struct mlcd_media_info info;
};
struct mlcd_buf {
SIMPLEQ_ENTRY(mlcd_buf) lb_q;
int lb_error;
int lb_partno;
int lb_blkno;
uint32_t lb_data[1];
};
#define MLCD_BUF_SZ(sc) (offsetof(struct mlcd_buf, lb_data) + (sc)->sc_bsize)
struct mlcd_softc {
device_t sc_dev;
device_t sc_parent;
struct maple_unit *sc_unit;
int sc_direction;
enum mlcd_stat {
MLCD_INIT,
MLCD_INIT2,
MLCD_IDLE,
MLCD_WRITE,
MLCD_DETACH
} sc_stat;
int sc_npt;
int sc_bsize;
int sc_wacc;
int sc_waccsz;
struct mlcd_pt {
int pt_flags;
#define MLCD_PT_OK 1
#define MLCD_PT_OPEN 2
struct mlcd_media_info pt_info;
int pt_size;
int pt_nblk;
char pt_name[16 + 4 ];
} *sc_pt;
union {
struct mlcd_request_write_data req_write;
struct mlcd_request_get_media_info req_minfo;
} sc_req;
#define sc_reqw sc_req.req_write
#define sc_reqm sc_req.req_minfo
SIMPLEQ_HEAD(mlcd_bufq, mlcd_buf) sc_q;
struct mlcd_buf *sc_bp;
int sc_retry;
#define MLCD_MAXRETRY 10
};
#define MLCD_PART(dev) (minor(dev) & 0xff)
#define MLCD_UNIT(dev) (minor(dev) >> 8)
#define MLCD_MINOR(unit, part) (((unit) << 8) | (part))
static int mlcdmatch(device_t, cfdata_t, void *);
static void mlcdattach(device_t, device_t, void *);
static int mlcddetach(device_t, int);
static void mlcd_intr(void *, struct maple_response *, int, int);
static void mlcd_printerror(const char *, uint32_t);
static struct mlcd_buf *mlcd_buf_alloc(int , int );
static void mlcd_buf_free(struct mlcd_buf *);
static inline uint32_t reverse_32(uint32_t);
static void mlcd_rotate_bitmap(void *, size_t);
static void mlcdstart(struct mlcd_softc *);
static void mlcdstart_bp(struct mlcd_softc *);
static void mlcddone(struct mlcd_softc *);
dev_type_open(mlcdopen);
dev_type_close(mlcdclose);
dev_type_write(mlcdwrite);
dev_type_ioctl(mlcdioctl);
const struct cdevsw mlcd_cdevsw = {
.d_open = mlcdopen,
.d_close = mlcdclose,
.d_read = noread,
.d_write = mlcdwrite,
.d_ioctl = mlcdioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = 0
};
CFATTACH_DECL_NEW(mlcd, sizeof(struct mlcd_softc),
mlcdmatch, mlcdattach, mlcddetach, NULL);
static const char initimg48x32[192] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1c, 0x70, 0x00, 0x7e, 0x1c, 0xf0, 0x0c, 0x60, 0x00, 0x33, 0x26, 0x6c,
0x0c, 0x60, 0x0c, 0x33, 0x66, 0x66, 0x1e, 0xc7, 0x0c, 0x62, 0x60, 0xc6,
0x1a, 0xc9, 0xbe, 0x7c, 0x30, 0xc6, 0x1a, 0xdb, 0x98, 0x66, 0x18, 0xc6,
0x1a, 0xdc, 0x18, 0x66, 0x0d, 0x8c, 0x31, 0xb0, 0x32, 0xc6, 0x8d, 0x8c,
0x31, 0xb1, 0x36, 0xcd, 0x99, 0x98, 0x71, 0x9e, 0x1d, 0xf9, 0xf3, 0xe0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08,
0x1d, 0x6c, 0x63, 0xc7, 0x30, 0xde, 0x25, 0x92, 0x12, 0xa8, 0x09, 0x08,
0x25, 0x1e, 0x72, 0xa8, 0x38, 0xc8, 0x25, 0x10, 0x92, 0xa8, 0x48, 0x28,
0x1d, 0x0e, 0x6a, 0xa7, 0x35, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static int
mlcdmatch(device_t parent, cfdata_t cf, void *aux)
{
struct maple_attach_args *ma = aux;
return (ma->ma_function == MAPLE_FN_LCD ? MAPLE_MATCH_FUNC : 0);
}
static void
mlcdattach(device_t parent, device_t self, void *aux)
{
struct mlcd_softc *sc = device_private(self);
struct maple_attach_args *ma = aux;
int i;
union {
uint32_t v;
struct mlcd_funcdef s;
} funcdef;
sc->sc_dev = self;
sc->sc_parent = parent;
sc->sc_unit = ma->ma_unit;
sc->sc_direction = ma->ma_basedevinfo->di_connector_direction;
funcdef.v = maple_get_function_data(ma->ma_devinfo, MAPLE_FN_LCD);
printf(": LCD display\n");
printf("%s: %d LCD, %d bytes/block, ",
device_xname(self),
sc->sc_npt = funcdef.s.pt + 1,
sc->sc_bsize = (funcdef.s.bb + 1) << 5);
if ((sc->sc_wacc = funcdef.s.wa) == 0)
printf("no ");
else
printf("%d acc/", sc->sc_wacc);
printf("write, %s, norm %s%s\n",
funcdef.s.hv ? "vert" : "horiz",
funcdef.s.bw ? "black" : "white",
sc->sc_direction == MAPLE_CONN_TOP ? ", upside-down" : "");
sc->sc_stat = MLCD_INIT;
SIMPLEQ_INIT(&sc->sc_q);
if (sc->sc_wacc != 0) {
sc->sc_waccsz = sc->sc_bsize / sc->sc_wacc;
if (sc->sc_bsize != sc->sc_waccsz * sc->sc_wacc) {
printf("%s: write access isn't equally divided\n",
device_xname(self));
sc->sc_wacc = 0;
} else if (sc->sc_waccsz > MLCD_MAXACCSIZE) {
printf("%s: write access size is too large\n",
device_xname(self));
sc->sc_wacc = 0;
}
}
if (sc->sc_wacc == 0) {
printf("%s: device doesn't support write\n",
device_xname(self));
return;
}
sc->sc_pt = malloc(sizeof(struct mlcd_pt) * sc->sc_npt, M_DEVBUF,
M_WAITOK|M_ZERO);
for (i = 0; i < sc->sc_npt; i++) {
snprintf(sc->sc_pt[i].pt_name, sizeof(sc->sc_pt[i].pt_name),
"%s.%d", device_xname(self), i);
}
maple_set_callback(parent, sc->sc_unit, MAPLE_FN_LCD,
mlcd_intr, sc);
sc->sc_reqm.func_code = htobe32(MAPLE_FUNC(MAPLE_FN_LCD));
sc->sc_reqm.pt = 0;
maple_command(sc->sc_parent, sc->sc_unit, MAPLE_FN_LCD,
MAPLE_COMMAND_GETMINFO, sizeof sc->sc_reqm / 4, &sc->sc_reqm, 0);
}
static int
mlcddetach(device_t self, int flags)
{
struct mlcd_softc *sc = device_private(self);
struct mlcd_buf *bp;
int minor_l, minor_h;
sc->sc_stat = MLCD_DETACH;
if ((bp = sc->sc_bp) != NULL) {
bp->lb_error = EIO;
wakeup(bp);
}
while ((bp = SIMPLEQ_FIRST(&sc->sc_q)) != NULL) {
SIMPLEQ_REMOVE_HEAD(&sc->sc_q, lb_q);
bp->lb_error = EIO;
wakeup(bp);
}
minor_l = MLCD_MINOR(device_unit(self), 0);
minor_h = MLCD_MINOR(device_unit(self), sc->sc_npt - 1);
vdevgone(cdevsw_lookup_major(&mlcd_cdevsw), minor_l, minor_h, VCHR);
if (sc->sc_pt)
free(sc->sc_pt, M_DEVBUF);
return 0;
}
static void
mlcd_intr(void *arg, struct maple_response *response, int sz, int flags)
{
struct mlcd_softc *sc = arg;
struct mlcd_response_media_info *rm = (void *) response->data;
struct mlcd_buf *bp;
int part;
struct mlcd_pt *pt;
switch (sc->sc_stat) {
case MLCD_INIT:
part = sc->sc_reqm.pt;
pt = &sc->sc_pt[part];
switch ((maple_response_t) response->response_code) {
case MAPLE_RESPONSE_DATATRF:
pt->pt_info = rm->info;
pt->pt_size = ((pt->pt_info.width + 1) *
(pt->pt_info.height + 1) + 7) / 8;
pt->pt_nblk = pt->pt_size / sc->sc_bsize;
printf("%s: %dx%d display, %d bytes\n",
pt->pt_name,
pt->pt_info.width + 1, pt->pt_info.height + 1,
pt->pt_size);
pt->pt_flags = MLCD_PT_OK;
break;
default:
printf("%s: init: unexpected response %#x, sz %d\n",
pt->pt_name, be32toh(response->response_code), sz);
break;
}
if (++part == sc->sc_npt) {
if (sc->sc_pt[0].pt_size == sizeof initimg48x32 &&
sc->sc_waccsz == sizeof initimg48x32 &&
sc->sc_wacc == 1) {
sc->sc_stat = MLCD_INIT2;
sc->sc_reqw.func_code =
htobe32(MAPLE_FUNC(MAPLE_FN_LCD));
sc->sc_reqw.pt = 0;
sc->sc_reqw.block = 0;
sc->sc_reqw.phase = 0;
memcpy(sc->sc_reqw.data, initimg48x32,
sizeof initimg48x32);
if (sc->sc_direction == MAPLE_CONN_TOP) {
mlcd_rotate_bitmap(sc->sc_reqw.data,
sizeof initimg48x32);
}
maple_command(sc->sc_parent, sc->sc_unit,
MAPLE_FN_LCD, MAPLE_COMMAND_BWRITE,
MLCD_SIZE_REQW(sc) / 4, &sc->sc_reqw, 0);
} else
sc->sc_stat = MLCD_IDLE;
} else {
sc->sc_reqm.pt = part;
maple_command(sc->sc_parent, sc->sc_unit,
MAPLE_FN_LCD, MAPLE_COMMAND_GETMINFO,
sizeof sc->sc_reqm / 4, &sc->sc_reqm, 0);
}
break;
case MLCD_INIT2:
sc->sc_stat = MLCD_IDLE;
break;
case MLCD_WRITE:
bp = sc->sc_bp;
switch ((maple_response_t) response->response_code) {
case MAPLE_RESPONSE_OK:
if (++sc->sc_reqw.phase == sc->sc_wacc) {
mlcddone(sc);
} else {
memcpy(sc->sc_reqw.data,
(char *)bp->lb_data +
sc->sc_waccsz * sc->sc_reqw.phase,
sc->sc_waccsz);
maple_command(sc->sc_parent, sc->sc_unit,
MAPLE_FN_LCD, MAPLE_COMMAND_BWRITE,
MLCD_SIZE_REQW(sc) / 4, &sc->sc_reqw, 0);
}
break;
case MAPLE_RESPONSE_LCDERR:
mlcd_printerror(sc->sc_pt[sc->sc_reqw.pt].pt_name,
rm->func_code );
mlcdstart_bp(sc);
break;
default:
printf("%s: write: unexpected response %#x, %#x, sz %d\n",
sc->sc_pt[sc->sc_reqw.pt].pt_name,
be32toh(response->response_code),
be32toh(rm->func_code), sz);
mlcdstart_bp(sc);
break;
}
break;
default:
break;
}
}
static void
mlcd_printerror(const char *head, uint32_t code)
{
printf("%s:", head);
NTOHL(code);
if (code & 1)
printf(" PT error");
if (code & 2)
printf(" Phase error");
if (code & 4)
printf(" Block error");
if (code & 010)
printf(" Write error");
if (code & 020)
printf(" Length error");
if (code & ~037)
printf(" Unknown error %#x", code & ~037);
printf("\n");
}
int
mlcdopen(dev_t dev, int flags, int devtype, struct lwp *l)
{
int unit, part;
struct mlcd_softc *sc;
struct mlcd_pt *pt;
unit = MLCD_UNIT(dev);
part = MLCD_PART(dev);
if ((sc = device_lookup_private(&mlcd_cd, unit)) == NULL
|| sc->sc_stat == MLCD_INIT
|| sc->sc_stat == MLCD_INIT2
|| part >= sc->sc_npt || (pt = &sc->sc_pt[part])->pt_flags == 0)
return ENXIO;
if (pt->pt_flags & MLCD_PT_OPEN)
return EBUSY;
pt->pt_flags |= MLCD_PT_OPEN;
return 0;
}
int
mlcdclose(dev_t dev, int flags, int devtype, struct lwp *l)
{
int unit, part;
struct mlcd_softc *sc;
struct mlcd_pt *pt;
unit = MLCD_UNIT(dev);
part = MLCD_PART(dev);
sc = device_lookup_private(&mlcd_cd, unit);
pt = &sc->sc_pt[part];
pt->pt_flags &= ~MLCD_PT_OPEN;
return 0;
}
static void
mlcdstart(struct mlcd_softc *sc)
{
struct mlcd_buf *bp;
if ((bp = SIMPLEQ_FIRST(&sc->sc_q)) == NULL) {
sc->sc_stat = MLCD_IDLE;
maple_enable_unit_ping(sc->sc_parent, sc->sc_unit,
MAPLE_FN_LCD, 1);
return;
}
SIMPLEQ_REMOVE_HEAD(&sc->sc_q, lb_q);
sc->sc_bp = bp;
sc->sc_retry = 0;
mlcdstart_bp(sc);
}
static void
mlcdstart_bp(struct mlcd_softc *sc)
{
struct mlcd_buf *bp;
bp = sc->sc_bp;
if (sc->sc_retry++ > MLCD_MAXRETRY) {
bp->lb_error = EIO;
mlcddone(sc);
return;
}
maple_enable_unit_ping(sc->sc_parent, sc->sc_unit, MAPLE_FN_LCD, 0);
sc->sc_stat = MLCD_WRITE;
sc->sc_reqw.func_code = htobe32(MAPLE_FUNC(MAPLE_FN_LCD));
sc->sc_reqw.pt = bp->lb_partno;
sc->sc_reqw.block = htobe16(bp->lb_blkno);
sc->sc_reqw.phase = 0;
memcpy(sc->sc_reqw.data,
(char *) bp->lb_data , sc->sc_waccsz);
maple_command(sc->sc_parent, sc->sc_unit, MAPLE_FN_LCD,
MAPLE_COMMAND_BWRITE, MLCD_SIZE_REQW(sc) / 4, &sc->sc_reqw, 0);
}
static void
mlcddone(struct mlcd_softc *sc)
{
struct mlcd_buf *bp;
bp = sc->sc_bp;
KASSERT(bp);
sc->sc_bp = NULL;
wakeup(bp);
mlcdstart(sc);
}
static struct mlcd_buf *
mlcd_buf_alloc(int dev, int flags)
{
struct mlcd_softc *sc;
struct mlcd_pt *pt;
int unit, part;
struct mlcd_buf *bp;
unit = MLCD_UNIT(dev);
part = MLCD_PART(dev);
sc = device_lookup_private(&mlcd_cd, unit);
KASSERT(sc);
pt = &sc->sc_pt[part];
KASSERT(pt);
if ((bp = malloc(MLCD_BUF_SZ(sc), M_DEVBUF, flags)) == NULL)
return bp;
if (sc != device_lookup_private(&mlcd_cd, unit)
|| sc->sc_stat == MLCD_INIT
|| sc->sc_stat == MLCD_INIT2
|| part >= sc->sc_npt || pt != &sc->sc_pt[part]
|| pt->pt_flags == 0) {
free(bp, M_DEVBUF);
return NULL;
}
bp->lb_error = 0;
return bp;
}
static void
mlcd_buf_free(struct mlcd_buf *bp)
{
free(bp, M_DEVBUF);
}
static inline uint32_t
reverse_32(uint32_t b)
{
uint32_t b1;
b1 = (b & 0x55555555) << 1; b = (b >> 1) & 0x55555555; b |= b1;
b1 = (b & 0x33333333) << 2; b = (b >> 2) & 0x33333333; b |= b1;
b1 = (b & 0x0f0f0f0f) << 4; b = (b >> 4) & 0x0f0f0f0f; b |= b1;
return bswap32(b);
}
static void
mlcd_rotate_bitmap(void *ptr, size_t size)
{
uint32_t *p, *q, tmp;
KDASSERT(size % sizeof(uint32_t) == 0);
for (p = ptr, q = (void *)((char *)ptr + size); p < q; ) {
tmp = reverse_32(*p);
*p++ = reverse_32(*--q);
*q = tmp;
}
}
int
mlcdwrite(dev_t dev, struct uio *uio, int flags)
{
struct mlcd_softc *sc;
struct mlcd_pt *pt;
struct mlcd_buf *bp;
int part;
off_t devsize;
int error = 0;
part = MLCD_PART(dev);
sc = device_lookup_private(&mlcd_cd, MLCD_UNIT(dev));
pt = &sc->sc_pt[part];
#if 0
printf("%s: mlcdwrite: offset %ld, size %d\n",
pt->pt_name, (long) uio->uio_offset, uio->uio_resid);
#endif
devsize = pt->pt_nblk * sc->sc_bsize;
if (uio->uio_offset % sc->sc_bsize || uio->uio_offset > devsize)
return EINVAL;
if ((bp = mlcd_buf_alloc(dev, M_WAITOK)) == NULL)
return EIO;
bp->lb_partno = part;
while (uio->uio_offset < devsize
&& uio->uio_resid >= (size_t) sc->sc_bsize) {
bp->lb_blkno = (sc->sc_direction == MAPLE_CONN_TOP) ?
pt->pt_nblk - uio->uio_offset / sc->sc_bsize - 1 :
uio->uio_offset / sc->sc_bsize;
if ((error = uiomove(bp->lb_data, sc->sc_bsize, uio)) != 0)
break;
if (sc->sc_direction == MAPLE_CONN_TOP) {
mlcd_rotate_bitmap(bp->lb_data, sc->sc_bsize);
}
SIMPLEQ_INSERT_TAIL(&sc->sc_q, bp, lb_q);
if (sc->sc_stat == MLCD_IDLE)
mlcdstart(sc);
tsleep(bp, PRIBIO + 1, "mlcdbuf", 0);
if ((error = bp->lb_error) != 0) {
uio->uio_resid += sc->sc_bsize;
break;
}
}
mlcd_buf_free(bp);
return error;
}
int
mlcdioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
int unit;
struct mlcd_softc *sc;
unit = MLCD_UNIT(dev);
sc = device_lookup_private(&mlcd_cd, unit);
switch (cmd) {
default:
return maple_unit_ioctl(sc->sc_parent, sc->sc_unit, cmd, data,
flag, l);
}
return 0;
}