diff options
Diffstat (limited to 'drivers/misc/i2s/i2s.c')
-rw-r--r-- | drivers/misc/i2s/i2s.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/drivers/misc/i2s/i2s.c b/drivers/misc/i2s/i2s.c new file mode 100644 index 00000000000..a77711e3dd4 --- /dev/null +++ b/drivers/misc/i2s/i2s.c @@ -0,0 +1,597 @@ +/*----------------------------------------------------------------------------*/ +/* copyright STMicroelectronics, 2007. */ +/* */ +/* 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.1 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 FITNES */ +/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */ +/* details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/*----------------------------------------------------------------------------*/ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/cache.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/i2s/i2s.h> +#include <linux/platform_device.h> + +/*******************************************************************************/ +static DEFINE_MUTEX(core_lock); + +static void i2sdev_release(struct device *dev) +{ + struct i2s_device *i2s = to_i2s_device(dev); + + if (i2s->controller) + put_device(&(i2s->controller->dev)); + kfree(dev); +} +static ssize_t +modalias_show(struct device *dev, struct device_attribute *a, char *buf) +{ + const struct i2s_device *i2s = to_i2s_device(dev); + return sprintf(buf, "%s\n", i2s->modalias); +} + +static struct device_attribute i2s_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +/* modalias support makes "modprobe $MODALIAS" new-style hotplug work, + * and the sysfs version makes coldplug work too. + */ +static const struct i2s_device_id *i2s_match_id(const struct i2s_device_id *id, + const struct i2s_device *device) +{ + while (id->name[0]) { + if (strcmp(device->modalias, id->name) == 0) + return id; + id++; + } + return NULL; +} + +static int i2s_match_device(struct device *dev, struct device_driver *drv) +{ + const struct i2s_device *device = to_i2s_device(dev); + struct i2s_driver *driver = to_i2s_driver(drv); + if (driver->id_table) + return i2s_match_id(driver->id_table, device) != NULL; + return 0; +} + +static int i2s_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + const struct i2s_device *i2s = to_i2s_device(dev); + + add_uevent_var(env, "MODALIAS=%s", i2s->modalias); + return 0; +} + +#ifdef CONFIG_PM +static int i2s_suspend(struct device *dev, pm_message_t message) +{ + int value = 0; + struct i2s_driver *drv = to_i2s_driver(dev->driver); + + /* suspend will stop irqs and dma; no more i/o */ + if (drv) { + if (drv->suspend) + value = drv->suspend(to_i2s_device(dev), message); + else + dev_dbg(dev, "... can't suspend\n"); + } + return value; +} + +static int i2s_resume(struct device *dev) +{ + int value = 0; + struct i2s_driver *drv = to_i2s_driver(dev->driver); + + /* resume may restart the i/o queue */ + if (drv) { + if (drv->resume) + value = drv->resume(to_i2s_device(dev)); + else + dev_dbg(dev, "... can't resume\n"); + } + return value; +} + +#else +#define i2s_suspend NULL +#define i2s_resume NULL +#endif + +/*This bus is designed to handle various protocols supported by the MSP- ARM Primecell IP + * such as + * I2s, PCM, AC97, TDM .... (refer to the data sheet for the complete list. + * Current MSP driver has the above ones coded. + * */ +struct bus_type i2s_bus_type = { + .name = "i2s", + .dev_attrs = i2s_dev_attrs, + .match = i2s_match_device, + .uevent = i2s_uevent, + .suspend = i2s_suspend, + .resume = i2s_resume, +}; + +EXPORT_SYMBOL_GPL(i2s_bus_type); + +static int i2s_drv_probe(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + return sdrv->probe(to_i2s_device(dev)); +} + +static int i2s_drv_remove(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + return sdrv->remove(to_i2s_device(dev)); +} + +static void i2s_drv_shutdown(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + sdrv->shutdown(to_i2s_device(dev)); +} + +/** + * i2s_register_driver - register a I2S driver + * @sdrv: the driver to register + * Context: can sleep + */ +int i2s_register_driver(struct i2s_driver *sdrv) +{ + sdrv->driver.bus = &i2s_bus_type; + if (sdrv->probe) + sdrv->driver.probe = i2s_drv_probe; + if (sdrv->remove) + sdrv->driver.remove = i2s_drv_remove; + if (sdrv->shutdown) + sdrv->driver.shutdown = i2s_drv_shutdown; + return driver_register(&sdrv->driver); +} + +EXPORT_SYMBOL_GPL(i2s_register_driver); + +/******************************************************************************/ +struct boardinfo { + struct list_head list; + unsigned n_board_info; + struct i2s_board_info board_info[0]; +}; + +static LIST_HEAD(board_list); +static DEFINE_MUTEX(board_lock); + +/* I2S devices should normally not be created by I2S device drivers; that + * would make them board-specific. Similarly with I2S master drivers. + * Device registration normally goes into like arch/.../mach.../board-YYY.c + * with other readonly (flashable) information about mainboard devices. + */ +struct i2s_device *i2s_alloc_device(struct device *device) +{ + struct i2s_device *i2s; + struct device *dev = device->parent; + + get_device(device); + i2s = kzalloc(sizeof *i2s, GFP_KERNEL); + if (!i2s) { + dev_err(dev, "cannot alloc i2s_device\n"); + return NULL; + } + + i2s->dev.parent = dev; + i2s->dev.bus = &i2s_bus_type; + i2s->dev.release = i2sdev_release; + device_initialize(&i2s->dev); + return i2s; +} + +EXPORT_SYMBOL_GPL(i2s_alloc_device); + +/** + * i2s_add_device - Add i2s_device allocated with i2s_alloc_device + * @i2s: i2s_device to register + * + * Companion function to i2s_alloc_device. Devices allocated with + * i2s_alloc_device can be added onto the i2s bus with this function. + * + * Returns 0 on success; negative errno on failure + */ +int i2s_add_device(struct i2s_device *i2s) +{ + static DEFINE_MUTEX(i2s_add_lock); + struct device *dev = i2s->dev.parent; + int status; + + dev_set_name(&i2s->dev, "%s.%u", "i2s", i2s->chip_select); + + mutex_lock(&i2s_add_lock); + + if (bus_find_device_by_name(&i2s_bus_type, NULL, dev_name(&i2s->dev)) + != NULL) { + dev_err(dev, "chipselect %d already in use\n", + i2s->chip_select); + status = -EBUSY; + goto done; + } + + /* Device may be bound to an active driver when this returns */ + status = device_add(&i2s->dev); + if (status < 0) + dev_err(dev, "can't %s %s, status %d\n", + "add", dev_name(&i2s->dev), status); + else + dev_dbg(dev, "registered child %s\n", dev_name(&i2s->dev)); + + done: + mutex_unlock(&i2s_add_lock); + return status; +} + +EXPORT_SYMBOL_GPL(i2s_add_device); + +/** + * i2s_new_device - instantiate one new I2S device + * @i2s_cont: Controller to which device is connected + * @chip: Describes the I2S device + * Context: can sleep + * + * On typical mainboards, this is purely internal; and it's not needed + * after board init creates the hard-wired devices. Some development + * platforms may not be able to use i2s_register_board_info though, and + * this is exported so that driver could add devices (which it would + * learn about out-of-band). + * + * Returns the new device, or NULL. + */ +struct i2s_device *i2s_new_device(struct i2s_controller *i2s_cont, + struct i2s_board_info *chip) +{ + struct i2s_device *proxy; + int status; + + /* NOTE: caller did any chip->bus_num checks necessary. + * + * Also, unless we change the return value convention to use + * error-or-pointer (not NULL-or-pointer), troubleshootability + * suggests syslogged diagnostics are best here (ugh). + */ + + proxy = i2s_alloc_device(&i2s_cont->dev); + if (!proxy) + return NULL; + + WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias)); + + proxy->chip_select = chip->chip_select; + strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); + proxy->dev.platform_data = (void *)chip->platform_data; + proxy->controller = i2s_cont; + + status = i2s_add_device(proxy); + if (status < 0) { + kfree(proxy); + return NULL; + } + + return proxy; +} + +EXPORT_SYMBOL_GPL(i2s_new_device); + +/** + * i2s_register_board_info - register I2S devices for a given board + * @info: array of chip descriptors + * @n: how many descriptors are provided + * Context: can sleep + * + * Board-specific early init code calls this (probably during arch_initcall) + * with segments of the I2S device table. Any device nodes are created later, + * after the relevant parent I2S controller (id) is defined. We keep + * this table of devices forever, so that reloading a controller driver will + * not make Linux forget about these hard-wired devices. + * + */ +int __init +i2s_register_board_info(struct i2s_board_info const *info, unsigned n) +{ + struct boardinfo *bi; + + bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL); + if (!bi) + return -ENOMEM; + bi->n_board_info = n; + memcpy(bi->board_info, info, n * sizeof *info); + + mutex_lock(&board_lock); + list_add_tail(&bi->list, &board_list); + mutex_unlock(&board_lock); + return 0; +} + +/** + * scan_boardinfo - Scan, creates and registered new i2s device structure. + * @i2s_cont: i2s controller structure + * Context: process + * + * It will scan the device list that may be registered statically using + * register_board_info func in arch specific directory and call + * i2s_new_device to create and registered i2s device over i2s bus. It is + * called by i2s_add_controller function. + * + * Returns void. + */ +static void scan_boardinfo(struct i2s_controller *i2s_cont) +{ + struct boardinfo *bi; + + mutex_lock(&board_lock); + list_for_each_entry(bi, &board_list, list) { + struct i2s_board_info *chip = bi->board_info; + unsigned n; + + for (n = bi->n_board_info; n > 0; n--, chip++) { + if (chip->id != i2s_cont->id) + continue; + /* NOTE: this relies on i2s_new_device to + * issue diagnostics when given bogus inputs + */ + (void)i2s_new_device(i2s_cont, chip); + } + } + mutex_unlock(&board_lock); +} + +/******************************************************************************/ +/**I2S Controller inittialization*/ +static void i2s_controller_dev_release(struct device *dev) +{ + struct i2s_controller *i2s_cont; + i2s_cont = container_of(dev, struct i2s_controller, dev); + kfree(i2s_cont); +} + +static ssize_t +show_controller_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2s_controller *cont = to_i2s_controller(dev); + return sprintf(buf, "%s\n", cont->name); +} + +static struct device_attribute i2s_controller_attrs[] = { + __ATTR(name, S_IRUGO, show_controller_name, NULL), + {}, +}; + +static struct class i2s_controller_class = { + .owner = THIS_MODULE, + .name = "i2s-controller", + .dev_attrs = i2s_controller_attrs, +}; + +static int i2s_register_controller(struct i2s_controller *cont) +{ + int res = 0; + mutex_init(&cont->bus_lock); + + mutex_lock(&core_lock); + + /* Add the controller to the driver core. + * If the parent pointer is not set up, + * we add this controller to the host bus. + */ + if (cont->dev.parent == NULL) { + cont->dev.parent = &platform_bus; + pr_debug("I2S controller driver [%s] forgot to specify " + "physical device\n", cont->name); + } + dev_set_name(&cont->dev, "I2Scrlr-%d", cont->id); + cont->dev.release = &i2s_controller_dev_release; + cont->dev.class = &i2s_controller_class; + res = device_register(&cont->dev); + if (res) + goto out_unlock; + + dev_dbg(&cont->dev, "controller [%s] registered\n", cont->name); + scan_boardinfo(cont); + out_unlock: + mutex_unlock(&core_lock); + return res; +} + +/** + * i2s_add_controller - declare i2s controller, use dynamic bus number + * @controller: the controller to add + * Context: can sleep + * + */ +int i2s_add_controller(struct i2s_controller *controller) +{ + return i2s_register_controller(controller); +} + +EXPORT_SYMBOL(i2s_add_controller); + +static int __unregister(struct device *dev, void *controller_dev) +{ + /* note: before about 2.6.14-rc1 this would corrupt memory: */ + if (dev != controller_dev) + i2s_unregister_device(to_i2s_device(dev)); + return 0; +} + +/** + * i2s_del_controller - unregister I2S controller + * @cont: the controller being unregistered + * Context: can sleep + * + * This unregisters an I2S controller which was previously registered + * by @i2s_add_controller. + */ +int i2s_del_controller(struct i2s_controller *cont) +{ + int res = 0; + int dummy; + mutex_lock(&core_lock); + + dummy = device_for_each_child(cont->dev.parent, &cont->dev, + __unregister); + device_unregister(&cont->dev); + mutex_unlock(&core_lock); + return res; +} + +EXPORT_SYMBOL(i2s_del_controller); + +/******************************************************************************/ +/*I2S interface apis*/ + +/** + * i2s_transfer - Main i2s transfer function. + * @i2s_cont: i2s controller structure passed by client driver. + * @message: i2s message structure contains transceive info. + * Context: process or interrupt. + * + * This API is called by client i2s driver as i2s_xfer funtion. It will handle + * main i2s transfer over i2s bus. The controller should registered its own + * functions using i2s algorithm structure. + * + * Returns error(-1) in case of failure or success(0). + */ +int i2s_transfer(struct i2s_controller *i2s_cont, struct i2s_message *message) +{ + return i2s_cont->algo->cont_transfer(i2s_cont, message); + +} + +EXPORT_SYMBOL(i2s_transfer); + +/** + * i2s_cleanup - Close the current i2s connection btw controller and client. + * @i2s_cont: i2s controller structure + * @flag: It indicates the functionality that needs to be disabled. + * Context: process + * + * This API will disable and reset the controller's configuration. Reset the + * controller so that i2s client driver can reconfigure with new configuration. + * Controller should release all the necessary resources which was acquired + * during setup. + * + * Returns error(-1) in case of failure or success(0). + */ +int i2s_cleanup(struct i2s_controller *i2s_cont, i2s_flag flag) +{ + int status = 0; + status = i2s_cont->algo->cont_cleanup(i2s_cont, flag); + if (status) + return -1; + else + return 0; +} + +EXPORT_SYMBOL(i2s_cleanup); + +/** + * i2s_setup - configures and enables the I2S controller. + * @i2s_cont: i2s controller sent by i2s device. + * @config: specifies the configuration parameters. + * + * This function configures the I2S controller with the client configuration. + * Controller was already registered on I2S bus by some master controller + * driver. + * + * Returns error(-1) in case of failure else success(0) + */ +int i2s_setup(struct i2s_controller *i2s_cont, void *config) +{ + return i2s_cont->algo->cont_setup(i2s_cont, config); +} + +EXPORT_SYMBOL(i2s_setup); + +/** + * i2s_hw_status - Get the current hw status for the i2s controller. + * @i2s_cont: i2s controller structure passed by client driver. + * Context: process or interrupt. + * + * This API is called by client i2s driver to find out current hw status. + * The controller should registered its own functions using i2s algorithm structure. + * + * Returns current hw status register. + */ +int i2s_hw_status(struct i2s_controller *i2s_cont) +{ + return i2s_cont->algo->cont_hw_status(i2s_cont); +} + +/** + * i2s_get_pointer - Get the current dma_addr_t for the i2s controller. + * @i2s_cont: i2s controller structure passed by client driver. + * @i2s_direction: Specifies TX or RX direction. + * Context: process or interrupt. + * + * This API is called by client i2s driver to return a dma_addr_t corresponding + * to the position of the DMA-controller. + * The controller should registered its own functions using i2s algorithm structure. + * + * Returns current hw status register. + */ +dma_addr_t i2s_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction) +{ + return i2s_cont->algo->cont_get_pointer(i2s_cont, i2s_direction); +} + +/******************************************************************************/ + +static int __init i2s_init(void) +{ + int status; + + status = bus_register(&i2s_bus_type); + if (status < 0) + goto err0; + + status = class_register(&i2s_controller_class); + if (status < 0) + goto err1; + return 0; + + err1: + bus_unregister(&i2s_bus_type); + err0: + return status; +} + +static void __exit i2s_exit(void) +{ + class_unregister(&i2s_controller_class); + bus_unregister(&i2s_bus_type); +} + +subsys_initcall(i2s_init); +module_exit(i2s_exit); + +MODULE_AUTHOR("Sandeep Kaushik, <sandeep-mmc.kaushik@st.com>"); +MODULE_LICENSE("GPL"); |