/*
module/drivers.c
functions for manipulating drivers
COMEDI - Linux Control and Measurement Device Interface
Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/highmem.h> /* for SuSE brokenness */
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include "comedidev.h"
#include "comedi_internal.h"
struct comedi_driver *comedi_drivers;
/* protects access to comedi_drivers */
DEFINE_MUTEX(comedi_drivers_list_lock);
int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev)
{
if (hw_dev == dev->hw_dev)
return 0;
if (dev->hw_dev != NULL)
return -EEXIST;
dev->hw_dev = get_device(hw_dev);
return 0;
}
EXPORT_SYMBOL_GPL(comedi_set_hw_dev);
static void comedi_clear_hw_dev(struct comedi_device *dev)
{
put_device(dev->hw_dev);
dev->hw_dev = NULL;
}
/**
* comedi_alloc_devpriv() - Allocate memory for the device private data.
* @dev: comedi_device struct
* @size: size of the memory to allocate
*/
void *comedi_alloc_devpriv(struct comedi_device *dev, size_t size)
{
dev->private = kzalloc(size, GFP_KERNEL);
return dev->private;
}
EXPORT_SYMBOL_GPL(comedi_alloc_devpriv);
int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices)
{
struct comedi_subdevice *s;
int i;
if (num_subdevices < 1)
return -EINVAL;
s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL);
if (!s)
return -ENOMEM;
dev->subdevices = s;
dev->n_subdevices = num_subdevices;
for (i = 0; i < num_subdevices; ++i) {
s = &dev->subdevices[i];
s->device = dev;
s->index = i;
s->async_dma_dir = DMA_NONE;
spin_lock_init(&s->spin_lock);
s->minor = -1;
}
return 0;
}
EXPORT_SYMBOL_GPL(comedi_alloc_subdevices);
/**
* comedi_alloc_subdev_readback() - Allocate memory for the subdevice readback.
* @s: comedi_subdevice struct
*/
int comedi_alloc_subdev_readback(struct comedi_subdevice *s)
{
if (!s->n_chan)
return -EINVAL;
s->readback = kcalloc(s->n_chan, sizeof(*s->readback), GFP_KERNEL);
if (!s->readback)
return -ENOMEM;
if (!s->insn_read)
s->insn_read = comedi_readback_insn_read;
return 0;
}
EXPORT_SYMBOL_GPL(comedi_alloc_subdev_readback);
static void comedi_device_detach_cleanup(struct comedi_device *dev)
{
int i;
struct comedi_subdevice *s;
if (dev->subdevices) {
for (i = 0; i < dev->n_subdevices; i++) {
s = &dev->subdevices[i];
if (s->runflags & COMEDI_SRF_FREE_SPRIV)
kfree(s->private);
comedi_free_subdevice_minor(s);
if (s->async) {
comedi_buf_alloc(dev, s, 0);
kfree(s->async);
}
kfree(s->readback);
}
kfree(dev->subdevices);
dev->subdevices = NULL;
dev->n_subdevices = 0;
}
kfree(dev->private);
dev->private = NULL;
dev->driver = NULL;
dev->board_name = NULL;
dev->board_ptr = NULL;
dev->mmio = NULL;
dev->iobase = 0;
dev->iolen = 0;
dev->ioenabled = false;
dev->irq = 0;
dev->read_subdev = NULL;
dev->write_subdev = NULL;
dev->open = NULL;
dev->close = NULL;
comedi_clear_hw_dev(dev);
}
void comedi_device_detach(struct comedi_device *dev)
{
comedi_device_cancel_all(dev);
down_write(&dev->attach_lock);
dev->attached = false;
dev->detach_count++;
if (dev->driver)
dev->driver->detach(dev);
comedi_device_detach_cl