From ee925ffab6fe7e5ba55cd4b0a3a6b8e0c3446343 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 18 Mar 2011 15:47:12 +0100 Subject: misc: Add i2s driver Signed-off-by: Robert Marklund --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 3 +- drivers/misc/i2s/Kconfig | 29 + drivers/misc/i2s/Makefile | 8 + drivers/misc/i2s/i2s.c | 597 ++++++++ drivers/misc/i2s/i2s_test_protocol_driver.c | 305 ++++ drivers/misc/i2s/msp_i2s.c | 2045 +++++++++++++++++++++++++++ drivers/misc/i2s/msp_i2s.h | 362 +++++ 8 files changed, 3349 insertions(+), 1 deletion(-) create mode 100644 drivers/misc/i2s/Kconfig create mode 100644 drivers/misc/i2s/Makefile create mode 100644 drivers/misc/i2s/i2s.c create mode 100644 drivers/misc/i2s/i2s_test_protocol_driver.c create mode 100644 drivers/misc/i2s/msp_i2s.c create mode 100644 drivers/misc/i2s/msp_i2s.h (limited to 'drivers') diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 8719c535781..8dce7fc1e20 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -505,6 +505,7 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/i2s/Kconfig" source "drivers/misc/audio_io_dev/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9ad03e8754e..4682fccff1f 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,5 +46,6 @@ obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ +obj-$(CONFIG_STM_I2S) += i2s/ obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ -obj-$(CONFIG_HWMEM) += hwmem/ +obj-$(CONFIG_HWMEM) += hwmem/ diff --git a/drivers/misc/i2s/Kconfig b/drivers/misc/i2s/Kconfig new file mode 100644 index 00000000000..a2652e9eab3 --- /dev/null +++ b/drivers/misc/i2s/Kconfig @@ -0,0 +1,29 @@ +# +# U8500 I2S HW kernel configuration +# +config STM_I2S + bool "U8500 I2S hardware driver" + depends on ARCH_U8500 && STE_DMA40 + default y + ---help--- + If you say Y here, you will enable the U8500 I2S hardware driver. + + If unsure, say N. +config STM_MSP_I2S + tristate "U8500 MSP_I2S hardware driver" + depends on ARCH_U8500 && STE_DMA40 && STM_I2S + default y + ---help--- + If you say Y here, you will enable the U8500 MSP_I2S hardware driver. + + If unsure, say N. + +config STM_I2S_TEST_PROTOCOL_DRIVER + tristate "U8500 I2S test protocol driver" + depends on STM_I2S && STE_DMA40 && STM_MSP_I2S + default n + ---help--- + If you say Y here, you will enable the test protocol driver used for testing I2S Rx and Tx controllers + + If unsure, say N. + diff --git a/drivers/misc/i2s/Makefile b/drivers/misc/i2s/Makefile new file mode 100644 index 00000000000..22cfdc07551 --- /dev/null +++ b/drivers/misc/i2s/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for I2S drivers +# + +nmdk_i2s-objs := i2s.o +obj-$(CONFIG_STM_I2S) += nmdk_i2s.o +obj-$(CONFIG_STM_MSP_I2S) += msp_i2s.o +obj-$(CONFIG_STM_I2S_TEST_PROTOCOL_DRIVER) += i2s_test_protocol_driver.o 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 . */ +/*----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*******************************************************************************/ +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, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/i2s_test_protocol_driver.c b/drivers/misc/i2s/i2s_test_protocol_driver.c new file mode 100644 index 00000000000..639a28454f5 --- /dev/null +++ b/drivers/misc/i2s/i2s_test_protocol_driver.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * Author: Sandeep Kaushik, + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int i2s_drv_offset = 1; + +module_param(i2s_drv_offset, int, 1); +MODULE_PARM_DESC(i2s_drv_offset, "i2s driver to be opened)=(0/1/2/3/4/5)"); + +#define MAX_I2S_CLIENTS 6 + +struct i2sdrv_data { + spinlock_t i2s_lock; + struct i2s_device *i2s; + /* flag to show the device is closed or not */ + bool device_closed; + u32 tx_status; + u32 rx_status; +}; + +static struct i2sdrv_data *i2sdrv[MAX_I2S_CLIENTS]; + +/*API Interface */ +int i2s_testprot_drv_open(int i2s_device_num) +{ + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + spin_lock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + if (!i2sdrv[i2s_device_num]->device_closed) { + spin_unlock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + return -EBUSY; + } + i2sdrv[i2s_device_num]->device_closed = false; + spin_unlock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + + return 0; +} +EXPORT_SYMBOL(i2s_testprot_drv_open); + +int i2s_config_default_protocol(int i2s_device_num, + struct test_prot_config *config) +{ + return __i2s_testprot_drv_configure(i2s_device_num, config, true); +} +EXPORT_SYMBOL(i2s_config_default_protocol); + +int i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config) +{ + return __i2s_testprot_drv_configure(i2s_device_num, config, false); +} +EXPORT_SYMBOL(i2s_testprot_drv_configure); + +int __i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config, bool use_default) +{ + int error_status = 0; + struct i2s_device *i2s_dev; + struct msp_config msp_config = { + .tx_clock_sel = TX_CLK_SEL_SRG, + .rx_clock_sel = 0x0, + .tx_frame_sync_sel = TX_SYNC_SRG_AUTO, + .rx_frame_sync_sel = 0x0, + .input_clock_freq = MSP_INPUT_FREQ_48MHZ, + .srg_clock_sel = SRG_CLK_SEL_APB, + .rx_frame_sync_pol = RX_FIFO_SYNC_HI, + .tx_frame_sync_pol = TX_FIFO_SYNC_HI, + .rx_fifo_config = RX_FIFO_ENABLE, + .tx_fifo_config = TX_FIFO_ENABLE, + .spi_clk_mode = SPI_CLK_MODE_NORMAL, + .tx_data_enable = 0x8000, + .spi_burst_mode = 0, + .loopback_enable = 0x80, + }; + + msp_config.default_protocol_desc = use_default; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) + return -EINVAL; + + if (!config) + return -EINVAL; + + msp_config.handler = config->handler; + msp_config.tx_callback_data = config->tx_callback_data; + msp_config.rx_callback_data = config->rx_callback_data; + msp_config.frame_freq = config->frame_freq; + msp_config.frame_size = config->frame_size; + msp_config.data_size = config->data_size; + msp_config.direction = config->direction; + msp_config.protocol = config->protocol; + msp_config.work_mode = config->work_mode; + + msp_config.def_elem_len = use_default; + + msp_config.multichannel_configured = 0; + msp_config.protocol_desc = config->protocol_desc; + + msp_config.multichannel_configured = config->multichannel_configured; + msp_config.multichannel_config.tx_multichannel_enable = + config->multichannel_config.tx_multichannel_enable; + /* Channel 1 to 3 */ + msp_config.multichannel_config.tx_channel_0_enable = + config->multichannel_config.tx_channel_0_enable; + /* Channel 33 to 64 */ + msp_config.multichannel_config.tx_channel_1_enable = + config->multichannel_config.tx_channel_1_enable; + /* Channel 65 to 96 */ + msp_config.multichannel_config.tx_channel_2_enable = + config->multichannel_config.tx_channel_2_enable; + /* Channel 97 to 128 */ + msp_config.multichannel_config.tx_channel_3_enable = + config->multichannel_config.tx_channel_3_enable; + msp_config.multichannel_config.rx_multichannel_enable = + config->multichannel_config.rx_multichannel_enable; + /* Channel 1 to 32 */ + msp_config.multichannel_config.rx_channel_0_enable = + config->multichannel_config.rx_channel_0_enable; + /* Channel 33 to 64 */ + msp_config.multichannel_config.rx_channel_1_enable = + config->multichannel_config.rx_channel_1_enable; + /* Channel 65 to 96 */ + msp_config.multichannel_config.rx_channel_2_enable = + config->multichannel_config.rx_channel_2_enable; + /* Channel 97 to 128 */ + msp_config.multichannel_config.rx_channel_3_enable = + config->multichannel_config.rx_channel_3_enable; + msp_config.multichannel_config.rx_comparison_enable_mode = + config->multichannel_config.rx_comparison_enable_mode; + msp_config.multichannel_config.comparison_value = + config->multichannel_config.comparison_value; + msp_config.multichannel_config.comparison_mask = + config->multichannel_config.comparison_mask; + + error_status = + i2s_setup(i2s_dev->controller, &msp_config); + if (error_status < 0) + dev_err(&i2s_dev->dev, "error in msp enable, error_status is %d\n", + error_status); + + return error_status; + +} + +int i2s_testprot_drv_transfer(int i2s_device_num, + void *txdata, size_t txbytes, void *rxdata, size_t rxbytes, + enum i2s_transfer_mode_t transfer_mode) +{ + int bytes_transreceive; + struct i2s_device *i2s_dev; + struct i2s_message message = {}; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) { + dev_info(&i2s_dev->dev, "msp device not opened yet\n"); + return -EINVAL; + } + + message.i2s_transfer_mode = transfer_mode; + message.i2s_direction = I2S_DIRECTION_BOTH; + message.txbytes = txbytes; + message.txdata = txdata; + message.rxbytes = rxbytes; + message.rxdata = rxdata; + message.dma_flag = 1; + bytes_transreceive = i2s_transfer(i2s_dev->controller, &message); + dev_dbg(&i2s_dev->dev, "bytes transreceived %d\n", bytes_transreceive); + + return bytes_transreceive; +} +EXPORT_SYMBOL(i2s_testprot_drv_transfer); + +int i2s_testprot_drv_close(int i2s_device_num) +{ + int status; + struct i2s_device *i2s_dev; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) + return -EINVAL; + + status = i2s_cleanup(i2s_dev->controller, DISABLE_ALL); + if (status) + return status; + + /* Mark the device as closed */ + i2sdrv[i2s_device_num]->device_closed = true; + + return 0; +} +EXPORT_SYMBOL(i2s_testprot_drv_close); + +static int i2sdrv_probe(struct i2s_device *i2s) +{ + int status = 0; + + /* Allocate driver data */ + if (!try_module_get(i2s->controller->dev.parent->driver->owner)) + return -ENOENT; + + i2sdrv[i2s->chip_select] = kzalloc(sizeof(*i2sdrv[i2s->chip_select]), + GFP_KERNEL); + + if (!i2sdrv[i2s->chip_select]) + return -ENOMEM; + + /* Initialize the driver data */ + i2sdrv[i2s->chip_select]->i2s = i2s; + i2sdrv[i2s->chip_select]->device_closed = true; + i2sdrv[i2s->chip_select]->tx_status = 0; + i2sdrv[i2s->chip_select]->rx_status = 0; + spin_lock_init(&i2sdrv[i2s->chip_select]->i2s_lock); + + i2s_set_drvdata(i2s, (void *)i2sdrv[i2s->chip_select]); + return status; +} + +static int i2sdrv_remove(struct i2s_device *i2s) +{ + spin_lock_irq(&i2sdrv[i2s->chip_select]->i2s_lock); + i2sdrv[i2s->chip_select]->i2s = NULL; + i2s_set_drvdata(i2s, NULL); + module_put(i2s->controller->dev.parent->driver->owner); + spin_unlock_irq(&i2sdrv[i2s->chip_select]->i2s_lock); + + kfree(i2sdrv[i2s->chip_select]); + + return 0; +} + +static const struct i2s_device_id i2s_test_prot_id_table[] = { + { "i2s_device.0", 0, 0 }, + { "i2s_device.1", 0, 0 }, + { "i2s_device.2", 0, 0 }, + { "i2s_device.3", 0, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2s, i2s_test_prot_id_table); + +static struct i2s_driver i2sdrv_i2s = { + .driver = { + .name = "i2s_test_protocol_driver", + .owner = THIS_MODULE, + }, + .probe = i2sdrv_probe, + .remove = __devexit_p(i2sdrv_remove), + .id_table = i2s_test_prot_id_table, + + /* + * NOTE: suspend/resume methods are not necessary here. + */ +}; + +static int __init i2sdrv_init(void) +{ + int status; + + status = i2s_register_driver(&i2sdrv_i2s); + if (status < 0) + printk(KERN_ERR "Unable to register i2s driver\n"); + + return status; +} +module_init(i2sdrv_init); + +static void __exit i2sdrv_exit(void) +{ + i2s_unregister_driver(&i2sdrv_i2s); +} +module_exit(i2sdrv_exit); + +MODULE_AUTHOR("Sandeep Kaushik, "); +MODULE_DESCRIPTION("Test Driver module I2S device interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/msp_i2s.c b/drivers/misc/i2s/msp_i2s.c new file mode 100644 index 00000000000..e8455e651b7 --- /dev/null +++ b/drivers/misc/i2s/msp_i2s.c @@ -0,0 +1,2045 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct regulator *msp_vape_supply; + +#define STM_MSP_NAME "STM_MSP" +#define MSP_NAME "msp" +#define DRIVER_DEBUG_PFX "MSP" +#define DRIVER_DEBUG CONFIG_STM_MSP_DEBUG +#define DRIVER_DBG "MSP" +#define NMDK_DBG /* message level */ + +extern struct driver_debug_st DBG_ST; + /* Protocol desciptors */ +static const struct msp_protocol_desc protocol_desc_tab[] = { + I2S_PROTOCOL_DESC, + PCM_PROTOCOL_DESC, + PCM_COMPAND_PROTOCOL_DESC, + AC97_PROTOCOL_DESC, + SPI_MASTER_PROTOCOL_DESC, + SPI_SLAVE_PROTOCOL_DESC, +}; + +/* Local static functions */ +static int msp_dma_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_polling_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_interrupt_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_start_dma(struct msp *msp, int transmit, dma_addr_t data, + size_t bytes); +static int configure_protocol(struct msp *msp, + struct msp_config *config); +static int configure_clock(struct msp *msp, + struct msp_config *config); +static int configure_multichannel(struct msp *msp, + struct msp_config *config); +static int stm_msp_configure_enable(struct i2s_controller *i2s_cont, + void *configuration); +static int stm_msp_transceive_data(struct i2s_controller *i2s_cont, + struct i2s_message *message); + +static int stm_msp_disable(struct msp *msp, int direction, + i2s_flag flag); +static int stm_msp_close(struct i2s_controller *i2s_cont, i2s_flag flag); +static int stm_msp_hw_status(struct i2s_controller *i2s_cont); +static dma_addr_t stm_msp_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction); + +#define I2S_DEVICE "i2s_device" +static struct i2s_algorithm i2s_algo = { + .cont_setup = stm_msp_configure_enable, + .cont_transfer = stm_msp_transceive_data, + .cont_cleanup = stm_msp_close, + .cont_hw_status = stm_msp_hw_status, + .cont_get_pointer = stm_msp_get_pointer, +}; + +/** + * stm_msp_write - writel a value to specified register + * @value: value + * @reg: pointer to register' address + * Context: atomic(can be both process and interrupt) + * Returns void. + */ +static inline void stm_msp_write(u32 value, void __iomem *reg) +{ + writel(value, reg); +} + +/** + * stm_msp_read - readl a value to specified register + * @reg: pointer to register' address + * Context: atomic(can be both process and interrupt) + * Returns u32 register's value. + */ +static inline u32 stm_msp_read(void __iomem *reg) +{ + return readl(reg); +} + +static void u8_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + message->rx_offset += 1; + *(u8 *) message->rxdata = + (u8) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rxdata += 1; + } +} + +static void u16_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + message->rx_offset += 2; + *(u16 *) message->rxdata = + (u16) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rxdata += 2; + } +} + +/** + * u32_msp_read - Msp 32bit read function. + * @xfer_data: transfer data structure. + * + * It reads 32bit data from msp receive fifo until it gets empty. + * + * Returns void. + */ +static void u32_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + *(u32 *) message->rxdata = + (u32) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rx_offset += 4; + message->rxdata += 4; + } +} +static void u8_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 1; + stm_msp_write(*(u8 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 1; + } +} + +static void u16_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 2; + stm_msp_write(*(u16 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 2; + } +} + +/** + * u32_msp_write - Msp 32bit write function. + * @xfer_data: transfer data structure. + * + * It writes 32bit data to msp transmit fifo until it gets full. + * + * Returns void. + */ +static void u32_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 4; + stm_msp_write(*(u32 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 4; + } +} + +/** + * set_transmit_protocol_descriptor - Set the Transmit Configuration register. + * @msp: main msp controller structure. + * @protocol_desc: pointer to protocol descriptor structure. + * @data_size: Run time configurable element length. + * + * It will setup transmit configuration register of msp. + * Various values related to a particular protocol can be set like, elemnet + * length, frame length, endianess etc. + * + * Returns void. + */ +static void set_transmit_protocol_descriptor(struct msp *msp, + struct msp_protocol_desc + *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= + MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1); + temp_reg |= + MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2); + if (protocol_desc->tx_element_length_1 == + protocol_desc->tx_element_length_2) { + msp->actual_data_size = + protocol_desc->tx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay); + temp_reg |= + MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + stm_msp_write(temp_reg, msp->registers + MSP_TCF); +} + +/** + * set_receive_protocol_descriptor - Set the Receive Configuration register. + * @msp: main msp controller structure. + * @protocol_desc: pointer to protocol descriptor structure. + * @data_size: Run time configurable element length. + * + * It will setup receive configuration register of msp. + * Various values related to a particular protocol can be set like, elemnet + * length, frame length, endianess etc. + * + * Returns void. + */ +static void set_receive_protocol_descriptor(struct msp *msp, + struct msp_protocol_desc + *protocol_desc, + enum msp_data_size + data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= + MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1); + temp_reg |= + MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2); + if (protocol_desc->rx_element_length_1 == + protocol_desc->rx_element_length_2) { + msp->actual_data_size = + protocol_desc->rx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay); + temp_reg |= + MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + stm_msp_write(temp_reg, msp->registers + MSP_RCF); + +} + +/** + * configure_protocol - Configures transmit and receive protocol. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will configure transmit and receive protocol decriptors. + * + * Returns error(-1) on failure else success(0). + */ +static int configure_protocol(struct msp *msp, + struct msp_config *config) +{ + int direction; + struct msp_protocol_desc *protocol_desc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + direction = config->direction; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + printk(KERN_ERR + "invalid protocol in configure_protocol()\n"); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT + || data_size > MSP_DATA_BITS_32) { + printk(KERN_ERR + "invalid data size requested in configure_protocol()\n"); + return -EINVAL; + } + + switch (direction) { + case MSP_TRANSMIT_MODE: + set_transmit_protocol_descriptor(msp, protocol_desc, data_size); + break; + case MSP_RECEIVE_MODE: + set_receive_protocol_descriptor(msp, protocol_desc, data_size); + break; + case MSP_BOTH_T_R_MODE: + set_transmit_protocol_descriptor(msp, protocol_desc, data_size); + set_receive_protocol_descriptor(msp, protocol_desc, data_size); + break; + default: + printk(KERN_ERR "Invalid direction given\n"); + return -EINVAL; + } + /* The below code is needed for both Rx and Tx path can't separate + * them. + */ + temp_reg = stm_msp_read(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(protocol_desc->tx_clock_pol); + stm_msp_write(temp_reg, msp->registers + MSP_GCR); + temp_reg = stm_msp_read(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol); + stm_msp_write(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +/** + * configure_clock - Set clock in sample rate generator. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will set the frame width and period. Also enable sample rate generator + * + * Returns error(-1) on failure else success(0). + */ +static int configure_clock(struct msp *msp, + struct msp_config *config) +{ + + u32 dummy; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + u32 bit_clock = 0; + struct msp_protocol_desc *protocol_desc = NULL; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(SRG_ENABLE))), msp->registers + MSP_GCR); + + if (config->default_protocol_desc) { + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = + config->input_clock_freq / (config->frame_freq * + (protocol_desc-> + total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = + config->input_clock_freq / (config->frame_freq * + (protocol_desc-> + total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + + break; + case MSP_AC97_PROTOCOL: + /* Not supported */ + printk(KERN_WARNING "AC97 protocol not supported\n"); + return -ENOSYS; + default: + printk(KERN_ERR "Invalid mode attempted for setting clocks\n"); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + stm_msp_write(temp_reg, msp->registers + MSP_SRG); + + /* Input clock frequency value configured is in MHz/1000 */ + bit_clock = (config->input_clock_freq * 1000)/(sck_div + 1); + + /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP */ + if (bit_clock > 19200000) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 100); + msp->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->vape_opp_constraint = 0; + } + + /* Wait a bit */ + dummy = ((stm_msp_read(msp->registers + MSP_SRG)) >> FRWID_SHIFT) & 0x0000003F; + + /* Enable clock */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | ((SRG_ENABLE))), + msp->registers + MSP_GCR); + + /* Another wait */ + dummy = + ((stm_msp_read(msp->registers + MSP_SRG)) >> FRWID_SHIFT) & + 0x0000003F; + return 0; +} + +/** + * configure_multichannel - Enable multichannel support for transmit & receive. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will enable multichannel support for transmit and receive. + * It will set Receive comparator also if configured. + * + * Returns error(-1) on failure else success(0). + */ +static int configure_multichannel(struct msp *msp, + struct msp_config *config) +{ + struct msp_protocol_desc *protocol_desc; + struct msp_multichannel_config *mult_config; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + printk(KERN_ERR + "invalid protocol in configure_protocol()\n"); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + mult_config = &config->multichannel_config; + if (true == mult_config->tx_multichannel_enable) { + if (MSP_SINGLE_PHASE == protocol_desc->tx_phase_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + tx_multichannel_enable << TMCEN_BIT) & + (0x0000020))), + msp->registers + MSP_MCR); + stm_msp_write(mult_config->tx_channel_0_enable, + msp->registers + MSP_TCE0); + stm_msp_write(mult_config->tx_channel_1_enable, + msp->registers + MSP_TCE1); + stm_msp_write(mult_config->tx_channel_2_enable, + msp->registers + MSP_TCE2); + stm_msp_write(mult_config->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + printk(KERN_ERR "Not in authorised mode\n"); + return -1; + } + } + if (true == mult_config->rx_multichannel_enable) { + if (MSP_SINGLE_PHASE == protocol_desc->rx_phase_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + rx_multichannel_enable << RMCEN_BIT) & + (0x0000001))), + msp->registers + MSP_MCR); + stm_msp_write(mult_config->rx_channel_0_enable, + msp->registers + MSP_RCE0); + stm_msp_write(mult_config->rx_channel_1_enable, + msp->registers + MSP_RCE1); + stm_msp_write(mult_config->rx_channel_2_enable, + msp->registers + MSP_RCE2); + stm_msp_write(mult_config->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + printk(KERN_ERR "Not in authorised mode\n"); + return -1; + } + if (mult_config->rx_comparison_enable_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + rx_comparison_enable_mode << RCMPM_BIT) + & (0x0000018))), + msp->registers + MSP_MCR); + + stm_msp_write(mult_config->comparison_mask, + msp->registers + MSP_RCM); + stm_msp_write(mult_config->comparison_value, + msp->registers + MSP_RCV); + + } + } + return 0; + +} + +/** + * configure_dma - configure dma channel for transmit or receive. + * @msp: msp structure + * @config: configuration structure. + * Context: process + * + * It will configure dma channels and request them in Logical mode for both + * transmit and recevie modes.It also register the respective callback handlers + * for DMA. + * + * Returns void. + */ +void configure_dma(struct msp *msp, struct msp_config *config) +{ + struct stedma40_chan_cfg *rx_dma_info = msp->dma_cfg_rx; + struct stedma40_chan_cfg *tx_dma_info = msp->dma_cfg_tx; + dma_cap_mask_t mask; + + if (config->direction == MSP_TRANSMIT_MODE + || config->direction == MSP_BOTH_T_R_MODE) { + + if (msp->tx_pipeid != NULL) { + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + + if (config->data_size == MSP_DATA_BITS_32) + tx_dma_info->src_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + tx_dma_info->src_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + tx_dma_info->src_info.data_width + = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + if (config->data_size == MSP_DATA_BITS_32) + tx_dma_info->dst_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + tx_dma_info->dst_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + tx_dma_info->dst_info.data_width + = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + msp->tx_pipeid = dma_request_channel(mask, stedma40_filter, + tx_dma_info); + } + if (config->direction == MSP_RECEIVE_MODE + || config->direction == MSP_BOTH_T_R_MODE) { + + if (msp->rx_pipeid != NULL) { + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + + if (config->data_size == MSP_DATA_BITS_32) + rx_dma_info->src_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + rx_dma_info->src_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + rx_dma_info->src_info.data_width = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + if (config->data_size == MSP_DATA_BITS_32) + rx_dma_info->dst_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + rx_dma_info->dst_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + rx_dma_info->dst_info.data_width = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + msp->rx_pipeid = dma_request_channel(mask, stedma40_filter, + rx_dma_info); + } + +} + +/** + * msp_enable - Setup the msp configuration. + * @msp: msp data contains main msp structure. + * @config: configuration structure sent by i2s client driver. + * Context: process + * + * Main msp configuring functions to configure msp in accordance with msp + * protocol descriptor, configuring msp clock,setup transfer mode selected by + * user like DMA, interrupt or polling and in the end enable RX and Tx path. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_enable(struct msp *msp, struct msp_config *config) +{ + int status = 0; + int state; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + state = msp->msp_state; + if (state == MSP_STATE_IDLE) { + if (msp->plat_init) { + status = msp->plat_init(); + if (status) { + printk(KERN_ERR "Error in msp_i2s_init," + " status is %d\n", status); + return status; + } + } + } + + /* Configure msp with protocol dependent settings */ + configure_protocol(msp, config); + configure_clock(msp, config); + if (config->multichannel_configured == 1) { + status = configure_multichannel(msp, config); + if (status) + printk(KERN_ERR "multichannel can't be configured\n"); + } + msp->work_mode = config->work_mode; + + if (msp->work_mode == MSP_DMA_MODE && !msp->dma_cfg_rx) { + switch (config->direction) { + case MSP_RECEIVE_MODE: + case MSP_BOTH_T_R_MODE: + dev_err(&msp->i2s_cont->dev, "RX DMA not available"); + return -EINVAL; + } + } + + if (msp->work_mode == MSP_DMA_MODE && !msp->dma_cfg_tx) { + switch (config->direction) { + case MSP_TRANSMIT_MODE: + case MSP_BOTH_T_R_MODE: + dev_err(&msp->i2s_cont->dev, "TX DMA not available"); + return -EINVAL; + } + } + + switch (config->direction) { + case MSP_TRANSMIT_MODE: + /*Currently they are ignored + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) | + TRANSMIT_UNDERRUN_ERR_INT | + TRANSMIT_FRAME_SYNC_ERR_INT), + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->write = u8_msp_write; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->write = u16_msp_write; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->write = u32_msp_write; + break; + } + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + break; + case MSP_RECEIVE_MODE: + /*Currently they are ignored + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) | + RECEIVE_OVERRUN_ERROR_INT | RECEIVE_FRAME_SYNC_ERR_INT, + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->read = u8_msp_read; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->read = u16_msp_read; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->read = u32_msp_read; + break; + } + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + + break; + case MSP_BOTH_T_R_MODE: + /*Currently they are ignored + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) | + RECEIVE_OVERRUN_ERROR_INT | RECEIVE_FRAME_SYNC_ERR_INT | + TRANSMIT_UNDERRUN_ERR_INT | TRANSMIT_FRAME_SYNC_ERR_INT , + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->read = u8_msp_read; + msp->write = u8_msp_write; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->read = u16_msp_read; + msp->write = u16_msp_write; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->read = u32_msp_read; + msp->write = u32_msp_write; + break; + } + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + + break; + default: + printk(KERN_ERR "Invalid direction parameter\n"); + if (msp->plat_exit) + msp->plat_exit(); + status = -EINVAL; + return status; + } + + switch (config->work_mode) { + case MSP_DMA_MODE: + msp->transfer = msp_dma_xfer; + break; + case MSP_POLLING_MODE: + msp->transfer = msp_polling_xfer; + break; + case MSP_INTERRUPT_MODE: + msp->transfer = msp_interrupt_xfer; + break; + default: + msp->transfer = NULL; + } + + stm_msp_write(config->iodelay, msp->registers + MSP_IODLY); + + /* enable frame generation logic */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (FRAME_GEN_ENABLE)), msp->registers + MSP_GCR); + + return status; +} + +/** + * flush_rx_fifo - Flush Rx fifo MSP controller. + * @msp: msp structure. + * + * This function flush the rx fifo of msp controller. + * + * Returns error(-1) in case of failure else success(0) + */ +static void flush_rx_fifo(struct msp *msp) +{ + u32 dummy = 0; + u32 limit = 32; + u32 cur = stm_msp_read(msp->registers + MSP_GCR); + stm_msp_write(cur | RX_ENABLE, msp->registers + MSP_GCR); + while (!(stm_msp_read(msp->registers + MSP_FLR) & RX_FIFO_EMPTY) + && limit--) { + dummy = stm_msp_read(msp->registers + MSP_DR); + } + stm_msp_write(cur, msp->registers + MSP_GCR); +} + +/** + * flush_tx_fifo - Flush Tx fifo MSP controller. + * @msp: msp structure. + * + * This function flush the tx fifo using test intergration register to read data + * from tx fifo directly. + * + * Returns error(-1) in case of failure else success(0) + */ +static void flush_tx_fifo(struct msp *msp) +{ + u32 dummy = 0; + u32 limit = 32; + u32 cur = stm_msp_read(msp->registers + MSP_GCR); + stm_msp_write(cur | TX_ENABLE, msp->registers + MSP_GCR); + stm_msp_write(0x3, msp->registers + MSP_ITCR); + while (!(stm_msp_read(msp->registers + MSP_FLR) & TX_FIFO_EMPTY) + && limit--) { + dummy = stm_msp_read(msp->registers + MSP_TSTDR); + } + stm_msp_write(0x0, msp->registers + MSP_ITCR); + stm_msp_write(cur, msp->registers + MSP_GCR); +} + +/** + * stm_msp_configure_enable - configures and enables the MSP controller. + * @i2s_cont: i2s controller sent by i2s device. + * @configuration: specifies the configuration parameters. + * + * This function configures the msp controller with the client configuration. + * + * Returns error(-1) in case of failure else success(0) + */ +static int stm_msp_configure_enable(struct i2s_controller *i2s_cont, + void *configuration) +{ + u32 old_reg; + u32 new_reg; + u32 mask; + int res; + struct msp_config *config = + (struct msp_config *)configuration; + struct msp *msp = (struct msp *)i2s_cont->data; + + if (in_interrupt()) { + printk(KERN_ERR + "can't call configure_enable in interrupt context\n"); + return -1; + } + + /* Two simultanous configuring msp is avoidable */ + down(&msp->lock); + switch (msp->users) { + case 0: + res = regulator_enable(msp_vape_supply); + if (res != 0) { + dev_err(&msp->i2s_cont->dev, + "Failed to enable regulator\n"); + up(&msp->lock); + return res; + } + + clk_enable(msp->clk); + msp->direction = config->direction; + break; + case 1: + if (msp->direction == MSP_BOTH_T_R_MODE || + config->direction == msp->direction || + config->direction == MSP_BOTH_T_R_MODE) { + dev_notice(&i2s_cont->dev, "%s: MSP in use in the " + "desired direction.\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->direction = MSP_BOTH_T_R_MODE; + break; + default: + dev_notice(&i2s_cont->dev, "%s: MSP in use in both " + "directions.\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->users++; + + /* First do the global config register */ + mask = + RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK | + TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = + (config->tx_clock_sel | config->rx_clock_sel | config-> + rx_frame_sync_pol | config->tx_frame_sync_pol | config-> + rx_frame_sync_sel | config->tx_frame_sync_sel | config-> + rx_fifo_config | config->tx_fifo_config | config-> + srg_clock_sel | config->loopback_enable | config->tx_data_enable); + + old_reg = stm_msp_read(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + stm_msp_write(new_reg, msp->registers + MSP_GCR); + + if (msp_enable(msp, config) != 0) { + printk(KERN_ERR "error enabling MSP\n"); + return -EBUSY; + } + if (config->loopback_enable & 0x80) + msp->loopback_enable = 1; + /*Sometimes FIFO doesn't gets empty hence limit is provided */ + flush_tx_fifo(msp); + /*This has been added in order to fix fifo flush problem + When last xfer occurs some data remains in fifo. In order to + flush that data delay is needed */ + msleep(10); + /* wait for fifo to flush */ + flush_rx_fifo(msp); + + /* RX_BUSY take a while to clear */ + msleep(10); + + msp->msp_state = MSP_STATE_CONFIGURED; + up(&msp->lock); + return 0; +} + +static int msp_start_dma(struct msp *msp, int transmit, dma_addr_t data, + size_t bytes) +{ + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(data)), bytes, + offset_in_page(data)); + sg_dma_address(&sg) = data; + sg_dma_len(&sg) = bytes; + + if (transmit) { + if (!msp->tx_pipeid) + return -EINVAL; + + desc = msp->tx_pipeid->device-> + device_prep_slave_sg(msp->tx_pipeid, + &sg, 1, DMA_TO_DEVICE, + DMA_PREP_INTERRUPT + | DMA_CTRL_ACK); + if (!desc) + return -ENOMEM; + + desc->callback = msp->xfer_data.tx_handler; + desc->callback_param = msp->xfer_data.tx_callback_data; + desc->tx_submit(desc); + dma_async_issue_pending(msp->tx_pipeid); + } else { + if (!msp->rx_pipeid) + return -EINVAL; + + desc = msp->rx_pipeid->device-> + device_prep_slave_sg(msp->rx_pipeid, + &sg, 1, DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT + | DMA_CTRL_ACK); + if (!desc) + return -EBUSY; + + desc->callback = msp->xfer_data.rx_handler; + desc->callback_param = msp->xfer_data.rx_callback_data; + desc->tx_submit(desc); + dma_async_issue_pending(msp->rx_pipeid); + } + + return 0; +} + +static int msp_single_dma_tx(struct msp *msp, dma_addr_t data, size_t bytes) +{ + int status; + status = msp_start_dma(msp, 1, data, bytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | (TX_ENABLE)), + msp->registers + MSP_GCR); + return status; +} + +static int msp_single_dma_rx(struct msp *msp, dma_addr_t data, size_t bytes) +{ + int status; + status = msp_start_dma(msp, 0, data, bytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | (RX_ENABLE)), + msp->registers + MSP_GCR); + return status; +} + +static void msp_cyclic_dma_start(struct msp *msp, + struct scatterlist *sg, + int sg_len, + enum dma_data_direction direction) +{ + struct stedma40_cyclic_desc *cdesc; + int ret; + struct dma_chan *pipeid = (direction == DMA_TO_DEVICE) ? + msp->tx_pipeid : + msp->rx_pipeid; + + cdesc = stedma40_cyclic_prep_sg(pipeid, + sg, + sg_len, + direction, + DMA_PREP_INTERRUPT); + if (IS_ERR(cdesc)) { + pr_err("%s: Error: stedma40_cyclic_prep_sg failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return; + } + + cdesc->period_callback = (direction == DMA_TO_DEVICE) ? + msp->xfer_data.tx_handler : + msp->xfer_data.rx_handler; + cdesc->period_callback_param = (direction == DMA_TO_DEVICE) ? + msp->xfer_data.tx_callback_data : + msp->xfer_data.rx_callback_data; + + ret = stedma40_cyclic_start(pipeid); + if (ret) { + pr_err("%s: stedma40_cyclic_start failed (%d)!\n", __func__, ret); + goto free; + } + + msp->infinite = true; + + return; + +free: + stedma40_cyclic_free(pipeid); +} + +/* Legacy function. Used by HATS driver. */ +static void msp_loopback_inf_start_dma(struct msp *msp, + dma_addr_t data, + size_t bytes) +{ + struct stedma40_cyclic_desc *rxcdesc; + struct stedma40_cyclic_desc *txcdesc; + struct scatterlist rxsg[2]; + struct scatterlist txsg[2]; + size_t len = bytes >> 1; + int ret; + + sg_init_table(rxsg, ARRAY_SIZE(rxsg)); + sg_init_table(txsg, ARRAY_SIZE(txsg)); + + sg_dma_len(&rxsg[0]) = len; + sg_dma_len(&rxsg[1]) = len; + sg_dma_len(&txsg[0]) = len; + sg_dma_len(&txsg[1]) = len; + + sg_dma_address(&rxsg[0]) = data; + sg_dma_address(&rxsg[1]) = data + len; + + sg_dma_address(&txsg[0]) = data + len; + sg_dma_address(&txsg[1]) = data; + + rxcdesc = stedma40_cyclic_prep_sg(msp->rx_pipeid, + rxsg, ARRAY_SIZE(rxsg), + DMA_FROM_DEVICE, 0); + if (IS_ERR(rxcdesc)) + return; + + txcdesc = stedma40_cyclic_prep_sg(msp->tx_pipeid, + txsg, ARRAY_SIZE(txsg), + DMA_TO_DEVICE, 0); + if (IS_ERR(txcdesc)) + goto free_rx; + + ret = stedma40_cyclic_start(msp->rx_pipeid); + if (ret) + goto free_tx; + + ret = stedma40_cyclic_start(msp->tx_pipeid); + if (ret) + goto stop_rx; + + msp->infinite = true; + + return; + +stop_rx: + stedma40_cyclic_stop(msp->rx_pipeid); +free_tx: + stedma40_cyclic_free(msp->tx_pipeid); +free_rx: + stedma40_cyclic_free(msp->rx_pipeid); +} + +/** + * msp_dma_xfer - Handles DMA transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: process + * + * This will first check whether data buffer is dmaable or not. + * Call dma_map_single apis etc to make it dmaable dma. Starts the dma transfer + * for TX and RX parallely and wait for it to get completed. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_dma_xfer(struct msp *msp, struct i2s_message *msg) +{ + int status = 0; + + switch (msg->i2s_transfer_mode) { + default: + case I2S_TRANSFER_MODE_SINGLE_DMA: + if (msg->i2s_direction == I2S_DIRECTION_TX || + msg->i2s_direction == I2S_DIRECTION_BOTH) + if (msg->txdata && (msg->txbytes > 0)) { + if (!msg->dma_flag) + msg->txdata = + (void *)dma_map_single(NULL, + msg->txdata, + msg->txbytes, + DMA_TO_DEVICE); + status = msp_single_dma_tx(msp, + (dma_addr_t)msg->txdata, + msg->txbytes); + } + if (msg->i2s_direction == I2S_DIRECTION_RX || + msg->i2s_direction == I2S_DIRECTION_BOTH) + if (msg->rxdata && (msg->rxbytes > 0)) { + if (!msg->dma_flag) + msg->rxdata = + (void *)dma_map_single(NULL, + msg->rxdata, + msg->rxbytes, + DMA_FROM_DEVICE + ); + status = msp_single_dma_rx(msp, + (dma_addr_t)msg->rxdata, + msg->rxbytes); + } + break; + + case I2S_TRANSFER_MODE_CYCLIC_DMA: + if (msg->i2s_direction == I2S_DIRECTION_TX) { + msp_cyclic_dma_start(msp, + msg->sg, + msg->sg_len, + DMA_TO_DEVICE); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), + msp->registers + MSP_GCR); + } else { + msp_cyclic_dma_start(msp, + msg->sg, + msg->sg_len, + DMA_FROM_DEVICE); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), + msp->registers + MSP_GCR); + } + break; + + case I2S_TRANSFER_MODE_INF_LOOPBACK: + msp_loopback_inf_start_dma(msp, + (dma_addr_t)msg->rxdata, + msg->rxbytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), + msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), + msp->registers + MSP_GCR); + break; + } + + return status; +} + +#if 0 +/** + * msp_handle_irq - Interrupt handler routine. + * @irq: irq no. + * @dev_id: device structure registered in request irq. + * + * Returns error(-1) on failure else success(0). + */ +static irqreturn_t msp_handle_irq(int irq, void *dev_id) +{ + u32 irq_status; + struct msp *msp = (struct msp *)dev_id; + struct i2s_message *message = &msp->xfer_data.message; + u32 irq_mask = 0; + irq_status = stm_msp_read(msp->registers + MSP_MIS); + irq_mask = stm_msp_read(msp->registers + MSP_IMSC); +/* Disable the interrupt to prevent immediate recurrence */ + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) & ~irq_status, + msp->registers + MSP_IMSC); + +/* Clear the interrupt */ + stm_msp_write(irq_status, msp->registers + MSP_ICR); +/* Check for an error condition */ + msp->msp_io_error = irq_status & (RECEIVE_OVERRUN_ERROR_INT | + RECEIVE_FRAME_SYNC_ERR_INT | + TRANSMIT_UNDERRUN_ERR_INT | + TRANSMIT_FRAME_SYNC_ERR_INT); + + /*Currently they are ignored */ + if (irq_status & RECEIVE_OVERRUN_ERROR_INT) + ; + if (irq_status & TRANSMIT_UNDERRUN_ERR_INT) + ; + + /* This code has been added basically to support loopback mode + * Basically Transmit interrupt is not disabled even after its + * completion so that receive fifo gets an additional interrupt + */ + if (irq_mask & (RECEIVE_SERVICE_INT) + && (irq_mask & (TRANSMIT_SERVICE_INT)) && (msp->loopback_enable)) { + if (msp->read) + msp->read(&msp->xfer_data); + if (msp->write) + msp->write(&msp->xfer_data); + if (message->rx_offset >= message->rxbytes) { + if (msp->xfer_data.rx_handler) + msp->xfer_data.rx_handler(msp-> + xfer_data. + rx_callback_data, + message->rx_offset); + msp->xfer_data.rx_handler = NULL; + return IRQ_HANDLED; + } + + if (message->tx_offset >= message->txbytes) { + if (msp->xfer_data.tx_handler) + msp->xfer_data.tx_handler(msp->xfer_data. + tx_callback_data, + message->tx_offset); + msp->xfer_data.tx_handler = NULL; + } + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + return IRQ_HANDLED; + } + + if (irq_status & RECEIVE_SERVICE_INT) { + if (msp->read) + msp->read(&msp->xfer_data); + if (message->rx_offset >= message->rxbytes) { + irq_mask &= ~RECEIVE_SERVICE_INT; + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + if (msp->xfer_data.rx_handler) + msp->xfer_data.rx_handler(msp-> + xfer_data. + rx_callback_data, + message->rx_offset); + if (!(irq_status & TRANSMIT_SERVICE_INT)) + return IRQ_HANDLED; + } + } + if (irq_status & TRANSMIT_SERVICE_INT) { + if (msp->write) + msp->write(&msp->xfer_data); + if (message->tx_offset >= message->txbytes) { + irq_mask &= ~TRANSMIT_SERVICE_INT; + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + if (msp->xfer_data.tx_handler) + msp->xfer_data.tx_handler(msp->xfer_data. + tx_callback_data, + message->tx_offset); + return IRQ_HANDLED; + } + } + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + return IRQ_HANDLED; + +} +#endif + +/** + * msp_interrupt_xfer - Handles Interrupt transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: Process or interrupt. + * + * This implements transfer and receive functions used in interrupt mode. + * This can be used in interrupt context if a callback handler is registered + * by client driver. This has been to improve performance in interrupt mode. + * Hence can't use sleep in this function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_interrupt_xfer(struct msp *msp, struct i2s_message *msg) +{ + struct i2s_message *message; + u32 irq_mask = 0; + + if (msg->i2s_transfer_mode != I2S_TRANSFER_MODE_NON_DMA) + return -EINVAL; + + if (msg->txbytes) { + msp->xfer_data.message.txbytes = msg->txbytes; + msp->xfer_data.message.txdata = msg->txdata; + msp->xfer_data.message.tx_offset = 0; + } + if (msg->rxbytes) { + msp->xfer_data.message.rxbytes = msg->rxbytes; + msp->xfer_data.message.rxdata = msg->rxdata; + msp->xfer_data.message.rx_offset = 0; + } + message = &msp->xfer_data.message; + if ((message->txdata == NULL || message->txbytes == 0) + && (message->rxdata == NULL || message->rxbytes == 0)) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + + msp->msp_io_error = 0; + + if (message->tx_offset < message->txbytes) { + irq_mask |= TRANSMIT_SERVICE_INT; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + } + if (message->rx_offset < message->rxbytes) { + irq_mask |= RECEIVE_SERVICE_INT; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) | + irq_mask), msp->registers + MSP_IMSC); + return 0; +} + +/** + * func_notify_timer - Handles Polling hang issue over i2s bus. + * @data: main msp data address + * Context: Interrupt. + * + * This is used to handle error condition in transfer and receive function used + * in polling mode. + * Sometimes due to passing wrong protocol desc , polling transfer may hang. + * To prevent this, timer is added. + * + * Returns void. + */ +static void func_notify_timer(unsigned long data) +{ + struct msp *msp = (struct msp *)data; + if (msp->polling_flag) { + msp->msp_io_error = 1; + printk(KERN_ERR + "Polling is taking two much time, may be it got hang\n"); + del_timer(&msp->notify_timer); + } +} + +/** + * msp_polling_xfer - Handles Polling transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: Process. + * + * This implements transfer and receive functions used in polling mode. This is + * blocking fucntion. + * It is recommended to use interrupt or dma mode for better performance rather + * than the polling mode. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_polling_xfer(struct msp *msp, struct i2s_message *msg) +{ + struct i2s_message *message; + u32 time_expire = 0; + u32 tr_ex = 0, rr_ex = 0; + u32 msec_jiffies = 0; + + if (msg->i2s_transfer_mode != I2S_TRANSFER_MODE_NON_DMA) + return -EINVAL; + + if (msg->txbytes) { + msp->xfer_data.message.txbytes = msg->txbytes; + msp->xfer_data.message.txdata = msg->txdata; + msp->xfer_data.message.tx_offset = 0; + tr_ex = msg->txbytes; + } + if (msg->rxbytes) { + msp->xfer_data.message.rxbytes = msg->rxbytes; + msp->xfer_data.message.rxdata = msg->rxdata; + msp->xfer_data.message.rx_offset = 0; + rr_ex = msg->rxbytes; + } + message = &msp->xfer_data.message; + time_expire = (tr_ex + rr_ex) / 1024; + if (!time_expire) + msec_jiffies = 500; + else + msec_jiffies = time_expire * 500; + msp->notify_timer.expires = jiffies + msecs_to_jiffies(msec_jiffies); + down(&msp->lock); + if (message->txdata == NULL && message->txbytes > 0) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + + if (message->rxdata == NULL && message->rxbytes > 0) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + msp->msp_io_error = 0; + msp->polling_flag = 1; + add_timer(&msp->notify_timer); + while (message->tx_offset < message->txbytes + || message->rx_offset < message->rxbytes) { + if (msp->msp_io_error) + break; + if (msp->read) + msp->read(&msp->xfer_data); + if (msp->write) + msp->write(&msp->xfer_data); + } + msp->polling_flag = 0; + del_timer(&msp->notify_timer); + up(&msp->lock); + return message->txbytes + message->rxbytes; +} + +/** + * stm_msp_transceive_data - 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 function is registered over i2s_xfer funtions. It will handle main i2s + * transfer over i2s bus in various modes.It call msp transfer function on which + * suitable transfer function is already registered i.e dma ,interrupt or + * polling function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int stm_msp_transceive_data(struct i2s_controller *i2s_cont, + struct i2s_message *message) +{ + int status = 0; + struct msp *msp = (struct msp *)i2s_cont->data; + + if (!message || (msp->msp_state == MSP_STATE_IDLE)) { + printk(KERN_ERR "Message is NULL\n"); + return -EPERM; + } + + msp->msp_state = MSP_STATE_RUN; + if (msp->transfer) + status = msp->transfer(msp, message); + + if (msp->msp_state == MSP_STATE_RUN) + msp->msp_state = MSP_STATE_CONFIGURED; + + return status; +} + +/** + * msp_disable_receive - Disable receive functionality. + * @msp: main msp structure. + * Context: process. + * + * This function will disable msp controller's receive functionality like dma, + * interrupt receive data buffer all are disabled. + * + * Returns void. + */ +static void msp_disable_receive(struct msp *msp) +{ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~RX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_DMACR) & + (~RX_DMA_ENABLE)), msp->registers + MSP_DMACR); + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) & + (~ + (RECEIVE_SERVICE_INT | + RECEIVE_OVERRUN_ERROR_INT))), + msp->registers + MSP_IMSC); + msp->xfer_data.message.rxbytes = 0; + msp->xfer_data.message.rx_offset = 0; + msp->xfer_data.message.rxdata = NULL; + msp->read = NULL; + +} + +/** + * msp_disable_transmit - Disable transmit functionality. + * @msp: main msp structure. + * Context: process. + * + * This function will disable msp controller's transmit functionality like dma, + * interrupt transmit data buffer all are disabled. + * + * Returns void. + */ +static void msp_disable_transmit(struct msp *msp) +{ + + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_DMACR) & + (~TX_DMA_ENABLE)), msp->registers + MSP_DMACR); + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) & + (~ + (TRANSMIT_SERVICE_INT | + TRANSMIT_UNDERRUN_ERR_INT))), + msp->registers + MSP_IMSC); + msp->xfer_data.message.txbytes = 0; + msp->xfer_data.message.tx_offset = 0; + msp->xfer_data.message.txdata = NULL; + msp->write = NULL; + +} + +/** + * stm_msp_disable - disable the given msp controller + * @msp: specifies the msp contoller data + * @direction: specifies the transmit/receive direction + * @flag: It indicates the functionality that needs to be disabled. + * + * Returns error(-1) in case of failure else success(0) + */ +static int stm_msp_disable(struct msp *msp, int direction, i2s_flag flag) +{ + int limit = 32; + u32 dummy = 0; + int status = 0; + if (! + (stm_msp_read(msp->registers + MSP_GCR) & + ((TX_ENABLE | RX_ENABLE)))) { + return 0; + } + if (msp->work_mode == MSP_DMA_MODE) { + if (flag == DISABLE_ALL || flag == DISABLE_TRANSMIT) { + if (msp->tx_pipeid != NULL) { + if (msp->infinite) { + stedma40_cyclic_stop(msp->tx_pipeid); + stedma40_cyclic_free(msp->tx_pipeid); + } + msp->tx_pipeid->device-> + device_control(msp->tx_pipeid, + DMA_TERMINATE_ALL, 0); + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + } + if ((flag == DISABLE_ALL || flag == DISABLE_RECEIVE)) { + if (msp->rx_pipeid != NULL) { + if (msp->infinite) { + stedma40_cyclic_stop(msp->rx_pipeid); + stedma40_cyclic_free(msp->rx_pipeid); + } + + msp->rx_pipeid->device-> + device_control(msp->rx_pipeid, + DMA_TERMINATE_ALL, 0); + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + } + + msp->infinite = false; + } + if (flag == DISABLE_TRANSMIT) + msp_disable_transmit(msp); + else if (flag == DISABLE_RECEIVE) + msp_disable_receive(msp); + else { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (LOOPBACK_MASK)), msp->registers + MSP_GCR); + /* Flush Tx fifo */ + while ((! + (stm_msp_read(msp->registers + MSP_FLR) & + TX_FIFO_EMPTY)) && limit--) + dummy = stm_msp_read(msp->registers + MSP_DR); + + /* Disable Transmit channel */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + limit = 32; + /* Flush Rx Fifo */ + while ((! + (stm_msp_read(msp->registers + MSP_FLR) & + RX_FIFO_EMPTY)) && limit--) + dummy = stm_msp_read(msp->registers + MSP_DR); + /* Disable Loopback and Receive channel */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + /*This has been added in order to fix fifo flush problem + When last xfer occurs some data remains in fifo. In order to + flush that data delay is needed */ + msleep(10); + msp_disable_transmit(msp); + msp_disable_receive(msp); + + } + + /* disable sample rate and frame generators */ + if (flag == DISABLE_ALL) { + msp->msp_state = MSP_STATE_IDLE; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + memset(&msp->xfer_data, 0, sizeof(struct trans_data)); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + printk(KERN_ERR "Error in msp_i2s_exit\n"); + if (msp->work_mode == MSP_POLLING_MODE + && msp->msp_state == MSP_STATE_RUN) { + up(&msp->lock); + } + msp->transfer = NULL; + stm_msp_write(0, msp->registers + MSP_GCR); + stm_msp_write(0, msp->registers + MSP_TCF); + stm_msp_write(0, msp->registers + MSP_RCF); + stm_msp_write(0, msp->registers + MSP_DMACR); + stm_msp_write(0, msp->registers + MSP_SRG); + stm_msp_write(0, msp->registers + MSP_MCR); + stm_msp_write(0, msp->registers + MSP_RCM); + stm_msp_write(0, msp->registers + MSP_RCV); + stm_msp_write(0, msp->registers + MSP_TCE0); + stm_msp_write(0, msp->registers + MSP_TCE1); + stm_msp_write(0, msp->registers + MSP_TCE2); + stm_msp_write(0, msp->registers + MSP_TCE3); + stm_msp_write(0, msp->registers + MSP_RCE0); + stm_msp_write(0, msp->registers + MSP_RCE1); + stm_msp_write(0, msp->registers + MSP_RCE2); + stm_msp_write(0, msp->registers + MSP_RCE3); + } + return status; +} + +/** + * stm_msp_close - 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 + * + * It will call msp_disable and reset the msp configuration. Disables Rx and Tx + * channels, free gpio irqs and interrupt pins. + * Called by i2s client driver to indicate the completion of use of i2s bus. + * It is registered on i2s_close function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int stm_msp_close(struct i2s_controller *i2s_cont, i2s_flag flag) +{ + int status = 0; + struct msp *msp = (struct msp *)i2s_cont->data; + down(&msp->lock); + if (msp->users == 0) { + pr_err("MSP already closed!\n"); + status = -EINVAL; + goto end; + } + dev_dbg(&i2s_cont->dev, "%s: users = %d, flag = %d.\n", + __func__, msp->users, flag); + /* We need to call it twice for DISABLE_ALL*/ + msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1; + if (msp->users) + status = stm_msp_disable(msp, MSP_BOTH_T_R_MODE, flag); + else { + status = stm_msp_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL); + clk_disable(msp->clk); + status = regulator_disable(msp_vape_supply); + if (status != 0) { + dev_err(&msp->i2s_cont->dev, + "Failed to disable regulator\n"); + clk_enable(msp->clk); + goto end; + } + } + if (status) + goto end; + if (msp->users) + msp->direction = flag == DISABLE_TRANSMIT ? + MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE; + + if (msp->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->vape_opp_constraint = 0; + } +end: + up(&msp->lock); + return status; + +} + +static int stm_msp_hw_status(struct i2s_controller *i2s_cont) +{ + struct msp *msp = (struct msp *)i2s_cont->data; + + int status = stm_msp_read(msp->registers + MSP_RIS) & 0xee; + if (status) + stm_msp_write(status, msp->registers + MSP_ICR); + + return status; +} + +static dma_addr_t stm_msp_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction) +{ + struct msp *msp = (struct msp *)i2s_cont->data; + return (i2s_direction == I2S_DIRECTION_TX) ? + stedma40_get_src_addr(msp->tx_pipeid) : + stedma40_get_dst_addr(msp->rx_pipeid); +} + + /*Platform driver's functions */ +/** + * msp_probe - Probe function + * @pdev: platform device structure. + * Context: process + * + * Probe function of msp platform driver.Handles allocation of memory and irq + * resource. It creates i2s_controller and one i2s_device per msp controller. + * + * Returns error(-1) in case of failure or success(0). + */ +int msp_probe(struct platform_device *pdev) +{ + int status = 0; + struct device *dev; + s16 platform_num = 0; + struct resource *res = NULL; + int irq; + struct i2s_controller *i2s_cont; + struct msp_i2s_platform_data *platform_data; + struct msp *msp; + + if (!pdev) + return -EPERM; + msp = kzalloc(sizeof(*msp), GFP_KERNEL); + + platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data; + + msp->id = platform_data->id; + msp->plat_init = platform_data->msp_i2s_init; + msp->plat_exit = platform_data->msp_i2s_exit; + + msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; + msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; + + dev = &pdev->dev; + platform_num = msp->id - 1; + + sema_init(&msp->lock, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "probe - MEM resources not defined\n"); + status = -EINVAL; + goto free_msp; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + status = -EINVAL; + goto free_msp; + } + msp->irq = irq; + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) { + status = -EINVAL; + goto free_msp; + } + + msp_vape_supply = regulator_get(NULL, "v-ape"); + if (IS_ERR(msp_vape_supply)) { + status = PTR_ERR(msp_vape_supply); + printk(KERN_WARNING "msp i2s : failed to get v-ape supply\n"); + goto free_irq; + } + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(msp->clk)) { + status = PTR_ERR(msp->clk); + goto free_irq; + } + + init_timer(&msp->notify_timer); + msp->notify_timer.expires = jiffies + msecs_to_jiffies(1000); + msp->notify_timer.function = func_notify_timer; + msp->notify_timer.data = (unsigned long)msp; + + msp->rx_pipeid = NULL; + msp->tx_pipeid = NULL; + msp->read = NULL; + msp->write = NULL; + msp->transfer = NULL; + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + dev_set_drvdata(&pdev->dev, msp); + /* I2S Controller is allocated and added in I2S controller class. */ + i2s_cont = + (struct i2s_controller *)kzalloc(sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + dev_err(&pdev->dev, "i2s controller alloc failed \n"); + status = -EINVAL; + goto del_timer; + } + i2s_cont->dev.parent = dev; + i2s_cont->algo = &i2s_algo; + i2s_cont->data = (void *)msp; + i2s_cont->id = platform_num; + snprintf(i2s_cont->name, sizeof(i2s_cont->name), + "MSP_I2S.%04x", platform_num); + + status = i2s_add_controller(i2s_cont); + if (status) { + dev_err(&pdev->dev, "i2s add controller failed (%d)\n", status); + goto free_cont; + } + msp->i2s_cont = i2s_cont; + return status; +free_cont: + kfree(msp->i2s_cont); +del_timer: + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); +free_irq: + iounmap(msp->registers); +free_msp: + kfree(msp); + return status; +} + +/** + * msp_remove - remove function + * @pdev: platform device structure. + * Context: process + * + * remove function of msp platform driver.Handles dellocation of memory and irq + * resource. It deletes i2s_controller and one i2s_device per msp controller + * created in msp_probe. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_remove(struct platform_device *pdev) +{ + struct msp *msp = + (struct msp *)dev_get_drvdata(&pdev->dev); + int status = 0; + i2s_del_controller(msp->i2s_cont); + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); + iounmap(msp->registers); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "msp_i2s"); + regulator_put(msp_vape_supply); + kfree(msp); + return status; +} +#ifdef CONFIG_PM +/** + * msp_suspend - MSP suspend function registered with PM framework. + * @pdev: Reference to platform device structure of the device + * @state: power mgmt state. + * + * This function is invoked when the system is going into sleep, called + * by the power management framework of the linux kernel. + * Nothing is required as controller is configured with every transfer. + * It is assumed that no active tranfer is in progress at this time. + * Client driver should make sure of this. + * + */ + +int msp_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct msp *msp = + (struct msp *)dev_get_drvdata(&pdev->dev); + + down(&msp->lock); + if (msp->users > 0) { + up(&msp->lock); + return -EBUSY; + } + up(&msp->lock); + + return 0; +} +/** + * msp_resume - MSP Resume function registered with PM framework. + * @pdev: Reference to platform device structure of the device + * + * This function is invoked when the system is coming out of sleep, called + * by the power management framework of the linux kernel. + * Nothing is required. + * + */ + +int msp_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define msp_suspend NULL +#define msp_resume NULL +#endif + +static struct platform_driver msp_i2s_driver = { + .probe = msp_probe, + .remove = msp_remove, + .suspend = msp_suspend, + .resume = msp_resume, + .driver = { + .owner = THIS_MODULE, + .name = "MSP_I2S", + }, +}; + +static int __init stm_msp_mod_init(void) +{ + return platform_driver_register(&msp_i2s_driver); +} + +static void __exit stm_msp_exit(void) +{ + platform_driver_unregister(&msp_i2s_driver); + return; +} + +module_init(stm_msp_mod_init); +module_exit(stm_msp_exit); + +MODULE_AUTHOR("Sandeep Kaushik"); +MODULE_DESCRIPTION("STM MSP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/msp_i2s.h b/drivers/misc/i2s/msp_i2s.h new file mode 100644 index 00000000000..2ac86e7700e --- /dev/null +++ b/drivers/misc/i2s/msp_i2s.h @@ -0,0 +1,362 @@ +/*----------------------------------------------------------------------------------*/ +/* 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 FITNESS */ +/* 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 . */ +/*----------------------------------------------------------------------------------*/ + + +#ifndef STM_MSP_HEADER +#define STM_MSP_HEADER + +#define MSP_DR 0x00 +#define MSP_GCR 0x04 +#define MSP_TCF 0x08 +#define MSP_RCF 0x0c +#define MSP_SRG 0x10 +#define MSP_FLR 0x14 +#define MSP_DMACR 0x18 + +#define MSP_IMSC 0x20 +#define MSP_RI 0x24 +#define MSP_MIS 0x28 +#define MSP_ICR 0x2c +#define MSP_MCR 0x30 +#define MSP_RCV 0x34 +#define MSP_RCM 0x38 + +#define MSP_TCE0 0x40 +#define MSP_TCE1 0x44 +#define MSP_TCE2 0x48 +#define MSP_TCE3 0x4c + +#define MSP_RCE0 0x60 +#define MSP_RCE1 0x64 +#define MSP_RCE2 0x68 +#define MSP_RCE3 0x6c + +#define MSP_ITCR 0x80 +#define MSP_ITIP 0x84 +#define MSP_ITOP 0x88 +#define MSP_TSTDR 0x8c + +#define MSP_PID0 0xfe0 +#define MSP_PID1 0xfe4 +#define MSP_PID2 0xfe8 +#define MSP_PID3 0xfec + +#define MSP_CID0 0xff0 +#define MSP_CID1 0xff4 +#define MSP_CID2 0xff8 +#define MSP_CID3 0xffc + + +/* Single or dual phase mode */ +enum +{ + MSP_SINGLE_PHASE, + MSP_DUAL_PHASE +}; + + +/* Transmit/Receive shifter status +-----------------------------------*/ +enum +{ + MSP_SxHIFTER_IDLE = 0, + MSP_SHIFTER_WORKING = 1 +}; + + +/* Transmit/Receive FIFO status +---------------------------------*/ +enum +{ + MSP_FIFO_FULL, + MSP_FIFO_PART_FILLED, + MSP_FIFO_EMPTY +}; + + +/* Frame length +------------------*/ +enum +{ + MSP_FRAME_LENGTH_1 = 0, + MSP_FRAME_LENGTH_2 = 1, + MSP_FRAME_LENGTH_4 = 3, + MSP_FRAME_LENGTH_8 = 7, + MSP_FRAME_LENGTH_12 = 11, + MSP_FRAME_LENGTH_16 = 15, + MSP_FRAME_LENGTH_20 = 19, + MSP_FRAME_LENGTH_32 = 31, + MSP_FRAME_LENGTH_48 = 47, + MSP_FRAME_LENGTH_64 = 63 +}; + +/* Element length */ +enum +{ + MSP_ELEM_LENGTH_8 = 0, + MSP_ELEM_LENGTH_10 = 1, + MSP_ELEM_LENGTH_12 = 2, + MSP_ELEM_LENGTH_14 = 3, + MSP_ELEM_LENGTH_16 = 4, + MSP_ELEM_LENGTH_20 = 5, + MSP_ELEM_LENGTH_24 = 6, + MSP_ELEM_LENGTH_32 = 7 +}; + + +/* Data delay (in bit clock cycles) +---------------------------------------*/ +enum +{ + MSP_DELAY_0 = 0, + MSP_DELAY_1 = 1, + MSP_DELAY_2 = 2, + MSP_DELAY_3 = 3 +}; + + +/* Configurations of clocks (transmit, receive or sample rate generator) +-------------------------------------------------------------------------*/ +enum +{ + MSP_RISING_EDGE = 0, + MSP_FALLING_EDGE = 1 +}; + +/* Protocol dependant parameters list */ +struct msp_protocol_desc +{ + u32 phase_mode; + u32 frame_len_1; + u32 frame_len_2; + u32 element_len_1; + u32 element_len_2; + u32 data_delay; + u32 tx_clock_edge; + u32 rx_clock_edge; +}; +#define RX_ENABLE_MASK 0x00000001 +#define RX_FIFO_ENABLE_MASK 0x00000002 +#define RX_FRAME_SYNC_MASK 0x00000004 +#define DIRECT_COMPANDING_MASK 0x00000008 +#define RX_SYNC_SEL_MASK 0x00000010 +#define RX_CLK_POL_MASK 0x00000020 +#define RX_CLK_SEL_MASK 0x00000040 +#define LOOPBACK_MASK 0x00000080 +#define TX_ENABLE_MASK 0x00000100 +#define TX_FIFO_ENABLE_MASK 0x00000200 +#define TX_FRAME_SYNC_MASK 0x00000400 +#define TX_MSP_TDR_TSR 0x00000800 +#define TX_SYNC_SEL_MASK 0x00001800 +#define TX_CLK_POL_MASK 0x00002000 +#define TX_CLK_SEL_MASK 0x00004000 +#define TX_EXTRA_DELAY_MASK 0x00008000 +#define SRG_ENABLE_MASK 0x00010000 +#define SRG_CLK_POL_MASK 0x00020000 +#define SRG_CLK_SEL_MASK 0x000C0000 +#define FRAME_GEN_EN_MASK 0x00100000 +#define SPI_CLK_MODE_MASK 0x00600000 +#define SPI_BURST_MODE_MASK 0x00800000 + +#define RXEN_BIT 0 +#define RFFEN_BIT 1 +#define RFSPOL_BIT 2 +#define DCM_BIT 3 +#define RFSSEL_BIT 4 +#define RCKPOL_BIT 5 +#define RCKSEL_BIT 6 +#define LBM_BIT 7 +#define TXEN_BIT 8 +#define TFFEN_BIT 9 +#define TFSPOL_BIT 10 +#define TFSSEL_BIT 11 +#define TCKPOL_BIT 13 +#define TCKSEL_BIT 14 +#define TXDDL_BIT 15 +#define SGEN_BIT 16 +#define SCKPOL_BIT 17 +#define SCKSEL_BIT 18 +#define FGEN_BIT 20 +#define SPICKM_BIT 21 + +#define msp_rx_clkpol_bit(n) ((n & 1) << RCKPOL_BIT) +#define msp_tx_clkpol_bit(n) ((n & 1) << TCKPOL_BIT) +#define msp_spi_clk_mode_bits(n) ((n & 3) << SPICKM_BIT) + + +/* Use this to clear the clock mode bits to non-spi */ +#define MSP_NON_SPI_CLK_MASK 0x00600000 + +#define P1ELEN_BIT 0 +#define P1FLEN_BIT 3 +#define DTYP_BIT 10 +#define ENDN_BIT 12 +#define DDLY_BIT 13 +#define FSIG_BIT 15 +#define P2ELEN_BIT 16 +#define P2FLEN_BIT 19 +#define P2SM_BIT 26 +#define P2EN_BIT 27 + +#define msp_p1_elem_len_bits(n) (n & 0x00000007) +#define msp_p2_elem_len_bits(n) (((n) << P2ELEN_BIT) & 0x00070000) +#define msp_p1_frame_len_bits(n) (((n) << P1FLEN_BIT) & 0x00000378) +#define msp_p2_frame_len_bits(n) (((n) << P2FLEN_BIT) & 0x03780000) +#define msp_data_delay_bits(n) (((n) << DDLY_BIT) & 0x00003000) +#define msp_data_type_bits(n) (((n) << DTYP_BIT) & 0x00000600) +#define msp_p2_start_mode_bit(n) (n << P2SM_BIT) +#define msp_p2_enable_bit(n) (n << P2EN_BIT) + +/* Flag register +--------------------*/ +#define RX_BUSY 0x00000001 +#define RX_FIFO_EMPTY 0x00000002 +#define RX_FIFO_FULL 0x00000004 +#define TX_BUSY 0x00000008 +#define TX_FIFO_EMPTY 0x00000010 +#define TX_FIFO_FULL 0x00000020 + +#define RBUSY_BIT 0 +#define RFE_BIT 1 +#define RFU_BIT 2 +#define TBUSY_BIT 3 +#define TFE_BIT 4 +#define TFU_BIT 5 + +/* Multichannel control register +---------------------------------*/ +#define RMCEN_BIT 0 +#define RMCSF_BIT 1 +#define RCMPM_BIT 3 +#define TMCEN_BIT 5 +#define TNCSF_BIT 6 + +/* Sample rate generator register +------------------------------------*/ +#define SCKDIV_BIT 0 +#define FRWID_BIT 10 +#define FRPER_BIT 16 + +#define SCK_DIV_MASK 0x0000003FF +#define frame_width_bits(n) (((n) << FRWID_BIT) &0x0000FC00) +#define frame_period_bits(n) (((n) << FRPER_BIT) &0x1FFF0000) + + +/* DMA controller register +---------------------------*/ +#define RX_DMA_ENABLE 0x00000001 +#define TX_DMA_ENABLE 0x00000002 + +#define RDMAE_BIT 0 +#define TDMAE_BIT 1 + +/*Interrupt Register +-----------------------------------------*/ +#define RECEIVE_SERVICE_INT 0x00000001 +#define RECEIVE_OVERRUN_ERROR_INT 0x00000002 +#define RECEIVE_FRAME_SYNC_ERR_INT 0x00000004 +#define RECEIVE_FRAME_SYNC_INT 0x00000008 +#define TRANSMIT_SERVICE_INT 0x00000010 +#define TRANSMIT_UNDERRUN_ERR_INT 0x00000020 +#define TRANSMIT_FRAME_SYNC_ERR_INT 0x00000040 +#define TRANSMIT_FRAME_SYNC_INT 0x00000080 +#define ALL_INT 0x000000ff + +/* Protocol configuration values +* I2S: Single phase, 16 bits, 2 words per frame +-----------------------------------------------*/ +#define I2S_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_32, \ + MSP_ELEM_LENGTH_32, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_FALLING_EDGE \ +} + +#define PCM_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_16, \ + MSP_DATA_DELAY, \ + MSP_TX_CLOCK_EDGE, \ + MSP_RX_CLOCK_EDGE \ +} + +/* Companded PCM: Single phase, 8 bits, 1 word per frame +--------------------------------------------------------*/ +#define PCM_COMPAND_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_0, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE \ +} + +/* AC97: Double phase, 1 element of 16 bits during first phase, +* 12 elements of 20 bits in second phase. +--------------------------------------------------------------*/ +#define AC97_PROTOCOL_DESC \ +{ \ + MSP_DUAL_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_12, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_20, \ + MSP_DELAY_1, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE \ +} + +#define SPI_MASTER_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE \ +} +#define SPI_SLAVE_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE \ +} + +#define MSP_FRAME_PERIOD_IN_MONO_MODE 256 +#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32 +#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16 + +#endif + -- cgit v1.2.3