/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include #endif /* a non zero value causes debug info to be displayed */ uint_t gpio_debug_flag = 0; #ifdef DEBUG static void gpio_debug(dev_info_t *dip, char *format, uint_t arg1, uint_t arg2, uint_t arg3, uint_t arg4, uint_t arg5); #define DBG(dip, format, arg1, arg2, arg3, arg4, arg5) \ gpio_debug(dip, format, (uint_t)arg1, (uint_t)arg2, (uint_t)arg3, \ (uint_t)arg4, (uint_t)arg5) #else #define DBG(dip, format, arg1, arg2, arg3, arg4, arg5) #endif /* Driver soft state structure */ struct gpio_softc { dev_info_t *gp_dip; kmutex_t gp_mutex; int gp_state; ddi_acc_handle_t gp_handle; uint8_t *gp_regs; }; #define getsoftc(minor) \ ((struct gpio_softc *)ddi_get_soft_state(statep, (minor))) /* dev_ops and cb_ops entry point function declarations */ static int gpio_attach(dev_info_t *, ddi_attach_cmd_t); static int gpio_detach(dev_info_t *, ddi_detach_cmd_t); static int gpio_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); static int gpio_open(dev_t *, int, int, cred_t *); static int gpio_close(dev_t, int, int, cred_t *); static int gpio_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); struct cb_ops gpio_cb_ops = { gpio_open, gpio_close, nodev, nodev, nodev, /* dump */ nodev, nodev, gpio_ioctl, nodev, /* devmap */ nodev, nodev, nochpoll, ddi_prop_op, NULL, /* for STREAMS drivers */ D_NEW | D_MP, /* driver compatibility flag */ CB_REV, nodev, nodev }; static struct dev_ops gpio_dev_ops = { DEVO_REV, /* driver build version */ 0, /* device reference count */ gpio_getinfo, nulldev, nulldev, /* probe */ gpio_attach, gpio_detach, nulldev, /* reset */ &gpio_cb_ops, (struct bus_ops *)NULL, nulldev, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; /* module configuration stuff */ static void *statep; extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, "gpio driver", &gpio_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, 0 }; int _init(void) { int e; if (e = ddi_soft_state_init(&statep, sizeof (struct gpio_softc), 1)) { return (e); } if ((e = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&statep); } return (e); } int _fini(void) { int e; if ((e = mod_remove(&modlinkage)) != 0) { return (e); } ddi_soft_state_fini(&statep); return (DDI_SUCCESS); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* ARGSUSED */ static int gpio_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { int instance = getminor((dev_t)arg); int retval = DDI_SUCCESS; struct gpio_softc *softc; switch (cmd) { case DDI_INFO_DEVT2DEVINFO: if ((softc = getsoftc(instance)) == NULL) { *result = (void *)NULL; retval = DDI_FAILURE; } else *result = (void *)softc->gp_dip; break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)instance; break; default: retval = DDI_FAILURE; } return (retval); } static int gpio_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance; struct gpio_softc *softc = NULL; ddi_device_acc_attr_t dev_attr; switch (cmd) { case DDI_ATTACH: /* Allocate and get the soft state structure for this instance. */ instance = ddi_get_instance(dip); DBG(dip, "attach: instance is %d", instance, 0, 0, 0, 0); if (ddi_soft_state_zalloc(statep, instance) != DDI_SUCCESS) goto attach_failed; softc = getsoftc(instance); softc->gp_dip = dip; softc->gp_state = 0; mutex_init(&softc->gp_mutex, NULL, MUTEX_DRIVER, NULL); /* Map in the gpio device registers. */ dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; dev_attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (ddi_regs_map_setup(dip, 0, (caddr_t *)&softc->gp_regs, 0, 0, &dev_attr, &softc->gp_handle) != DDI_SUCCESS) goto attach_failed; DBG(dip, "attach: regs=0x%p", (uintptr_t)softc->gp_regs, 0, 0, 0, 0); DBG(dip, "attach: port 1 data is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[0]), 0, 0, 0, 0); DBG(dip, "attach: port 1 direction is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[1]), 0, 0, 0, 0); DBG(dip, "attach: port 1 output type is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[2]), 0, 0, 0, 0); DBG(dip, "attach: port 1 pull up control type is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[3]), 0, 0, 0, 0); DBG(dip, "attach: port 2 data is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[4]), 0, 0, 0, 0); DBG(dip, "attach: port 2 direction is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[5]), 0, 0, 0, 0); DBG(dip, "attach: port 2 output type is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[6]), 0, 0, 0, 0); DBG(dip, "attach: port 2 pull up control type is %x", (uintptr_t)ddi_get8(softc->gp_handle, &softc->gp_regs[7]), 0, 0, 0, 0); /* Create device minor nodes. */ if (ddi_create_minor_node(dip, "gpio", S_IFCHR, instance, NULL, 0) == DDI_FAILURE) { ddi_regs_map_free(&softc->gp_handle); goto attach_failed; } ddi_report_dev(dip); return (DDI_SUCCESS); case DDI_RESUME: /* Nothing to do for a resume. */ return (DDI_SUCCESS); default: return (DDI_FAILURE); } attach_failed: if (softc) { mutex_destroy(&softc->gp_mutex); if (softc->gp_handle) ddi_regs_map_free(&softc->gp_handle); ddi_soft_state_free(statep, instance); ddi_remove_minor_node(dip, NULL); } return (DDI_FAILURE); } static int gpio_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance; struct gpio_softc *softc; switch (cmd) { case DDI_DETACH: instance = ddi_get_instance(dip); DBG(dip, "detach: instance is %d", instance, 0, 0, 0, 0); if ((softc = getsoftc(instance)) == NULL) return (ENXIO); mutex_destroy(&softc->gp_mutex); ddi_regs_map_free(&softc->gp_handle); ddi_soft_state_free(statep, instance); ddi_remove_minor_node(dip, NULL); return (DDI_SUCCESS); case DDI_SUSPEND: /* Nothing to do in the suspend case. */ return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /* ARGSUSED */ static int gpio_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int instance = getminor(*devp); DBG(NULL, "open: instance is %d", instance, 0, 0, 0, 0); return (getsoftc(instance) == NULL ? ENXIO : 0); } /* ARGSUSED */ static int gpio_close(dev_t dev, int flag, int otyp, cred_t *credp) { int instance = getminor(dev); DBG(NULL, "close: instance is %d", instance, 0, 0, 0, 0); return (getsoftc(instance) == NULL ? ENXIO : 0); } /* ARGSUSED */ static int gpio_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { int instance = getminor(dev); struct gpio_softc *softc = getsoftc(instance); gpio_87317_op_t info; uint8_t byte; DBG(softc->gp_dip, "ioctl: instance is %d", instance, 0, 0, 0, 0); if (softc == NULL) return (ENXIO); /* Copy the command from user space. */ if (ddi_copyin((caddr_t)arg, (caddr_t)&info, sizeof (gpio_87317_op_t), mode) != 0) return (EFAULT); /* Check the command arguments. We only support port 1 in bank 0. */ if ((info.gpio_bank != 0) || (info.gpio_offset != GPIO_87317_PORT1_DATA)) { return (EINVAL); } /* Grap the instance's mutex to insure exclusive access. */ mutex_enter(&softc->gp_mutex); /* Get the contents of the GPIO register we're suppose to modify. */ byte = ddi_get8(softc->gp_handle, &softc->gp_regs[info.gpio_offset]); switch (cmd) { case GPIO_CMD_SET_BITS: DBG(softc->gp_dip, "ioctl: SET_BITS, byte is %x", byte, 0, 0, 0, 0); byte |= info.gpio_data; ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset], byte); byte = ddi_get8(softc->gp_handle, &softc->gp_regs[info.gpio_offset]); DBG(softc->gp_dip, "ioctl: SET_BITS, byte is %x", byte, 0, 0, 0, 0); break; case GPIO_CMD_CLR_BITS: DBG(softc->gp_dip, "ioctl: CLR_BITS, byte is %x", byte, 0, 0, 0, 0); byte &= ~info.gpio_data; ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset], byte); byte = ddi_get8(softc->gp_handle, &softc->gp_regs[info.gpio_offset]); DBG(softc->gp_dip, "ioctl: CLR_BITS, byte is %x", byte, 0, 0, 0, 0); break; case GPIO_CMD_GET: DBG(softc->gp_dip, "ioctl: GPIO_CMD_GET", 0, 0, 0, 0, 0); info.gpio_data = byte; if (ddi_copyout((caddr_t)&info, (caddr_t)arg, sizeof (gpio_87317_op_t), mode) != 0) { mutex_exit(&softc->gp_mutex); return (EFAULT); } break; case GPIO_CMD_SET: DBG(softc->gp_dip, "ioctl: GPIO_CMD_SET", 0, 0, 0, 0, 0); ddi_put8(softc->gp_handle, &softc->gp_regs[info.gpio_offset], info.gpio_data); break; default: mutex_exit(&softc->gp_mutex); return (EINVAL); } mutex_exit(&softc->gp_mutex); return (0); } #ifdef DEBUG void gpio_debug(dev_info_t *dip, char *format, uint_t arg1, uint_t arg2, uint_t arg3, uint_t arg4, uint_t arg5) { if (gpio_debug_flag == 0) { return; } if (dip == NULL) { prom_printf("gpio: "); } else { prom_printf("%s%d: ", ddi_driver_name(dip), ddi_get_instance(dip)); } prom_printf(format, arg1, arg2, arg3, arg4, arg5); prom_printf("\n"); } #endif