summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/i2c/clients/tda8444.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/i2c/clients/tda8444.c')
-rw-r--r--usr/src/uts/sun4u/io/i2c/clients/tda8444.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/i2c/clients/tda8444.c b/usr/src/uts/sun4u/io/i2c/clients/tda8444.c
new file mode 100644
index 0000000000..7a725decf5
--- /dev/null
+++ b/usr/src/uts/sun4u/io/i2c/clients/tda8444.c
@@ -0,0 +1,531 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/uio.h>
+#include <sys/modctl.h>
+#include <sys/open.h>
+#include <sys/types.h>
+#include <sys/kmem.h>
+#include <sys/systm.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/conf.h>
+#include <sys/mode.h>
+#include <sys/note.h>
+#include <sys/i2c/misc/i2c_svc.h>
+#include <sys/i2c/clients/tda8444_impl.h>
+
+/*
+ * cb ops
+ */
+static int tda8444_open(dev_t *, int, int, cred_t *);
+static int tda8444_close(dev_t, int, int, cred_t *);
+static int tda8444_read(dev_t dev, struct uio *uiop, cred_t *cred_p);
+static int tda8444_write(dev_t dev, struct uio *uiop, cred_t *cred_p);
+static int tda8444_io(dev_t dev, struct uio *uiop, int rw);
+/*
+ * dev ops
+ */
+static int tda8444_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
+ void **result);
+static int tda8444_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
+static int tda8444_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
+
+static struct cb_ops tda8444_cbops = {
+ tda8444_open, /* open */
+ tda8444_close, /* close */
+ nodev, /* strategy */
+ nodev, /* print */
+ nodev, /* dump */
+ tda8444_read, /* read */
+ tda8444_write, /* write */
+ nodev, /* ioctl */
+ nodev, /* devmap */
+ nodev, /* mmap */
+ nodev, /* segmap */
+ nochpoll, /* poll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* streamtab */
+ D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */
+ CB_REV, /* rev */
+ nodev, /* int (*cb_aread)() */
+ nodev /* int (*cb_awrite)() */
+};
+
+static struct dev_ops tda8444_ops = {
+ DEVO_REV,
+ 0,
+ tda8444_info,
+ nulldev,
+ nulldev,
+ tda8444_attach,
+ tda8444_detach,
+ nodev,
+ &tda8444_cbops,
+ NULL
+};
+
+static struct modldrv tda8444_modldrv = {
+ &mod_driverops, /* type of module - driver */
+ "tda8444 device driver v%I%",
+ &tda8444_ops,
+};
+
+static struct modlinkage tda8444_modlinkage = {
+ MODREV_1,
+ &tda8444_modldrv,
+ 0
+};
+
+static void *tda8444_soft_statep;
+static int tda8444_debug = 0;
+
+int
+_init(void)
+{
+ int error;
+
+ error = mod_install(&tda8444_modlinkage);
+ if (error == 0) {
+ (void) ddi_soft_state_init(&tda8444_soft_statep,
+ sizeof (struct tda8444_unit), TDA8444_MAX_DACS);
+ }
+
+ return (error);
+}
+
+int
+_fini(void)
+{
+ int error;
+
+ error = mod_remove(&tda8444_modlinkage);
+ if (error == 0) {
+ ddi_soft_state_fini(&tda8444_soft_statep);
+ }
+
+ return (error);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&tda8444_modlinkage, modinfop));
+}
+
+/* ARGSUSED */
+static int
+tda8444_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+ dev_t dev;
+ int instance;
+
+ if (infocmd == DDI_INFO_DEVT2INSTANCE) {
+ dev = (dev_t)arg;
+ instance = TDA8444_MINOR_TO_DEVINST(dev);
+ *result = (void *)(uintptr_t)instance;
+ return (DDI_SUCCESS);
+ }
+ return (DDI_FAILURE);
+}
+
+static int
+tda8444_do_resume(dev_info_t *dip)
+{
+ int instance = ddi_get_instance(dip);
+ struct tda8444_unit *unitp;
+ int channel;
+ int ret = DDI_SUCCESS;
+
+ unitp = (struct tda8444_unit *)
+ ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ if (unitp == NULL) {
+
+ return (ENXIO);
+ }
+
+ for (channel = 0; channel < TDA8444_CHANS; channel++) {
+ unitp->tda8444_transfer->i2c_wbuf[0] = TDA8444_REGBASE |
+ channel;
+ unitp->tda8444_transfer->i2c_wbuf[1] =
+ unitp->tda8444_output[channel];
+ DPRINTF(RESUME, ("tda8444_resume: setting channel %d to %d",
+ channel, unitp->tda8444_output[channel]));
+ if (i2c_transfer(unitp->tda8444_hdl,
+ unitp->tda8444_transfer) != I2C_SUCCESS) {
+ ret = DDI_FAILURE;
+ }
+ }
+
+ mutex_enter(&unitp->tda8444_mutex);
+ unitp->tda8444_flags = 0;
+ cv_signal(&unitp->tda8444_cv);
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (ret);
+}
+
+static int
+tda8444_do_attach(dev_info_t *dip)
+{
+ struct tda8444_unit *unitp;
+ char name[MAXNAMELEN];
+ int instance;
+ minor_t minor;
+ int i;
+
+ instance = ddi_get_instance(dip);
+
+ if (ddi_soft_state_zalloc(tda8444_soft_statep, instance) != 0) {
+ cmn_err(CE_WARN, "%s%d failed to zalloc softstate",
+ ddi_get_name(dip), instance);
+
+ return (DDI_FAILURE);
+ }
+
+ unitp = ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ if (unitp == NULL) {
+ return (DDI_FAILURE);
+ }
+
+ (void) snprintf(unitp->tda8444_name, sizeof (unitp->tda8444_name),
+ "%s%d", ddi_driver_name(dip), instance);
+
+ for (i = 0; i < TDA8444_CHANS; i++) {
+ (void) sprintf(name, "%d", i);
+ minor = TDA8444_CHANNEL_TO_MINOR(i) |
+ TDA8444_DEVINST_TO_MINOR(instance);
+ if (ddi_create_minor_node(dip, name, S_IFCHR, minor,
+ TDA8444_NODE_TYPE, NULL) == DDI_FAILURE) {
+ cmn_err(CE_WARN, "%s ddi_create_minor_node failed",
+ unitp->tda8444_name);
+ ddi_soft_state_free(tda8444_soft_statep, instance);
+ ddi_remove_minor_node(dip, NULL);
+
+ return (DDI_FAILURE);
+ }
+ unitp->tda8444_output[i] = TDA8444_UNKNOWN_OUT;
+ }
+
+ /*
+ * preallocate a single buffer for all writes
+ */
+ if (i2c_transfer_alloc(unitp->tda8444_hdl, &unitp->tda8444_transfer,
+ 2, 0, I2C_SLEEP) != I2C_SUCCESS) {
+ cmn_err(CE_WARN, "i2c_transfer_alloc failed");
+ ddi_remove_minor_node(dip, NULL);
+ ddi_soft_state_free(tda8444_soft_statep, instance);
+
+ return (DDI_FAILURE);
+ }
+ unitp->tda8444_transfer->i2c_flags = I2C_WR;
+ unitp->tda8444_transfer->i2c_version = I2C_XFER_REV;
+
+ if (i2c_client_register(dip, &unitp->tda8444_hdl) != I2C_SUCCESS) {
+ ddi_remove_minor_node(dip, NULL);
+ cmn_err(CE_WARN, "i2c_client_register failed");
+ ddi_soft_state_free(tda8444_soft_statep, instance);
+ i2c_transfer_free(unitp->tda8444_hdl, unitp->tda8444_transfer);
+
+ return (DDI_FAILURE);
+ }
+
+ mutex_init(&unitp->tda8444_mutex, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&unitp->tda8444_cv, NULL, CV_DRIVER, NULL);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+tda8444_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_ATTACH:
+
+ return (tda8444_do_attach(dip));
+ case DDI_RESUME:
+
+ return (tda8444_do_resume(dip));
+ default:
+
+ return (DDI_FAILURE);
+ }
+}
+
+static int
+tda8444_do_detach(dev_info_t *dip)
+{
+ struct tda8444_unit *unitp;
+ int instance;
+
+ instance = ddi_get_instance(dip);
+ unitp = ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ i2c_transfer_free(unitp->tda8444_hdl, unitp->tda8444_transfer);
+ i2c_client_unregister(unitp->tda8444_hdl);
+ ddi_remove_minor_node(dip, NULL);
+ mutex_destroy(&unitp->tda8444_mutex);
+ cv_destroy(&unitp->tda8444_cv);
+ ddi_soft_state_free(tda8444_soft_statep, instance);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+tda8444_do_suspend(dev_info_t *dip)
+{
+ struct tda8444_unit *unitp;
+ int instance;
+
+ instance = ddi_get_instance(dip);
+ unitp = ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ /*
+ * Set the busy flag so that future transactions block
+ * until resume.
+ */
+ mutex_enter(&unitp->tda8444_mutex);
+ while (unitp->tda8444_flags == TDA8444_BUSY) {
+ if (cv_wait_sig(&unitp->tda8444_cv,
+ &unitp->tda8444_mutex) <= 0) {
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (DDI_FAILURE);
+ }
+ }
+ unitp->tda8444_flags = TDA8444_SUSPENDED;
+ mutex_exit(&unitp->tda8444_mutex);
+ return (DDI_SUCCESS);
+}
+
+static int
+tda8444_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_DETACH:
+
+ return (tda8444_do_detach(dip));
+ case DDI_SUSPEND:
+
+ return (tda8444_do_suspend(dip));
+ default:
+
+ return (DDI_FAILURE);
+ }
+}
+
+static int
+tda8444_open(dev_t *devp, int flags, int otyp, cred_t *credp)
+{
+ _NOTE(ARGUNUSED(credp))
+ struct tda8444_unit *unitp;
+ int err = 0;
+ int instance = TDA8444_MINOR_TO_DEVINST(*devp);
+ int channel = TDA8444_MINOR_TO_CHANNEL(*devp);
+
+ if (instance < 0) {
+
+ return (ENXIO);
+ }
+
+ unitp = (struct tda8444_unit *)
+ ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ if (unitp == NULL) {
+
+ return (ENXIO);
+ }
+
+ if (otyp != OTYP_CHR) {
+
+ return (EINVAL);
+ }
+
+ mutex_enter(&unitp->tda8444_mutex);
+
+ if (flags & FEXCL) {
+ if (unitp->tda8444_oflag[channel] != 0) {
+ err = EBUSY;
+ } else {
+ unitp->tda8444_oflag[channel] = FEXCL;
+ }
+ } else {
+ if (unitp->tda8444_oflag[channel] == FEXCL) {
+ err = EBUSY;
+ } else {
+ unitp->tda8444_oflag[channel] = FOPEN;
+ }
+ }
+
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (err);
+}
+
+static int
+tda8444_close(dev_t dev, int flags, int otyp, cred_t *credp)
+{
+ _NOTE(ARGUNUSED(flags, otyp, credp))
+ struct tda8444_unit *unitp;
+ int instance = TDA8444_MINOR_TO_DEVINST(dev);
+ int channel = TDA8444_MINOR_TO_CHANNEL(dev);
+
+ if (instance < 0) {
+
+ return (ENXIO);
+ }
+
+ unitp = (struct tda8444_unit *)
+ ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ if (unitp == NULL) {
+
+ return (ENXIO);
+ }
+
+ mutex_enter(&unitp->tda8444_mutex);
+
+ unitp->tda8444_oflag[channel] = 0;
+
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+tda8444_read(dev_t dev, struct uio *uiop, cred_t *cred_p)
+{
+ _NOTE(ARGUNUSED(cred_p))
+ return (tda8444_io(dev, uiop, B_READ));
+}
+
+static int
+tda8444_write(dev_t dev, struct uio *uiop, cred_t *cred_p)
+{
+ _NOTE(ARGUNUSED(cred_p))
+ return (tda8444_io(dev, uiop, B_WRITE));
+}
+
+static int
+tda8444_io(dev_t dev, struct uio *uiop, int rw)
+{
+ struct tda8444_unit *unitp;
+ int instance = TDA8444_MINOR_TO_DEVINST(getminor(dev));
+ int channel = TDA8444_MINOR_TO_CHANNEL(getminor(dev));
+ int ret = 0;
+ size_t len = uiop->uio_resid;
+ int8_t out_value;
+
+ if (instance < 0) {
+
+ return (ENXIO);
+ }
+
+ if (len == 0) {
+ return (0);
+ }
+
+ unitp = (struct tda8444_unit *)
+ ddi_get_soft_state(tda8444_soft_statep, instance);
+
+ if (unitp == NULL) {
+
+ return (ENXIO);
+ }
+
+ if (rw == B_READ) {
+ if (unitp->tda8444_output[channel] != TDA8444_UNKNOWN_OUT) {
+ return (uiomove(&unitp->tda8444_output[channel], 1,
+ UIO_READ, uiop));
+ } else {
+ return (EIO);
+ }
+ }
+
+ /*
+ * rw == B_WRITE. Make sure each write to a device is single
+ * threaded since we pre-allocate a single write buffer. This is not a
+ * bottleneck since concurrent writes would serialize at the
+ * transport level anyway.
+ */
+ mutex_enter(&unitp->tda8444_mutex);
+ if (unitp->tda8444_flags == TDA8444_SUSPENDED) {
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (EAGAIN);
+ }
+
+ while (unitp->tda8444_flags == TDA8444_BUSY) {
+ if (cv_wait_sig(&unitp->tda8444_cv,
+ &unitp->tda8444_mutex) <= 0) {
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (EINTR);
+ }
+ }
+ unitp->tda8444_flags = TDA8444_BUSY;
+ mutex_exit(&unitp->tda8444_mutex);
+
+ unitp->tda8444_transfer->i2c_wbuf[0] = (TDA8444_REGBASE | channel);
+ if ((ret = uiomove(&out_value, sizeof (out_value), UIO_WRITE,
+ uiop)) == 0) {
+
+ /*
+ * Check bounds
+ */
+ if ((out_value > TDA8444_MAX_OUT) ||
+ (out_value < TDA8444_MIN_OUT)) {
+ ret = EINVAL;
+ } else {
+ unitp->tda8444_transfer->i2c_wbuf[1] =
+ (uchar_t)out_value;
+ DPRINTF(IO, ("setting channel %d to %d", channel,
+ unitp->tda8444_transfer->i2c_wbuf[1]));
+
+ if (i2c_transfer(unitp->tda8444_hdl,
+ unitp->tda8444_transfer) != I2C_SUCCESS) {
+ ret = EIO;
+ } else {
+ unitp->tda8444_output[channel] = out_value;
+ }
+ }
+ } else {
+ ret = EFAULT;
+ }
+
+ mutex_enter(&unitp->tda8444_mutex);
+ unitp->tda8444_flags = 0;
+ cv_signal(&unitp->tda8444_cv);
+ mutex_exit(&unitp->tda8444_mutex);
+
+ return (ret);
+}