From: Michail Kurochkin <michail.kurach...@promwad.com>

Signed-off-by: Michail Kurochkin <michail.kurach...@promwad.com>
---
 drivers/staging/Kconfig        |    4 +
 drivers/staging/Makefile       |    4 +-
 drivers/staging/tdm/Kconfig    |   38 ++
 drivers/staging/tdm/Makefile   |   19 +
 drivers/staging/tdm/tdm.h      |  292 ++++++++++++++
 drivers/staging/tdm/tdm_core.c |  826 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1182 insertions(+), 1 deletions(-)
 create mode 100644 drivers/staging/tdm/Kconfig
 create mode 100644 drivers/staging/tdm/Makefile
 create mode 100644 drivers/staging/tdm/tdm.h
 create mode 100644 drivers/staging/tdm/tdm_core.c

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 329bdb4..9bba991 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -26,6 +26,10 @@ if STAGING
 
 source "drivers/staging/et131x/Kconfig"
 
+source "drivers/staging/si3226x/Kconfig"
+
+source "drivers/staging/tdm/Kconfig"
+
 source "drivers/staging/slicoss/Kconfig"
 
 source "drivers/staging/usbip/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index c7ec486..a7f3699 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -1,4 +1,4 @@
-# Makefile for staging directory
+    # Makefile for staging directory
 
 # fix for build system bug...
 obj-$(CONFIG_STAGING)          += staging.o
@@ -7,6 +7,8 @@ obj-y                           += media/
 obj-y                          += net/
 obj-$(CONFIG_ET131X)           += et131x/
 obj-$(CONFIG_SLICOSS)          += slicoss/
+obj-$(CONFIG_SI3226X)          += si3226x/
+obj-$(CONFIG_TDM)              += tdm/
 obj-$(CONFIG_USBIP_CORE)       += usbip/
 obj-$(CONFIG_W35UND)           += winbond/
 obj-$(CONFIG_PRISM2_USB)       += wlan-ng/
diff --git a/drivers/staging/tdm/Kconfig b/drivers/staging/tdm/Kconfig
new file mode 100644
index 0000000..77b08fb4
--- /dev/null
+++ b/drivers/staging/tdm/Kconfig
@@ -0,0 +1,38 @@
+#
+# TDM driver configuration
+#
+menuconfig TDM
+       bool "TDM support"
+       depends on HAS_IOMEM
+       help
+         Time-division multiplexing (TDM) is a type of digital (or rarely 
analog)
+         multiplexing in which two or more bit streams or signals are
+         transferred apparently simultaneously as sub-channels in one
+         communication channel, but are physically taking turns on the
+         channel. The time domain is divided into several recurrent
+         timeslots of fixed length, one for each sub-channel. A sample
+         byte or data block of sub-channel 1 is transmitted during timeslot 1,
+         sub-channel 2 during timeslot 2, etc. One TDM frame consists of one
+         timeslot per sub-channel plus a synchronization channel and
+         sometimes error correction channel before the synchronization.
+
+if TDM
+
+config TDM_DEBUG
+       bool "Debug support for TDM drivers"
+       depends on DEBUG_KERNEL
+       help
+         Say "yes" to enable debug messaging (like dev_dbg and pr_debug),
+         sysfs, and debugfs support in TDM controller and protocol drivers.
+
+
+comment "TDM controllers"
+
+config TDM_KIRKWOOD
+#  TODO: add depend on kirkwood architecture
+#      depends on ARCH_KIRKWOOD
+       tristate "Marvel Kirkwood TDM Controller"
+       help
+         This selects a driver for the Marvel Kirkwood TDM Controller.
+                 
+endif # TDM
diff --git a/drivers/staging/tdm/Makefile b/drivers/staging/tdm/Makefile
new file mode 100644
index 0000000..f99ab5a
--- /dev/null
+++ b/drivers/staging/tdm/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for kernel TDM drivers.
+#
+
+ccflags-$(CONFIG_TDM_DEBUG) := -DDEBUG
+
+# small core, mostly translating board-specific
+# config declarations into driver model code
+obj-$(CONFIG_TDM)              += tdm_core.o
+
+# TDM controller drivers (bus)
+obj-$(CONFIG_TDM_KIRKWOOD)                     += kirkwood_tdm.o
+
+#      ... add above this line ...
+
+# TDM protocol drivers (device/link on bus)
+#obj-$(CONFIG_TDM_TDMDEV)      += tdmdev.o
+#      ... add above this line ...
+
diff --git a/drivers/staging/tdm/tdm.h b/drivers/staging/tdm/tdm.h
new file mode 100644
index 0000000..ee7b5bf
--- /dev/null
+++ b/drivers/staging/tdm/tdm.h
@@ -0,0 +1,292 @@
+/*
+ * tdm_base.h
+ *
+ *  Created on: 20.01.2012
+ *      Author: Michail Kurochkin
+ */
+
+#ifndef TDM_BASE_H_
+#define TDM_BASE_H_
+
+#include <linux/device.h>
+
+extern struct bus_type tdm_bus_type;
+
+
+
+/**
+ * General hardware TDM settings
+ */
+struct tdm_controller_hw_settings {
+       u8 fs_freq; /*  Frequency of discretization for audio transport. 
Normally 8KHz. */
+       u8 count_time_slots; /*  Total count of time slots in frame. One time 
slot = 8bit */
+       u8 channel_size; /*  Sample size (in time slots). 1 or 2 time slots. */
+
+       /*  Controller generate PCLK clock - TDM_CLOCK_OUTPUT, */
+       /*  or clock generate remote device - TDM_CLOCK_INPUT */
+#define TDM_CLOCK_INPUT 0
+#define TDM_CLOCK_OUTPUT 1
+       u8 clock_direction; /*  Drirection for PCLK */
+       u8 fs_clock_direction;  /*  Drirection for FS */
+
+       /*  FS and data polarity. Detection on rise or fall */
+#define TDM_POLAR_NEGATIVE 0
+#define TDM_POLAR_POSITIV 1
+       u8 fs_polarity;
+       u8 data_polarity;
+
+       struct mbus_dram_target_info    *dram;
+};
+
+
+/*
+ * Voice channel
+ */
+struct tdm_voice_channel {
+       u8 channel_num; /*  Hardware channel number */
+       u8 mode_wideband; /*  1 - support wideband mode for current voice 
channel */
+       u8 tdm_channel; /*  TDM channel on registered voice channel */
+       u8 buffer_len; /*  Length of transmit and receive buffers */
+       void *private_data; /*  hardware dependency channel private data */
+
+       /*  wait queue for transmit and receive operations */
+       wait_queue_head_t tx_queue;
+       wait_queue_head_t rx_queue;
+
+        struct list_head list; /*  Union all tdm voice channels by one 
controller */
+
+       struct device *dev; /*  device requested voice channel */
+};
+
+
+/**
+ * Remote device connected to TDM bus
+ */
+struct tdm_device {
+       struct device dev; /*  device connected to TDM bus */
+       struct tdm_controller *controller; /*  controller attendant TDM bus */
+       u8 tdm_channel_num; /*  requested TDM channel number on TDM frame */
+       u16 buffer_sample_count; /*  count samples for tx and rx buffers */
+       u8 mode_wideband; /*  quality mode 1 or 0. Wideband mode demand is 
16bit tdm channel size */
+       struct tdm_voice_channel *ch; /*  requested voice channel */
+       char modalias[32];
+};
+
+
+/**
+ * Driver remote device connected to TDM bus
+ */
+struct tdm_driver {
+       int                     (*probe)(struct tdm_device *tdm_dev);
+       int                     (*remove)(struct tdm_device *tdm_dev);
+       void                    (*shutdown)(struct tdm_device *tdm_dev);
+       int                     (*suspend)(struct tdm_device *tdm_dev, 
pm_message_t mesg);
+       int                     (*resume)(struct tdm_device *tdm_dev);
+       struct device_driver    driver;
+};
+
+
+/**
+ * TDM controller device
+ */
+struct tdm_controller {
+       struct device   dev;
+       spinlock_t              lock;
+
+       struct list_head list; /*  Union all TDM controllers */
+
+       s16 bus_num; /*  Number of controller, use -1 for auto numeration */
+
+/*     struct tdm_voice_channel *voice_channels; // Hardware or software voice 
channels transport */
+/*     u8 count_voice_channels; // count voice channels supported by current 
TDM controller */
+
+       /* List of voice channels */
+       struct list_head voice_channels;
+
+       /*  TDM hardware settings */
+       struct tdm_controller_hw_settings *settings;
+
+       int (*setup_voice_channel)(struct tdm_voice_channel *ch);
+       int (*send)(struct tdm_voice_channel *ch, u8 *data);
+       int (*recv)(struct tdm_voice_channel *ch, u8 *data);
+       int (*run_audio)(struct tdm_device *tdm_dev);
+       int (*stop_audio)(struct tdm_device *tdm_dev);
+       int (*poll_rx)(struct tdm_device *tdm_dev);
+       int (*poll_tx)(struct tdm_device *tdm_dev);
+
+       /*  called on release() for TDM device to free memory provided by 
tdm_controller */
+       void    (*cleanup)(struct tdm_device *tdm);
+};
+
+
+
+/*
+ * Board specific information for requested TDM channel
+ */
+struct tdm_board_info {
+       /* the device name and module name are coupled, like platform_bus;
+        * "modalias" is normally the driver name.
+        *
+        * platform_data goes to tdm_controller.dev.platform_data,
+        * controller_data goes to tdm_device.controller_data,
+        * irq is copied too
+        */
+       char            modalias[32];
+
+       /* bus_num is board specific and matches the bus_num of some
+        * tdm_controller that will probably be registered later.
+        */
+       u16             bus_num;
+
+       const void      *platform_data;
+       void            *controller_data;
+
+       u8              tdm_channel_num; /*  Number TDM channel for connected 
device */
+       u8              buffer_sample_count; /*  Size for transmit and receive 
buffer */
+       u8              mode_wideband; /*  1 - Enable wideband mode for 
requested TDM */
+
+       struct list_head        list; /*  Entry for union all board info */
+};
+
+
+
+
+/**
+ * Search address tdm_controller structure contained device stucture
+ * @param dev - device
+ * @return pointer to tdm_device
+ */
+static inline struct tdm_controller *to_tdm_controller(struct device *dev) {
+       return dev ? container_of(dev, struct tdm_controller, dev) : NULL;
+}
+
+
+/**
+ * Search address tdm_device structure contained device stucture
+ * @param dev - device
+ * @return pointer to tdm_device
+ */
+static inline struct tdm_device *to_tdm_device(struct device *dev) {
+       return dev ? container_of(dev, struct tdm_device, dev) : NULL;
+}
+
+/**
+ * Search address tdm_driver structure contained device stucture
+ * @param dev - device
+ * @return pointer to tdm_driver
+ */
+static inline struct tdm_driver *to_tdm_driver(struct device_driver *drv) {
+       return drv ? container_of(drv, struct tdm_driver, driver) : NULL;
+}
+
+
+/**
+ * Get private driver tdm controller data
+ * @param tdm
+ */
+static inline void *tdm_controller_get_devdata(struct tdm_controller *tdm)
+{
+       return dev_get_drvdata(&tdm->dev);
+}
+
+
+/**
+ * decrement pointer counter to tdm_controller
+ * @param tdm - tdm_controller
+ */
+static inline void tdm_controller_put(struct tdm_controller *tdm)
+{
+       if (tdm)
+               put_device(&tdm->dev);
+}
+
+
+/**
+ * Increment pointer counter to tdm_controller
+ * @param tdm - tdm_controller
+ */
+static inline struct tdm_controller *tdm_controller_get(struct tdm_controller 
*tdm) {
+       if (!tdm || !get_device(&tdm->dev))
+               return NULL;
+       return tdm;
+}
+
+
+/**
+ * Store private data for tdm device
+ * @param tdm_dev - tdm device
+ * @param data - private data
+ */
+static inline void tdm_set_drvdata(struct tdm_device *tdm_dev, void *data)
+{
+       dev_set_drvdata(&tdm_dev->dev, data);
+}
+
+
+/**
+ * Get stored early private data for tdm device
+ * @param tdm_dev - tdm device
+ */
+static inline void *tdm_get_drvdata(struct tdm_device *tdm_dev)
+{
+       return dev_get_drvdata(&tdm_dev->dev);
+}
+
+
+int tdm_register_driver(struct tdm_driver *tdm_drv);
+
+void tdm_unregister_driver(struct tdm_driver *tdm_dev);
+
+struct tdm_controller *tdm_alloc_controller(struct device *dev, unsigned size);
+
+struct tdm_voice_channel *tdm_alloc_voice_channel(void);
+
+int tdm_free_controller(struct tdm_controller *tdm);
+
+int tdm_controller_register(struct tdm_controller *tdm);
+
+void tdm_controller_unregister(struct tdm_controller *tdm);
+
+struct tdm_device *tdm_new_device(struct tdm_controller *tdm,
+                                  struct tdm_board_info *chip);
+
+int tdm_add_device(struct tdm_device *tdm_dev);
+
+int tdm_recv(struct tdm_device *tdm_dev, u8 *data);
+
+int tdm_send(struct tdm_device *tdm_dev, u8 *data);
+
+int tdm_run_audio(struct tdm_device *tdm_dev);
+
+int tdm_stop_audio(struct tdm_device *tdm_dev);
+
+int tdm_poll_rx(struct tdm_device *tdm_dev);
+
+int tdm_poll_tx(struct tdm_device *tdm_dev);
+
+int tdm_get_voice_block_size(struct tdm_device *tdm_dev);
+
+int
+tdm_register_new_voice_channel(struct tdm_controller *tdm,
+    struct tdm_voice_channel *ch,
+    void *driver_private);
+
+int tdm_free_voice_channels(struct tdm_controller *tdm);
+
+struct tdm_voice_channel *
+get_voice_channel_by_num(struct tdm_controller *tdm, int num);
+
+
+#ifdef CONFIG_TDM
+int __init
+tdm_register_board_info(struct tdm_board_info const *info, unsigned n);
+#else
+/* board init code may ignore whether TDM is configured or not */
+static inline int __init
+tdm_register_board_info(struct tdm_board_info const *info, unsigned n)
+{
+       return 0;
+}
+#endif
+
+#endif /* TDM_BASE_H_ */
diff --git a/drivers/staging/tdm/tdm_core.c b/drivers/staging/tdm/tdm_core.c
new file mode 100644
index 0000000..0182a4f
--- /dev/null
+++ b/drivers/staging/tdm/tdm_core.c
@@ -0,0 +1,826 @@
+/**********************************************************************
+ * Author: Michail Kurachkin
+ *
+ * Contact: michail.kurach...@promwad.com
+ *
+ * Copyright (c) 2013 Promwad Inc.
+ *
+ * This file 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.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
+ * NONINFRINGEMENT.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ * or visit http://www.gnu.org/licenses/.
+**********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include "tdm.h"
+
+#include <linux/cache.h>
+#include <linux/mutex.h>
+#include <linux/mod_devicetable.h>
+
+static void tdmdev_release(struct device *dev)
+{
+        struct tdm_device *tdm_dev = to_tdm_device(dev);
+
+        /* tdm controllers may cleanup for released devices */
+        if (tdm_dev->controller->cleanup)
+                tdm_dev->controller->cleanup(tdm_dev);
+
+        put_device(&tdm_dev->controller->dev);
+        kfree(tdm_dev);
+}
+
+static ssize_t
+modalias_show(struct device *dev, struct device_attribute *a, char *buf)
+{
+        const struct tdm_device *tdm_dev = to_tdm_device(dev);
+
+        return sprintf(buf, "%s\n", tdm_dev->modalias);
+}
+
+static struct device_attribute tdm_dev_attrs[] = {
+        __ATTR_RO(modalias),
+        __ATTR_NULL,
+};
+
+
+static int tdm_match_device(struct device *dev, struct device_driver *drv)
+{
+        const struct tdm_device *tdm_dev = to_tdm_device(dev);
+
+        return strcmp(tdm_dev->modalias, drv->name) == 0;
+}
+
+
+static int tdm_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+        const struct tdm_device *tdm_dev = to_tdm_device(dev);
+
+        add_uevent_var(env, "MODALIAS=%s%s", "tdm:", tdm_dev->modalias);
+        return 0;
+}
+
+
+static void tdm_release(struct device *dev)
+{
+        struct tdm_device *tdm_dev;
+
+        tdm_dev = to_tdm_device(dev);
+
+        kfree(tdm_dev);
+}
+
+
+static struct class tdm_class = {
+                .name           = "tdm",
+                .owner          = THIS_MODULE,
+                .dev_release    = tdm_release,
+        };
+
+
+
+/**
+ * Struct for TDM bus
+ */
+struct bus_type tdm_bus_type = {
+        .name           = "tdm",
+        .dev_attrs      = tdm_dev_attrs,
+        .match          = tdm_match_device,
+        .uevent         = tdm_uevent,
+        .suspend        = NULL,
+        .resume         = NULL,
+};
+EXPORT_SYMBOL_GPL(tdm_bus_type);
+
+
+
+static int tdm_drv_probe(struct device *dev)
+{
+        const struct tdm_driver *tdm_drv = to_tdm_driver(dev->driver);
+
+        return tdm_drv->probe(to_tdm_device(dev));
+}
+
+static int tdm_drv_remove(struct device *dev)
+{
+        const struct tdm_driver *tdm_drv = to_tdm_driver(dev->driver);
+
+        return tdm_drv->remove(to_tdm_device(dev));
+}
+
+static void tdm_drv_shutdown(struct device *dev)
+{
+        const struct tdm_driver *tdm_drv = to_tdm_driver(dev->driver);
+
+        tdm_drv->shutdown(to_tdm_device(dev));
+}
+
+
+/**
+ * tdm_register_driver - register a TDM driver
+ * @sdrv: the driver to register
+ * Context: can sleep
+ */
+int tdm_register_driver(struct tdm_driver *tdm_drv)
+{
+        tdm_drv->driver.bus = &tdm_bus_type;
+
+        if (tdm_drv->probe)
+                tdm_drv->driver.probe = tdm_drv_probe;
+
+        if (tdm_drv->remove)
+                tdm_drv->driver.remove = tdm_drv_remove;
+
+        if (tdm_drv->shutdown)
+                tdm_drv->driver.shutdown = tdm_drv_shutdown;
+
+        return driver_register(&tdm_drv->driver);
+}
+EXPORT_SYMBOL_GPL(tdm_register_driver);
+
+
+/**
+ * tdm_unregister_driver - reverse effect of tdm_register_driver
+ * @sdrv: the driver to unregister
+ * Context: can sleep
+ */
+void tdm_unregister_driver(struct tdm_driver *tdm_dev)
+{
+        if (tdm_dev)
+                driver_unregister(&tdm_dev->driver);
+}
+EXPORT_SYMBOL_GPL(tdm_unregister_driver);
+
+
+
+/**
+ * Request unused voice channel
+ * @param tdm_dev - TDM device requested voice channel
+ * @return pointer to voice channel
+ */
+struct tdm_voice_channel *request_voice_channel(struct tdm_device *tdm_dev) {
+        struct tdm_controller *tdm = tdm_dev->controller;
+        struct tdm_voice_channel *ch;
+        unsigned long flags;
+
+        spin_lock_irqsave(&tdm->lock, flags);
+
+       list_for_each_entry(ch, &tdm->voice_channels, list)
+                if (ch->dev == NULL) {
+                        ch->dev = &tdm_dev->dev;
+                        tdm_dev->ch = ch;
+                        spin_unlock_irqrestore(&tdm->lock, flags);
+                        return ch;
+                }
+
+        spin_unlock_irqrestore(&tdm->lock, flags);
+        return NULL;
+}
+
+/**
+ * Release requested voice channel
+ * @param TDM device requested early voice channel
+ * @return 0 - OK
+ */
+int release_voice_channel(struct tdm_device *tdm_dev)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+        struct tdm_voice_channel *ch;
+        unsigned long flags;
+
+        spin_lock_irqsave(&tdm->lock, flags);
+
+        tdm_dev->ch = NULL;
+       list_for_each_entry(ch, &tdm->voice_channels, list)
+                if (ch->dev == &tdm_dev->dev) {
+                        ch->dev = NULL;
+                        spin_unlock_irqrestore(&tdm->lock, flags);
+                        return 0;
+                }
+
+        spin_unlock_irqrestore(&tdm->lock, flags);
+
+        return -ENODEV;
+}
+
+
+
+/*
+ * Container for union board info of all TDM devices
+ */
+struct tdm_board_devices {
+        struct list_head        list;
+        struct tdm_board_info   bi; /* Board specific info */
+};
+
+/* List of all board specific TDM devices */
+static LIST_HEAD(tdm_devices_list);
+
+/* List of all registred TDM controllers */
+static LIST_HEAD(tdm_controller_list);
+
+
+/*
+ * Used to protect add/del opertion for board_info list and
+ * tdm_controller list, and their matching process
+ */
+static DEFINE_MUTEX(board_lock);
+
+
+static void tdm_match_controller_to_boardinfo(struct tdm_controller *tdm,
+        struct tdm_board_info *bi)
+{
+        struct tdm_device *tdm_dev;
+
+        if (tdm->bus_num != bi->bus_num)
+                return;
+
+        tdm_dev = tdm_new_device(tdm, bi);
+        if (!tdm_dev)
+                dev_err(tdm->dev.parent, "can't create new device for %s\n",
+                        bi->modalias);
+}
+
+
+
+/**
+ * Allocate memory for TDM controller device
+ * @dev: the controller, possibly using the platform_bus
+ * @size: how much zeroed driver-private data to allocate; the pointer to this
+ *      memory is in the driver_data field of the returned device,
+ *      accessible with tdm_controller_get_devdata().
+ * Context: can sleep
+ */
+struct tdm_controller *tdm_alloc_controller(struct device *dev, unsigned size) 
{
+        struct tdm_controller   *tdm;
+
+        if (!dev)
+                return NULL;
+
+        tdm = kzalloc(sizeof *tdm + size, GFP_KERNEL);
+        if (!tdm)
+                return NULL;
+
+        device_initialize(&tdm->dev);
+        tdm->dev.class = &tdm_class;
+        tdm->dev.parent = get_device(dev);
+        spin_lock_init(&tdm->lock);
+        INIT_LIST_HEAD(&tdm->list);
+        INIT_LIST_HEAD(&tdm->voice_channels);
+
+        dev_set_drvdata(&tdm->dev, tdm + 1);
+
+        return tdm;
+}
+EXPORT_SYMBOL_GPL(tdm_alloc_controller);
+
+
+/**
+ * Free memory for TDM controller device, allocated by tdm_alloc_controller
+ * @dev: the controller, possibly using the platform_bus
+ * Context: can sleep
+ */
+int tdm_free_controller(struct tdm_controller *tdm)
+{
+        if (!tdm)
+                return -EINVAL;
+
+        dev_set_drvdata(&tdm->dev, NULL);
+        put_device(&tdm->dev);
+        kzfree(tdm);
+
+        return 0;
+}
+EXPORT_SYMBOL_GPL(tdm_free_controller);
+
+
+/**
+ * Allocate memory for voice channel
+ * @return object voice_channel or NULL if error
+ */
+struct tdm_voice_channel *tdm_alloc_voice_channel(void)
+{
+        struct tdm_voice_channel *ch;
+
+        ch = kzalloc(sizeof *ch, GFP_KERNEL);
+        if (!ch)
+                return NULL;
+
+        memset(ch, 0, sizeof *ch);
+        init_waitqueue_head(&ch->tx_queue);
+        init_waitqueue_head(&ch->rx_queue);
+
+        return ch;
+}
+EXPORT_SYMBOL_GPL(tdm_alloc_voice_channel);
+
+
+
+/**
+ * Add allocated voice channel in tdm controller
+ * @param tdm - tdm controller
+ * @param ch - allocated voice channel
+ * @param driver_private - private driver structure
+ * @return 0 - ok
+ */
+int
+tdm_register_new_voice_channel(struct tdm_controller *tdm,
+    struct tdm_voice_channel *ch,
+    void *driver_private)
+{
+       struct tdm_voice_channel *c;
+       int last_num;
+
+       last_num = -1;
+       list_for_each_entry(c, &tdm->voice_channels, list)
+               if (c->channel_num > last_num)
+                       last_num = c->channel_num;
+
+       ch->channel_num = last_num + 1;
+       ch->private_data = driver_private;
+       list_add_tail(&ch->list, &tdm->voice_channels);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(tdm_register_new_voice_channel);
+
+/**
+ * free memory for voice channels allocated by tdm_alloc_voice_channels
+ * @param tdm - TDM controller
+ * @param count_channels - count supported voice channels
+ * @return
+ */
+int tdm_free_voice_channels(struct tdm_controller *tdm)
+{
+       struct tdm_voice_channel *ch, *ch_tmp;
+        if (!tdm)
+               return -EINVAL;
+
+       list_for_each_entry_safe(ch, ch_tmp, &tdm->voice_channels, list)
+        {
+               list_del(&ch->list);
+               kzfree(ch);
+        }
+
+        return 0;
+}
+EXPORT_SYMBOL_GPL(tdm_free_voice_channels);
+
+
+/**
+ * get voice_channel object by voice number
+ * @param tdm - tdm_controller contained voice channels
+ * @param num - voice channel number
+ * @return voice_channel object or NULL
+ */
+struct tdm_voice_channel *
+get_voice_channel_by_num(struct tdm_controller *tdm, int num)
+{
+       struct tdm_voice_channel *ch;
+       list_for_each_entry(ch, &tdm->voice_channels, list)
+               if (ch->channel_num == num)
+                       return ch;
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(get_voice_channel_by_num);
+
+
+/**
+ * tdm_controller_register - register TDM controller
+ * @master: initialized master, originally from spi_alloc_master()
+ * Context: can sleep
+ *
+ * This must be called from context that can sleep.  It returns zero on
+ * success, else a negative error code (dropping the master's refcount).
+ * After a successful return, the caller is responsible for calling
+ * tdm_controller_unregister().
+ */
+int tdm_controller_register(struct tdm_controller *tdm)
+{
+        static atomic_t         dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
+        struct device           *dev = tdm->dev.parent;
+        struct tdm_board_devices        *bi;
+        int                     status = -ENODEV;
+        int                     dynamic = 0;
+
+        if (!dev) {
+                dev_err(dev, "parent device not exist\n");
+                return -ENODEV;
+        }
+
+        /* convention:  dynamically assigned bus IDs count down from the max */
+        if (tdm->bus_num < 0) {
+                tdm->bus_num = atomic_dec_return(&dyn_bus_id);
+                dynamic = 1;
+        }
+
+        /* register the device, then userspace will see it.
+         * registration fails if the bus ID is in use.
+         */
+        dev_set_name(&tdm->dev, "tdm%u", tdm->bus_num);
+        status = device_add(&tdm->dev);
+        if (status < 0) {
+                dev_err(dev, "cannot added controller device, %d\n", status);
+                goto done;
+        }
+        dev_dbg(dev, "registered controller %s%s\n", dev_name(&tdm->dev),
+                dynamic ? " (dynamic)" : "");
+
+        mutex_lock(&board_lock);
+        list_add_tail(&tdm->list, &tdm_controller_list);
+        list_for_each_entry(bi, &tdm_devices_list, list)
+                tdm_match_controller_to_boardinfo(tdm, &bi->bi);
+
+        mutex_unlock(&board_lock);
+
+        status = 0;
+
+done:
+        return status;
+}
+EXPORT_SYMBOL_GPL(tdm_controller_register);
+
+
+static int __unregister(struct device *dev, void *null)
+{
+        device_unregister(dev);
+        return 0;
+}
+
+
+/**
+ * tdm_controller_unregister - unregister TDM controller
+ * @tdm: the controller being unregistered
+ * Context: can sleep
+ *
+ * This call is used only by TDM controller drivers, which are the
+ * only ones directly touching chip registers.
+ *
+ * This must be called from context that can sleep.
+ */
+void tdm_controller_unregister(struct tdm_controller *tdm)
+{
+        int dummy;
+
+        mutex_lock(&board_lock);
+        list_del(&tdm->list);
+        mutex_unlock(&board_lock);
+
+        dummy = device_for_each_child(&tdm->dev, NULL, __unregister);
+        device_unregister(&tdm->dev);
+}
+EXPORT_SYMBOL_GPL(tdm_controller_unregister);
+
+
+/**
+ * tdm_new_device - instantiate one new TDM device
+ * @tdm: TDM Controller to which device is connected
+ * @chip: Describes the TDM device
+ * Context: can sleep
+ *
+ * Returns the new device, or NULL.
+ */
+struct tdm_device *tdm_new_device(struct tdm_controller *tdm,
+                                  struct tdm_board_info *chip) {
+        struct tdm_device       *tdm_dev;
+        int                     status;
+
+        if (!tdm_controller_get(tdm))
+                return NULL;
+
+        tdm_dev = kzalloc(sizeof *tdm_dev, GFP_KERNEL);
+        if (!tdm_dev) {
+                dev_err(tdm->dev.parent, "cannot alloc for TDM device\n");
+                tdm_controller_put(tdm);
+                return NULL;
+        }
+
+        tdm_dev->controller = tdm;
+        tdm_dev->dev.parent = tdm->dev.parent;
+        tdm_dev->dev.bus = &tdm_bus_type;
+        tdm_dev->dev.release = tdmdev_release;
+        device_initialize(&tdm_dev->dev);
+
+        WARN_ON(strlen(chip->modalias) >= sizeof(tdm_dev->modalias));
+
+        tdm_dev->tdm_channel_num = chip->tdm_channel_num;
+        tdm_dev->mode_wideband = chip->mode_wideband;
+        tdm_dev->buffer_sample_count = chip->buffer_sample_count;
+        strlcpy(tdm_dev->modalias, chip->modalias, sizeof(tdm_dev->modalias));
+
+        status = tdm_add_device(tdm_dev);
+        if (status < 0) {
+                put_device(&tdm_dev->dev);
+                return NULL;
+        }
+
+        return tdm_dev;
+}
+EXPORT_SYMBOL_GPL(tdm_new_device);
+
+
+
+/**
+ * tdm_add_device - Add tdm_device on TDM bus
+ * @tdm_dev: TDM device to register
+ *
+ * Returns 0 on success; negative errno on failure
+ */
+int tdm_add_device(struct tdm_device *tdm_dev)
+{
+        static DEFINE_MUTEX(tdm_add_lock);
+        struct tdm_controller *tdm = tdm_dev->controller;
+        struct tdm_controller_hw_settings *hw = tdm->settings;
+        struct device *dev = tdm->dev.parent;
+        struct device *d;
+        struct tdm_voice_channel *ch;
+        int status;
+        u8 count_tdm_channels;
+
+
+        if (tdm_dev->mode_wideband) {
+                dev_err(dev, "mode_wideband is not supported by this 
driver\n");
+                status = -EINVAL;
+                goto done;
+        }
+
+        count_tdm_channels = hw->count_time_slots / hw->channel_size;
+
+        if (tdm_dev->tdm_channel_num >= count_tdm_channels) {
+                dev_err(dev, "Incorrect requested TDM channel.\n"
+                        "Requested %d TDM channel, %d TDM channels 
available.\n",
+                        tdm_dev->tdm_channel_num, count_tdm_channels);
+
+                status = -EINVAL;
+                goto done;
+        }
+
+        if (tdm_dev->mode_wideband &&
+            (tdm_dev->tdm_channel_num > count_tdm_channels / 2)) {
+                dev_err(dev, "Incorrect requested TDM channel in wideband 
mode.\n"
+                        "Requested %d TDM channel, %d TDM channels available\n"
+                        "in wideband mode\n",
+                        tdm_dev->tdm_channel_num, count_tdm_channels / 2);
+
+                status = -EINVAL;
+                goto done;
+        }
+
+
+        /* Set the bus ID string */
+        dev_set_name(&tdm_dev->dev, "%s.%u", 
dev_name(&tdm_dev->controller->dev),
+                     tdm_dev->tdm_channel_num);
+
+        /* We need to make sure there's no other device with this
+         * chipselect **BEFORE** we call setup(), else we'll trash
+         * its configuration.  Lock against concurrent add() calls.
+         */
+        mutex_lock(&tdm_add_lock);
+
+        d = bus_find_device_by_name(&tdm_bus_type, NULL, 
dev_name(&tdm_dev->dev));
+        if (d != NULL) {
+                dev_err(dev, "TDM channel %d already in use\n",
+                        tdm_dev->tdm_channel_num);
+                put_device(d);
+                status = -EBUSY;
+                goto done;
+        }
+
+        ch = request_voice_channel(tdm_dev);
+        if (ch == NULL) {
+                dev_err(dev, "Can't request TDM voice channel. All voice 
channels is busy\n");
+                status = -EBUSY;
+                goto done;
+        }
+
+        printk("ch = %s\n", dev_name(ch->dev));
+
+        /* Configuring voice channel */
+        ch->mode_wideband = tdm_dev->mode_wideband;
+        ch->tdm_channel = tdm_dev->tdm_channel_num;
+
+        /* Run setup voice channel */
+        status = tdm_dev->controller->setup_voice_channel(ch);
+        if (status < 0) {
+                dev_err(dev, "can't setup voice channel, status %d\n", status);
+                goto done;
+        }
+
+        /* Device may be bound to an active driver when this returns */
+        status = device_add(&tdm_dev->dev);
+        if (status < 0)
+                dev_err(dev, "can't add %s, status %d\n",
+                        dev_name(&tdm_dev->dev), status);
+        else
+                dev_dbg(dev, "registered child %s\n", dev_name(&tdm_dev->dev));
+
+done:
+        mutex_unlock(&tdm_add_lock);
+        return status;
+}
+EXPORT_SYMBOL_GPL(tdm_add_device);
+
+
+
+/**
+ * Receive audio-data from tdm device.
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @param data - pointer to receive block data.
+ *              Allocated data size must be equal value
+ *              returned by tdm_get_voice_block_size();
+ * @return 0 - success
+ */
+int tdm_recv(struct tdm_device *tdm_dev, u8 *data)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        if (tdm_dev->ch == NULL)
+                return -ENODEV;
+
+        return tdm->recv(tdm_dev->ch, data);
+}
+EXPORT_SYMBOL_GPL(tdm_recv);
+
+
+/**
+ * Transmit audio-data from tdm device.
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @param data - pointer to transmit block data.
+ *              Transmit data size must be equal value
+ *              returned by tdm_get_voice_block_size();
+ * @return 0 - success
+ */
+int tdm_send(struct tdm_device *tdm_dev, u8 *data)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        if (tdm_dev->ch == NULL)
+                return -ENODEV;
+
+        return tdm->send(tdm_dev->ch, data);
+}
+EXPORT_SYMBOL_GPL(tdm_send);
+
+
+/**
+ * Enable audio transport
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @return 0 - success
+ */
+int tdm_run_audio(struct tdm_device *tdm_dev)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        if (!tdm_dev->ch) {
+                dev_err(&tdm_dev->dev, "Can't run audio because not allocated 
tdm voice channel\n");
+                return -ENODEV;
+        }
+
+        return tdm->run_audio(tdm_dev);
+}
+EXPORT_SYMBOL_GPL(tdm_run_audio);
+
+
+/**
+ * Disable audio transport
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @return 0 - success
+ */
+int tdm_stop_audio(struct tdm_device *tdm_dev)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        if (!tdm_dev->ch) {
+                dev_err(&tdm_dev->dev, "Can't stop audio because not allocated 
tdm voice channel\n");
+                return -ENODEV;
+        }
+
+        return tdm->stop_audio(tdm_dev);
+}
+EXPORT_SYMBOL_GPL(tdm_stop_audio);
+
+
+
+/**
+ * Check rx audio buffer for exist new data
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @return 0 - not enought data, 1 - data exist
+ */
+int tdm_poll_rx(struct tdm_device *tdm_dev)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        return tdm->poll_rx(tdm_dev);
+}
+EXPORT_SYMBOL_GPL(tdm_poll_rx);
+
+
+/**
+ * Check tx audio buffer for free space
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @return 0 - not enought free space, 1 - exist free space
+ */
+int tdm_poll_tx(struct tdm_device *tdm_dev)
+{
+        struct tdm_controller *tdm = tdm_dev->controller;
+
+        return tdm->poll_tx(tdm_dev);
+}
+EXPORT_SYMBOL_GPL(tdm_poll_tx);
+
+
+/**
+ * Get voice block size for transmit or receive operations
+ * @param tdm_dev - tdm device registered on TDM bus
+ * @return voice block size, or error if returned value less 0
+ */
+int tdm_get_voice_block_size(struct tdm_device *tdm_dev)
+{
+        struct tdm_voice_channel *ch;
+
+        ch = tdm_dev->ch;
+        if (ch == NULL)
+                return -ENODEV;
+
+        return ch->buffer_len;
+}
+EXPORT_SYMBOL_GPL(tdm_get_voice_block_size);
+
+
+
+/**
+ * tdm_register_board_info - register TDM devices for a given board
+ * @info: array of chip descriptors
+ * @n: how many descriptors are provided
+ * Context: can sleep
+ */
+int __init
+tdm_register_board_info(struct tdm_board_info const *info, unsigned n)
+{
+        struct tdm_board_devices *bi;
+        int i;
+
+        bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
+        if (!bi)
+                return -ENOMEM;
+
+        for (i = 0; i < n; i++, bi++, info++) {
+                struct tdm_controller *tdm;
+
+                memcpy(&bi->bi, info, sizeof(*info));
+                mutex_lock(&board_lock);
+
+                list_add_tail(&bi->list, &tdm_devices_list);
+                list_for_each_entry(tdm, &tdm_controller_list, list)
+                        tdm_match_controller_to_boardinfo(tdm, &bi->bi);
+
+                mutex_unlock(&board_lock);
+        }
+
+        return 0;
+}
+
+
+
+static int __init tdm_core_init(void)
+{
+        int status;
+
+        status = bus_register(&tdm_bus_type);
+        if (status < 0)
+                goto err0;
+
+        status = class_register(&tdm_class);
+        if (status < 0)
+                goto err1;
+        return 0;
+
+err1:
+        bus_unregister(&tdm_bus_type);
+err0:
+        return status;
+}
+
+postcore_initcall(tdm_core_init);
+
-- 
1.7.5.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to