linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/9] Support of TDM bus, TDM driver for Marvell Kirkwood and SLIC driver for Silabs Si3226x
@ 2013-02-27 16:22 Michail Kurachkin
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
  0 siblings, 1 reply; 17+ messages in thread
From: Michail Kurachkin @ 2013-02-27 16:22 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh
  Cc: Michail Kurachkin


Hi guys,

Thanks for your comments. Most of them were considered while I worked on the second revision of code. 
The most huge change includes rework of register/unregister and request/release voice channel related code.
The following are my replies on the disputable comments.


1)
+static int slic_chr_open(struct inode *inode, struct file *file)
+{
+ struct slic_chr_dev *chr_dev;
+ struct si3226x_slic *slic;
+ struct si3226x_line *line;
+ struct tdm_device *tdm_dev;
+ struct tdm_voice_channel *ch;
+ int status = -ENXIO;
+
+ mutex_lock(&slic_chr_dev_lock);
+
+ list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+  switch (chr_dev->type) {
+  case SLIC_CHR_DEV:
+   slic = dev_get_drvdata(chr_dev->dev);

Ryan Mallon> It's a bit clunky having two device types on the same character device. Is there a better way to do this?

SLIC has two different types of configuration structures: general settings and channel specific.
There are 2 channels. For each of them I created one struct device. And one struct device more for the whole SLIC.



2) 
+slic_chr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct slic_chr_dev *chr_dev = file->private_data;
+ struct si3226x_slic *slic;
+ struct si3226x_line *line;
+ int status = -ENXIO;
+
+ if (_IOC_TYPE(cmd) != SI3226X_IOC_MAGIC)
+  return -ENOTTY;
+
+ mutex_lock(&slic_chr_dev_lock);

Ryan Mallon> This locking is very heavy handed. You are holding it across the entire open, close, read, write, ioctl, and it is protecting a bunch of different things. Can you make the
locking a bit more fine grained?

Agreed. Will do it later when I will be able to test it as I currently have no suitable hardware.



3) 
+/*
+ * probe tdm driver
+ */
+static int probe_tdm_slic(struct tdm_device *tdm_dev)
+{
+ struct si3226x_slic *slic;
+ int rc;
+
+ dev_dbg(&tdm_dev->dev, "probe\n");
+
+ mutex_lock(&dev_list_lock);
+
+ rc = add_to_slic_devices_list(&tdm_dev->dev, TDM_DEVICE);

Ryan Mallon> This function is the same as probe_spi_slic, except for the device type. A single function would prevent the code duplication.

I thought about that. This would lead to much more code because there is not really much such a code duplication.



4)
> +static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
> +{
[skipped]
> +
> +     enum irq_event_mode {
> +             IRQ_RECEIVE,
> +             IRQ_TRANSMIT,
> +     };
> +
> +     status = readl(&regs->int_status);
> +
> +     if ((status & 0xFF) == 0)
> +             return ret;
> +
> +     /*  Check first 8 bit in status mask register for detect event type */
> +     for(i = 0; i < 8; i++) {
> +             if((status & (1 << i)) == 0)
> +                     continue;
> +
> +             writel(status & ~(1 << i), &regs->int_status);
> +
> +             switch(i) {
> +             case 0:
> +                     mode = IRQ_RECEIVE;
> +                     voice_num = 0;
> +                     overflow = 1;
> +                     break;
> +
[skipped]
> +
> +             case 7:
> +                     mode = IRQ_TRANSMIT;
> +                     voice_num = 1;
> +                     overflow = 0;
> +                     full = 0;
> +                     break;
> +             }


Oliver Neukum> Are you really sure that you cannot have multiple reasons for an interrupt at once?

Yes, this can be happened. Why is this problem? This code can handle multiple interrupt events at once.




5)
> +
> +
> +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;
> +}

Oliver Neukum> This seems to preclude two devices bound to the same driver.

Why do you think this is the case? I tested such condition and it works ok.


Regards,
Michail



Michail Kurachkin (4):
  remove device_attribute
  added issues description in TODO file
  removing of buffer filling flag and also reverting old buffer related
    fix which is not really effective
  fixed e-mail address

Michail Kurochkin (5):
  Initial commit of TDM core
  Initial commit of Kirkwood TDM driver
  Initial commit of SLIC si3226x driver
  added TODO file for si3226x
  refactoring request/free voice channels

 drivers/staging/Kconfig                            |    4 +
 drivers/staging/Makefile                           |    4 +-
 drivers/staging/si3226x/Kconfig                    |    9 +
 drivers/staging/si3226x/Makefile                   |    4 +
 drivers/staging/si3226x/TODO                       |    8 +
 drivers/staging/si3226x/si3226x_drv.c              | 1323 +++++++++++++++
 drivers/staging/si3226x/si3226x_drv.h              |  188 +++
 drivers/staging/si3226x/si3226x_hw.c               | 1691 ++++++++++++++++++++
 drivers/staging/si3226x/si3226x_hw.h               |  219 +++
 .../staging/si3226x/si3226x_patch_C_FB_2011MAY19.c |  176 ++
 drivers/staging/si3226x/si3226x_setup.c            | 1413 ++++++++++++++++
 drivers/staging/si3226x/slic_dtmf_table.c          |  127 ++
 drivers/staging/si3226x/slic_si3226x.h             |   75 +
 drivers/staging/tdm/Kconfig                        |   38 +
 drivers/staging/tdm/Makefile                       |   19 +
 drivers/staging/tdm/kirkwood_tdm.c                 |  998 ++++++++++++
 drivers/staging/tdm/kirkwood_tdm.h                 |  112 ++
 drivers/staging/tdm/tdm.h                          |  293 ++++
 drivers/staging/tdm/tdm_core.c                     |  809 ++++++++++
 19 files changed, 7509 insertions(+), 1 deletions(-)
 create mode 100644 drivers/staging/si3226x/Kconfig
 create mode 100644 drivers/staging/si3226x/Makefile
 create mode 100644 drivers/staging/si3226x/TODO
 create mode 100644 drivers/staging/si3226x/si3226x_drv.c
 create mode 100644 drivers/staging/si3226x/si3226x_drv.h
 create mode 100644 drivers/staging/si3226x/si3226x_hw.c
 create mode 100644 drivers/staging/si3226x/si3226x_hw.h
 create mode 100644 drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c
 create mode 100644 drivers/staging/si3226x/si3226x_setup.c
 create mode 100644 drivers/staging/si3226x/slic_dtmf_table.c
 create mode 100644 drivers/staging/si3226x/slic_si3226x.h
 create mode 100644 drivers/staging/tdm/Kconfig
 create mode 100644 drivers/staging/tdm/Makefile
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.c
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.h
 create mode 100644 drivers/staging/tdm/tdm.h
 create mode 100644 drivers/staging/tdm/tdm_core.c

-- 
1.7.5.4


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 00/11] staging: Support of TDM bus, TDM driver for Marvell Kirkwood and SLIC driver for Silabs Si3226x
  2013-02-27 16:22 [PATCH 0/9] Support of TDM bus, TDM driver for Marvell Kirkwood and SLIC driver for Silabs Si3226x Michail Kurachkin
@ 2013-03-01 10:42 ` Michail Kurachkin
  2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
                     ` (10 more replies)
  0 siblings, 11 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:42 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Michail Kurochkin (11):
  remove device_attribute
  added issues description in TODO file
  removing of buffer filling flag and also reverting old buffer related
    fix which is not really effective
  fixed e-mail address
  add issuses in TODO
  add issuses in TODO
  Initial commit of TDM core
  Initial commit of Kirkwood TDM driver
  Initial commit of SLIC si3226x driver
  added TODO file for si3226x
  refactoring request/free voice channels

 drivers/staging/Kconfig                            |    4 +
 drivers/staging/Makefile                           |    4 +-
 drivers/staging/si3226x/Kconfig                    |    9 +
 drivers/staging/si3226x/Makefile                   |    4 +
 drivers/staging/si3226x/TODO                       |   19 +
 drivers/staging/si3226x/si3226x_drv.c              | 1323 +++++++++++++++
 drivers/staging/si3226x/si3226x_drv.h              |  188 +++
 drivers/staging/si3226x/si3226x_hw.c               | 1691 ++++++++++++++++++++
 drivers/staging/si3226x/si3226x_hw.h               |  219 +++
 .../staging/si3226x/si3226x_patch_C_FB_2011MAY19.c |  176 ++
 drivers/staging/si3226x/si3226x_setup.c            | 1413 ++++++++++++++++
 drivers/staging/si3226x/slic_dtmf_table.c          |  127 ++
 drivers/staging/si3226x/slic_si3226x.h             |   75 +
 drivers/staging/tdm/Kconfig                        |   38 +
 drivers/staging/tdm/Makefile                       |   19 +
 drivers/staging/tdm/TODO                           |   16 +
 drivers/staging/tdm/kirkwood_tdm.c                 |  998 ++++++++++++
 drivers/staging/tdm/kirkwood_tdm.h                 |  112 ++
 drivers/staging/tdm/tdm.h                          |  293 ++++
 drivers/staging/tdm/tdm_core.c                     |  809 ++++++++++
 20 files changed, 7536 insertions(+), 1 deletions(-)
 create mode 100644 drivers/staging/si3226x/Kconfig
 create mode 100644 drivers/staging/si3226x/Makefile
 create mode 100644 drivers/staging/si3226x/TODO
 create mode 100644 drivers/staging/si3226x/si3226x_drv.c
 create mode 100644 drivers/staging/si3226x/si3226x_drv.h
 create mode 100644 drivers/staging/si3226x/si3226x_hw.c
 create mode 100644 drivers/staging/si3226x/si3226x_hw.h
 create mode 100644 drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c
 create mode 100644 drivers/staging/si3226x/si3226x_setup.c
 create mode 100644 drivers/staging/si3226x/slic_dtmf_table.c
 create mode 100644 drivers/staging/si3226x/slic_si3226x.h
 create mode 100644 drivers/staging/tdm/Kconfig
 create mode 100644 drivers/staging/tdm/Makefile
 create mode 100644 drivers/staging/tdm/TODO
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.c
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.h
 create mode 100644 drivers/staging/tdm/tdm.h
 create mode 100644 drivers/staging/tdm/tdm_core.c

-- 
1.7.5.4


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 01/11] staging: Initial commit of TDM core
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
@ 2013-03-01 10:50   ` Michail Kurachkin
  2013-03-01 22:54     ` Ryan Mallon
  2013-03-11 17:17     ` Greg Kroah-Hartman
  2013-03-01 10:52   ` [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver Michail Kurachkin
                     ` (9 subsequent siblings)
  10 siblings, 2 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:50 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurochkin

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

Signed-off-by: Michail Kurochkin <michail.kurachkin@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.kurachkin@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


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
  2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
@ 2013-03-01 10:52   ` Michail Kurachkin
  2013-03-01 23:54     ` Ryan Mallon
  2013-03-01 10:54   ` [PATCH v2 03/11] staging: Initial commit of SLIC si3226x driver Michail Kurachkin
                     ` (8 subsequent siblings)
  10 siblings, 1 reply; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:52 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurochkin

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="UTF-8", Size: 31444 bytes --]

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

Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/kirkwood_tdm.c |  932 ++++++++++++++++++++++++++++++++++++
 drivers/staging/tdm/kirkwood_tdm.h |  110 +++++
 2 files changed, 1042 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.c
 create mode 100644 drivers/staging/tdm/kirkwood_tdm.h

diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
new file mode 100644
index 0000000..4366d38
--- /dev/null
+++ b/drivers/staging/tdm/kirkwood_tdm.c
@@ -0,0 +1,932 @@
+/**********************************************************************
+ * Author: Michail Kurachkin
+ *
+ * Contact: michail.kurachkin@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/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/gpio.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/inet_lro.h>
+#include <linux/slab.h>
+#include <asm/system.h>
+#include <linux/io.h>
+#include <asm/uaccess.h>
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <linux/mbus.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+
+#include "tdm.h"
+#include "kirkwood_tdm.h"
+
+
+/**
+ * Init hardware tdm controller
+ * @param tdm - tdm_controller descriptor
+ * @return
+ */
+static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
+{
+	struct kirkwood_tdm *onchip_tdm =
+	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+	struct tdm_controller_hw_settings *hw = tdm->settings;
+	u32 control_val = 0;
+	u32 pclk_freq;
+	unsigned long flags;
+	spinlock_t lock;
+	spin_lock_init(&lock);
+
+
+	// Errata GL-CODEC-10: resolve problem witch FS
+	spin_lock_irqsave(&lock, flags);
+	writel(0x03ffff00, &regs->pcm_ctrl_reg + 0x1000);
+	writel(5, &regs->pcm_ctrl_reg + 0x101C);
+	spin_unlock_irqrestore(&lock, flags);
+
+
+	writel(0, &regs->int_reset_sel);
+	writel(0x3ffff, &regs->int_event_mask);
+	writel(0, &regs->int_status_mask);
+	writel(0, &regs->int_status);
+
+	writeb(0x41, &regs->pcm_ctrl_reg + 0xC41);
+	writeb(0x10, &regs->pcm_ctrl_reg + 0xC40);
+
+	// Configuring PCLK and FS frequency
+	pclk_freq = hw->count_time_slots * 8 * hw->fs_freq;
+	switch(pclk_freq) {
+	case 256:
+		writel(0x1, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x4, &regs->num_time_slot);
+		break;
+
+	case 512:
+		writel(0x2, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x8, &regs->num_time_slot);
+		break;
+
+	case 1024:
+		writel(0x4, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x10, &regs->num_time_slot);
+		break;
+
+	case 2048:
+		writel(0x8, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x20, &regs->num_time_slot);
+		break;
+
+	case 4096:
+		writel(0x10, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x40, &regs->num_time_slot);
+		break;
+
+	case 8192:
+		writel(0x20, &regs->tdm_pcm_clock_div);
+		writel(0x0, &regs->dummy_rx_write);
+		writel(0x80, &regs->num_time_slot);
+		break;
+
+	default:
+		dev_err(&tdm->dev, "Incorrect count of time slots parameter\n");
+		return -EINVAL;
+	}
+
+	control_val = readl(&regs->pcm_ctrl_reg);
+
+	if(hw->clock_direction == TDM_CLOCK_OUTPUT)
+		control_val &= (u32)~(1 << 0);
+	else
+		control_val |= (1 << 0);
+
+
+	if(hw->fs_clock_direction == TDM_CLOCK_OUTPUT)
+		control_val &= (u32) ~(1 << 1);
+	else
+		control_val |= (1 << 1);
+
+
+	if(hw->fs_polarity == TDM_POLAR_POSITIV)
+		control_val &= (u32) ~(1 << 4);
+	else
+		control_val |= (1 << 4);
+
+
+	if(hw->data_polarity == TDM_POLAR_POSITIV)
+		control_val &= (u32) ~(1 << 2);
+	else
+		control_val |= (1 << 2);
+
+
+	if(hw->channel_size == 1)
+		control_val &= (u32) ~(1 << 6);
+	else
+		control_val |= (1 << 6);
+
+	writel(control_val, &regs->pcm_ctrl_reg);
+	writel(0, &regs->chan_time_slot_ctrl);
+	writel(0, &regs->chan0_enable_disable);
+	writel(0, &regs->chan0_enable_disable + 4);
+
+	return 0;
+}
+
+/**
+ * deInitialization hardware tdm controller
+ * @param tdm - tdm_controller descriptor
+ */
+static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
+{
+	struct kirkwood_tdm *onchip_tdm =
+	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+
+	writel(0, &regs->pcm_ctrl_reg);
+	writel(0, &regs->chan_time_slot_ctrl);
+}
+
+/**
+ * Setup hardware voice channel
+ * @param ch - voice hardware channel
+ * @return 0 - OK
+ */
+int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
+{
+	struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
+	struct tdm_controller *tdm = tdm_dev->controller;
+	struct tdm_controller_hw_settings *hw = tdm->settings;
+	struct kirkwood_tdm *onchip_tdm =
+	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+	unsigned long timeout;
+	u32 state;
+	int i;
+
+	u8 voice_num = ch->channel_num;
+
+	/* calculate buffer size */
+	ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
+
+	/* Request timeslot for voice channel */
+	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
+	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
+
+	/* FIXME: move coherent_dma_mask to board specific */
+	tdm_dev->dev.coherent_dma_mask = 0xffffffff;
+
+	/* Allocate rx and tx buffers */
+	for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++) {
+		/* allocate memory for DMA receiver */
+		onchip_ch->rx_buf[i] =
+		        dma_alloc_coherent(&tdm_dev->dev,
+		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
+
+		if (onchip_ch->rx_buf == NULL) {
+			dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
+			return -ENOMEM;
+		}
+		memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
+
+
+		/* allocate memory for DMA transmitter */
+		onchip_ch->tx_buf[i] =
+		        dma_alloc_coherent(&tdm_dev->dev,
+		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
+
+		if (onchip_ch->tx_buf == NULL) {
+			dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
+			return -ENOMEM;
+		}
+		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
+	}
+
+	atomic_set(&onchip_ch->write_rx_buf_num, 0);
+	atomic_set(&onchip_ch->write_tx_buf_num, 0);
+	atomic_set(&onchip_ch->read_rx_buf_num, 0);
+	atomic_set(&onchip_ch->read_tx_buf_num, 0);
+
+	/* Set length for DMA */
+	writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
+	       &regs->chan0_total_sample + voice_num);
+
+	/* Waiting for program transmit DMA */
+	timeout = jiffies + HZ;
+	do {
+		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100;
+		if(time_after(jiffies, timeout)) {
+			dev_err(ch->dev, "Can`t program DMA tx buffer\n");
+			return -EBUSY;
+		}
+	} while(state);
+
+	/* Set DMA buffers fo transmitter */
+	writel((u32)onchip_ch->tx_buff_phy[0],
+	    &regs->chan0_transmit_start_addr + 4 * voice_num);
+	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
+
+	/* Waiting for program received DMA */
+	timeout = jiffies + HZ;
+	do {
+		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1;
+		if(time_after(jiffies, timeout)) {
+			dev_err(ch->dev, "Can`t program DMA rx buffer\n");
+			return -EBUSY;
+		}
+	} while(state);
+
+	/* Set DMA buffers for receiver */
+	writel((u32)onchip_ch->rx_buff_phy[0],
+	    &regs->chan0_receive_start_addr + 4 * voice_num);
+	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) );
+
+	return 0;
+}
+
+
+/**
+ * Run tdm transmitter and receiver
+ * @param tdm_dev - tdm device
+ * @return 0 - ok
+ */
+int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
+{
+	struct tdm_controller *tdm = tdm_dev->controller;
+	struct kirkwood_tdm *onchip_tdm =
+	        (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+	struct tdm_voice_channel *ch = tdm_dev->ch;
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+
+	memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
+	memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
+
+	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
+	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );
+
+	/* enable Tx interrupts */
+	writel(0, &regs->int_status);
+	writel((readl(&regs->int_status_mask) | TDM_INT_TX(ch->channel_num)),
+	        &regs->int_status_mask);
+
+	/* enable Rx interrupts */
+	writel(0, &regs->int_status);
+	writel((readl(&regs->int_status_mask) | TDM_INT_RX(ch->channel_num)),
+	        &regs->int_status_mask);
+
+	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * ch->channel_num));
+	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * ch->channel_num) + 1);
+
+	return 0;
+}
+
+
+/**
+ * Stop tdm transmitter and receiver
+ * @param tdm_dev - tdm device
+ * @return 0 - ok
+ */
+int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
+{
+	struct tdm_controller *tdm = tdm_dev->controller;
+	struct kirkwood_tdm *onchip_tdm =
+			(struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+	struct tdm_voice_channel *ch = tdm_dev->ch;
+
+	/* disable Tx interrupts */
+	writel((readl(&regs->int_status_mask) & (~TDM_INT_TX(ch->channel_num))),
+	    &regs->int_status_mask);
+	writel(0, &regs->int_status);
+
+	/* disable Rx interrupts */
+	writel((readl(&regs->int_status_mask) & (~TDM_INT_RX(ch->channel_num))),
+	    &regs->int_status_mask);
+	writel(0, &regs->int_status);
+
+	writeb(0x0, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
+	writeb(0x0, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );
+
+	return 0;
+}
+
+
+/**
+ * Get DMA tx buffers latency
+ * @param onchip_ch - kirkwood based voice channel
+ * @return latency in buffers
+ */
+int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
+{
+	if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
+		return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);
+	else
+		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
+		       + atomic_read(&onchip_ch->write_tx_buf_num);
+}
+
+
+/**
+ * Get DMA rx buffers latency
+ * @param onchip_ch - kirkwood based voice channel
+ * @return latency in buffers
+ */
+int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
+{
+	if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
+		return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
+	else
+		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
+		       + atomic_read(&onchip_ch->write_rx_buf_num);
+}
+
+
+/**
+ * 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 kirkwood_poll_rx(struct tdm_device *tdm_dev)
+{
+	struct tdm_voice_channel *ch = tdm_dev->ch;
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+
+	return get_rx_latency(onchip_ch) > 1;
+}
+
+
+/**
+ * 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 kirkwood_poll_tx(struct tdm_device *tdm_dev)
+{
+	struct tdm_voice_channel *ch = tdm_dev->ch;
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+
+	return get_tx_latency(onchip_ch) > 1;
+}
+
+
+
+/**
+ * Get next dma buffer number
+ * @param num - current buffer number
+ * @return next buffer number
+ */
+static int inc_next_dma_buf_num(int num)
+{
+	num ++;
+	if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
+		num = 0;
+
+	return num;
+}
+
+
+
+/**
+ * Send voice data block to tdm voice channel controller.
+ * @param ch - voice channel attendant to transmit data in TDM frame
+ * @param data - data to be transmit. Length of data must be equal to
+ * 		value returned by get_voice_block_size()
+ *
+ * Context: can sleep
+ * @return 0 on success; negative errno on failure
+ */
+static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
+{
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+	int rc = 0;
+
+	rc = wait_event_interruptible(ch->tx_queue,
+	                         get_tx_latency(onchip_ch) > 1);
+
+	if (rc)
+		return rc;
+
+	memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
+	       ch->buffer_len);
+	atomic_set(&onchip_ch->read_tx_buf_num,
+		inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+	return rc;
+}
+
+
+/**
+ * Receive voice data block from TDM voice channel controller.
+ * @param ch - voice channel attendant to transmit data in TDM frame
+ * @param data - pointer to read data received by DMA.
+                 Length data for read equal to value returned by get_tdm_voice_block_size()
+ *
+ * Context: can sleep
+ * @return 0 on success; negative errno on failure
+ */
+static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
+{
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+	int rc = 0;
+
+	rc = wait_event_interruptible(ch->rx_queue,
+	                         get_rx_latency(onchip_ch) > 1);
+
+	if (rc)
+		return rc;
+
+	memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
+	       ch->buffer_len);
+	atomic_set(&onchip_ch->read_rx_buf_num,
+		   inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+	return rc;
+}
+
+
+
+/**
+ * kirkwood_tdm_irq - IRQ handler for Kirkwood TDM
+ * @irq: IRQ number for this TDM controller
+ * @context_data: structure for TDM controller kirkwood_tdm
+ * Context: can not sleep
+ */
+static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
+{
+	struct kirkwood_tdm *onchip_tdm = context_data;
+	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
+	struct tdm_controller *tdm = to_tdm_controller(onchip_tdm->controller_dev);
+	struct tdm_voice_channel *ch;
+	struct kirkwood_tdm_voice *onchip_ch;
+	struct tdm_device *tdm_dev;
+
+	irqreturn_t ret = IRQ_NONE;
+	u32 status;
+	u8 i;
+
+	int voice_num; /* current voice channel */
+	int next_buf_num; /* number of next buffer */
+	int mode; /* irq event mode: */
+	int overflow = 0;
+	int full = 0;
+
+	enum irq_event_mode {
+		IRQ_RECEIVE,
+		IRQ_TRANSMIT,
+	};
+
+	status = readl(&regs->int_status);
+
+	if ((status & 0xFF) == 0)
+		return ret;
+
+	/*  Check first 8 bit in status mask register for detect event type */
+	for(i = 0; i < 8; i++) {
+		if((status & (1 << i)) == 0)
+			continue;
+
+		writel(status & ~(1 << i), &regs->int_status);
+
+		switch(i) {
+		case 0:
+			mode = IRQ_RECEIVE;
+			voice_num = 0;
+			overflow = 1;
+			break;
+
+		case 1:
+			mode = IRQ_TRANSMIT;
+			voice_num = 0;
+			overflow = 1;
+			break;
+
+		case 2:
+			mode = IRQ_RECEIVE;
+			voice_num = 1;
+			overflow = 1;
+			break;
+
+		case 3:
+			mode = IRQ_TRANSMIT;
+			voice_num = 1;
+			overflow = 1;
+			break;
+
+		case 4:
+			mode = IRQ_RECEIVE;
+			voice_num = 0;
+			overflow = 0;
+			full = 0;
+			break;
+
+		case 5:
+			mode = IRQ_TRANSMIT;
+			voice_num = 0;
+			overflow = 0;
+			full = 0;
+			break;
+
+		case 6:
+			mode = IRQ_RECEIVE;
+			voice_num = 1;
+			overflow = 0;
+			full = 0;
+			break;
+
+		case 7:
+			mode = IRQ_TRANSMIT;
+			voice_num = 1;
+			overflow = 0;
+			full = 0;
+			break;
+		}
+
+		/* ñurrent voice channel struct */
+		ch = get_voice_channel_by_num(tdm, voice_num);
+		onchip_ch = ch->private_data;
+
+		/* TDM device attached to current voice channel */
+		tdm_dev = to_tdm_device(ch->dev);
+
+		switch(mode) {
+		case IRQ_RECEIVE: {
+			/* get next buffer number, and move write/read pointer */
+			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
+			atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
+			if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
+				atomic_set(&onchip_ch->read_rx_buf_num,
+				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+
+			/* if receive overflow event */
+			if (overflow) {
+				/* set next buffer address */
+				writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
+				       &regs->chan0_receive_start_addr + 4 * voice_num);
+				writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num));
+
+				/* enable receiver */
+				writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * voice_num));
+
+				ret = IRQ_HANDLED;
+				break;
+			}
+
+			/* waiting while dma providing access to buffer */
+			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1);
+
+			/* set next buffer address */
+			writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
+			       &regs->chan0_receive_start_addr + 4 * voice_num);
+			writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num));
+
+			wake_up_interruptible(&ch->rx_queue);
+
+			ret = IRQ_HANDLED;
+		}
+		break;
+
+		case IRQ_TRANSMIT: {
+			/* get next buffer number, and move write/read pointer */
+			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
+			atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
+			if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
+				atomic_set(&onchip_ch->read_tx_buf_num,
+				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+
+			/* if transmit overflow event */
+			if (overflow) {
+				/* set next buffer address */
+				writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
+				       &regs->chan0_transmit_start_addr + 4 * voice_num);
+				writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
+
+				/* enable transmitter */
+				writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * voice_num) + 1);
+
+				ret = IRQ_HANDLED;
+				break;
+			}
+
+			/* waiting while dma providing access to buffer */
+			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100);
+
+			/* set next buffer address */
+			writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
+			       &regs->chan0_transmit_start_addr + 4 * voice_num);
+			writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
+
+			wake_up_interruptible(&ch->tx_queue);
+
+			ret = IRQ_HANDLED;
+		}
+		break;
+		}
+	}
+
+	return ret;
+}
+
+
+/**
+ * Configuring mbus windows for correct access to DMA memory
+ * @param base_regs - base address for tdm registers
+ * @param dram - dram settings
+ */
+static void kirkwood_tdm_mbus_windows(void *base_regs,
+                                      struct mbus_dram_target_info *dram)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		writel(0, (u8 *)base_regs + TDM_WINDOW_CTRL(i));
+		writel(0, (u8 *)base_regs + TDM_WINDOW_BASE(i));
+	}
+
+	for (i = 0; i < dram->num_cs; i++) {
+		struct mbus_dram_window *cs = dram->cs + i;
+
+		writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) |
+		       (dram->mbus_dram_target_id << 4) | 1,
+		       (u8 *)base_regs + TDM_WINDOW_CTRL(i));
+
+		writel(cs->base, (u8 *)base_regs + TDM_WINDOW_BASE(i));
+	}
+}
+
+
+
+static int __init kirkwood_tdm_probe(struct platform_device *pdev)
+{
+	int err = 0;
+	struct tdm_controller *tdm;
+	struct kirkwood_tdm *onchip_tdm = NULL;
+	struct resource *res;
+	struct tdm_controller_hw_settings *hw = pdev->dev.platform_data;
+	struct tdm_voice_channel *ch;
+	int i;
+
+	tdm = tdm_alloc_controller(&pdev->dev, sizeof(struct kirkwood_tdm));
+	if (tdm == NULL) {
+		dev_err(&pdev->dev, "Can`t alloc memory\n");
+		err = -ENOMEM;
+		goto out0;
+	}
+
+	platform_set_drvdata(pdev, tdm);
+	onchip_tdm = tdm_controller_get_devdata(tdm);
+
+	if (pdev->id != -1)
+		tdm->bus_num = pdev->id;
+
+	/* Check hardware settings */
+	if (hw->fs_freq != 8 && hw->fs_freq != 16) {
+		dev_err(&pdev->dev, "Fs frequency may be 8kHz o 16kHz. "
+		    "Frequency %d is not incorrect\n", hw->fs_freq);
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->count_time_slots > 64) {
+		dev_err(&pdev->dev, "Incorrect count time slots. "
+		    "No more than 64 timeslots per one FS. "
+		    "Current set %d\n", hw->count_time_slots);
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->channel_size > 2 || hw->channel_size == 0) {
+		dev_err(&pdev->dev, "Incorrect count time slots. "
+		    "No more than 64 timeslots per one FS. "
+		    "Current set %d\n", hw->count_time_slots);
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->clock_direction != TDM_CLOCK_INPUT &&
+	    hw->clock_direction != TDM_CLOCK_OUTPUT) {
+		dev_err(&pdev->dev, "Incorrect PCLK clock direction value\n");
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->fs_clock_direction != TDM_CLOCK_INPUT &&
+	    hw->fs_clock_direction != TDM_CLOCK_OUTPUT) {
+		dev_err(&pdev->dev, "Incorrect FS clock direction value\n");
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->fs_polarity != TDM_POLAR_NEGATIVE &&
+	    hw->fs_polarity != TDM_POLAR_POSITIV) {
+		dev_err(&pdev->dev, "Incorrect FS polarity value\n");
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (hw->data_polarity != TDM_POLAR_NEGATIVE &&
+	    hw->data_polarity != TDM_POLAR_POSITIV) {
+		dev_err(&pdev->dev, "Incorrect data polarity value\n");
+		err = -EINVAL;
+		goto out1;
+	}
+
+	/* Set controller data */
+	tdm->bus_num = pdev->id;
+	tdm->settings = hw;
+	tdm->setup_voice_channel = kirkwood_setup_voice_channel;
+	tdm->recv = kirkwood_recv;
+	tdm->send = kirkwood_send;
+	tdm->run_audio = kirkwood_tdm_run_audio;
+	tdm->stop_audio = kirkwood_tdm_stop_audio;
+	tdm->poll_rx = kirkwood_poll_rx;
+	tdm->poll_tx = kirkwood_poll_tx;
+
+	for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
+	{
+	        ch = tdm_alloc_voice_channel();
+	        if (ch == NULL) {
+	                dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
+	                goto out1;
+	        }
+
+            tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
+	}
+
+
+	/* Get resources(memory, IRQ) associated with the device */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "Can`t request registers memory\n");
+		err = -ENODEV;
+		goto out2;
+	}
+
+	onchip_tdm->regs = ioremap(res->start, resource_size(res));
+	if (!onchip_tdm->regs) {
+		dev_err(&pdev->dev, "Incorrect setup registers memory area\n");
+		err = -EINVAL;
+		goto out2;
+	}
+
+	/* If need remap mbus windows */
+	if (hw->dram != NULL)
+		kirkwood_tdm_mbus_windows(onchip_tdm->regs, hw->dram);
+
+	/* Get IRQ number for all controllers events */
+	onchip_tdm->irq = platform_get_irq(pdev, 0);
+	if (onchip_tdm->irq < 0) {
+		dev_err(&pdev->dev, "Can`t request IRQ\n");
+		err = onchip_tdm->irq;
+		goto out3;
+	}
+
+	/* Set interrupt callback kirkwood_tdm_irq */
+	err = request_irq(onchip_tdm->irq, kirkwood_tdm_irq, IRQF_DISABLED,
+	                  dev_name(&pdev->dev), onchip_tdm);
+	if (err) {
+		dev_err(&pdev->dev, "Can`t setup IRQ callback\n");
+		err = -EINVAL;
+		goto out4;
+	}
+
+	onchip_tdm->controller_dev = &tdm->dev;
+
+	/* Initialization TDM controller */
+	err = kirkwood_tdm_hw_init(tdm);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Can't initialization tdm controller, %d\n",
+		        err);
+		goto out4;
+	}
+
+	err = tdm_controller_register(tdm);
+	if(err) {
+		dev_err(&pdev->dev, "cannot register tdm controller, %d\n", err);
+		goto out5;
+	}
+
+	printk("Init ok");
+
+	dev_dbg(&tdm->dev, "tdm controller registred sucessfully\n");
+	return 0;
+
+
+out5:
+	kirkwood_tdm_hw_deinit(tdm);
+
+out4:
+	free_irq(onchip_tdm->irq, onchip_tdm);
+
+out3:
+	if (onchip_tdm->regs)
+		iounmap(onchip_tdm->regs);
+
+out2:
+	tdm_free_voice_channels(tdm);
+
+out1:
+	tdm_free_controller(tdm);
+
+out0:
+	return err;
+}
+
+
+static int __exit kirkwood_tdm_remove(struct platform_device *pdev)
+{
+	struct tdm_controller *tdm = NULL;
+	struct kirkwood_tdm *onchip_tdm = NULL;
+
+	tdm = platform_get_drvdata(pdev);
+	if (tdm == NULL)
+		goto out0;
+
+	onchip_tdm = tdm_controller_get_devdata(tdm);
+
+	kirkwood_tdm_hw_deinit(tdm);
+	free_irq(onchip_tdm->irq, onchip_tdm);
+
+	tdm_free_voice_channels(tdm);
+	tdm_controller_unregister(tdm);
+	if (onchip_tdm->regs)
+		iounmap(onchip_tdm->regs);
+
+	tdm_free_controller(tdm);
+out0:
+	return 0;
+}
+
+
+static struct platform_driver kirkwood_tdm_driver = {
+	.probe	= kirkwood_tdm_probe,
+	.remove	= __exit_p(kirkwood_tdm_remove),
+	.driver = {
+		.name = "kirkwood_tdm",
+		.owner = THIS_MODULE,
+	},
+	.suspend = NULL,
+	.resume  = NULL,
+};
+
+
+static int __init kirkwood_init_tdm(void)
+{
+	return platform_driver_register(&kirkwood_tdm_driver);
+}
+
+static void __exit kirkwood_exit_tdm(void)
+{
+	platform_driver_unregister(&kirkwood_tdm_driver);
+}
+
+module_init(kirkwood_init_tdm);
+module_exit(kirkwood_exit_tdm);
+MODULE_AUTHOR("Michail Kurochkin <stelhs@yandex.ru>");
+MODULE_DESCRIPTION("TDM controller driver for Marvel kirkwood arch.");
+MODULE_LICENSE("GPL");
+
+
diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
new file mode 100644
index 0000000..6c7f34d
--- /dev/null
+++ b/drivers/staging/tdm/kirkwood_tdm.h
@@ -0,0 +1,110 @@
+/*
+ * kirkwood_tdm.h
+ *
+ *  Created on: 25.01.2012
+ *      Author: Michail Kurochkin
+ */
+
+#ifndef KIRKWOOD_TDM_H_
+#define KIRKWOOD_TDM_H_
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+struct kirkwood_tdm_regs {
+	u32 pcm_ctrl_reg; /*  0x0 PCM control register */
+	u32 chan_time_slot_ctrl; /*  0x4 Channel Time Slot Control Register */
+	u32 chan0_delay_ctrl; /*  0x8 Channel 0 Delay Control Register */
+	u32 chan1_delay_ctrl; /*  0xC Channel 1 Delay Control Register */
+	u32 chan0_enable_disable; /*  0x10 Channel 0 Enable and Disable Register */
+	u32 chan0_buff_ownership; /*  0x14 Channel 0 Buffer Ownership Register */
+	u32 chan0_transmit_start_addr; /*  0x18 Channel 0 Transmit Data Start Address Register */
+	u32 chan0_receive_start_addr; /*  0x1C Channel 0 Receive Data Start Address Register */
+	u32 chan1_enable_disable; /*  0x20 Channel 1 Enable and Disable Register */
+	u32 chan1_buff_ownership; /*  0x24 Channel 1 Buffer Ownership Register */
+	u32 chan1_transmit_start_addr; /*  0x28 Channel 1 Transmit Data Start Address Register */
+	u32 chan1_receive_start_addr; /*  0x2C Channel 1 Receive Data Start Address Register */
+	u32 chan0_total_sample; /*  0x30 Channel 0 Total Sample Count Register */
+	u32 chan1_total_sample; /*  0x34 Channel 1 Total Sample Count Register */
+	u32 num_time_slot; /*  0x38 Number of Time Slot Register */
+	u32 tdm_pcm_clock_div; /*  0x3C TDM PCM Clock Rate Divisor Register */
+	u32 int_event_mask; /*  0x40 Interrupt Event Mask Register */
+	u32 reserved_44h; /*  0x44 */
+	u32 int_status_mask; /*  0x48 Interrupt Status Mask Register */
+	u32 int_reset_sel; /*  0x4C Interrupt Reset Selection Register */
+	u32 int_status; /*  0x50 Interrupt Status Register */
+	u32 dummy_rx_write;/*  0x54 Dummy Data for Dummy RX Write Register */
+	u32 misc_control; /*  0x58 Miscellaneous Control Register */
+	u32 reserved_5Ch; /*  0x5C */
+	u32 chan0_current_tx_addr; /*  0x60 Channel 0 Transmit Data Current Address Register (for DMA) */
+	u32 chan0_current_rx_addr;  /*  0x64 Channel 0 Receive Data Current Address Register (for DMA) */
+	u32 chan1_current_tx_addr; /*  0x68 Channel 1 Transmit Data Current Address Register (for DMA) */
+	u32 chan1_current_rx_addr; /*  0x6C Channel 1 Receive Data Current Address Register (for DMA) */
+	u32 curr_time_slot; /*  0x70 Current Time Slot Register */
+	u32 revision; /*  0x74 TDM Revision Register */
+	u32 chan0_debug; /*  0x78 TDM Channel 0 Debug Register */
+	u32 chan1_debug; /*  0x7C TDM Channel 1 Debug Register */
+	u32 tdm_dma_abort_1; /*  0x80 TDM DMA Abort Register 1 */
+	u32 tdm_dma_abort_2; /*  0x84 TDM DMA Abort Register 2 */
+	u32 chan0_wideband_delay_ctrl; /*  0x88 TDM Channel 0 Wideband Delay Control Register */
+	u32 chan1_wideband_delay_ctrl; /*  0x8C TDM Channel 1 Wideband Delay Control Register */
+};
+
+
+#define KIRKWOOD_MAX_VOICE_CHANNELS 2 /*  Max count hardware channels */
+#define COUNT_DMA_BUFFERS_PER_CHANNEL 6 /*  Count of dma buffers for tx or rx path by one channel */
+
+/*
+ * Data specified for kirkwood hardware voice channel
+ */
+struct kirkwood_tdm_voice {
+	/* Transmitter and receiver buffers split to half.
+	 * While first half buffer is filling by DMA controller,
+	 * second half buffer is used by consumer and etc.
+	*/
+	u8 *tx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  transmitter voice buffers */
+	u8 *rx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  receiver voice buffers pointer */
+
+	dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to tx_buf */
+	dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to rx_buf */
+	atomic_t write_tx_buf_num; /*  current writing transmit buffer number */
+	atomic_t read_tx_buf_num; /*  current reading transmit buffer number */
+	atomic_t write_rx_buf_num; /*  current writing receive buffer number */
+	atomic_t read_rx_buf_num; /*  current reading receive buffer number */
+};
+
+
+/*
+ * Data specified for kirkwood tdm controller
+ */
+struct kirkwood_tdm {
+	struct kirkwood_tdm_regs *regs; /*  Registers for hardware TDM */
+	u32			irq; /*  Irq number for all TDM operations */
+
+	struct kirkwood_tdm_voice voice_channels[KIRKWOOD_MAX_VOICE_CHANNELS];
+	struct device *controller_dev;
+};
+
+
+#define TDM_WINDOW_CTRL(i)		(0x4030 + ((i) << 4))
+#define TDM_WINDOW_BASE(i)	(0x4034 + ((i) << 4))
+
+
+/* INT_STATUS_REG bits */
+#define RX_OVERFLOW_BIT(ch)	(1<<(0+(ch)*2))
+#define TX_UNDERFLOW_BIT(ch)	(1<<(1+((ch)*2)))
+#define RX_BIT(ch)		(1<<(4+((ch)*2)))
+#define TX_BIT(ch)		(1<<(5+((ch)*2)))
+#define RX_IDLE_BIT(ch)		(1<<(8+((ch)*2)))
+#define TX_IDLE_BIT(ch)		(1<<(9+((ch)*2)))
+#define RX_FIFO_FULL(ch)	(1<<(12+((ch)*2)))
+#define TX_FIFO_EMPTY(ch)	(1<<(13+((ch)*2)))
+#define DMA_ABORT_BIT		(1<<16)
+#define SLIC_INT_BIT		(1<<17)
+#define TDM_INT_TX(ch)	(TX_UNDERFLOW_BIT(ch) | TX_BIT(ch))
+#define TDM_INT_RX(ch)	(RX_OVERFLOW_BIT(ch) | RX_BIT(ch))
+
+
+#endif /* KIRKWOOD_TDM_H_ */
+
+
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 03/11] staging: Initial commit of SLIC si3226x driver
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
  2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
  2013-03-01 10:52   ` [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver Michail Kurachkin
@ 2013-03-01 10:54   ` Michail Kurachkin
  2013-03-01 10:56   ` [PATCH v2 04/11] staging: added TODO file for si3226x Michail Kurachkin
                     ` (7 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:54 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurochkin

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

Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
---
 drivers/staging/si3226x/Kconfig                    |    9 +
 drivers/staging/si3226x/Makefile                   |    4 +
 drivers/staging/si3226x/si3226x_drv.c              | 1323 +++++++++++++++
 drivers/staging/si3226x/si3226x_drv.h              |  188 +++
 drivers/staging/si3226x/si3226x_hw.c               | 1691 ++++++++++++++++++++
 drivers/staging/si3226x/si3226x_hw.h               |  219 +++
 .../staging/si3226x/si3226x_patch_C_FB_2011MAY19.c |  176 ++
 drivers/staging/si3226x/si3226x_setup.c            | 1413 ++++++++++++++++
 drivers/staging/si3226x/slic_dtmf_table.c          |  127 ++
 drivers/staging/si3226x/slic_si3226x.h             |   75 +
 10 files changed, 5225 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/si3226x/Kconfig
 create mode 100644 drivers/staging/si3226x/Makefile
 create mode 100644 drivers/staging/si3226x/si3226x_drv.c
 create mode 100644 drivers/staging/si3226x/si3226x_drv.h
 create mode 100644 drivers/staging/si3226x/si3226x_hw.c
 create mode 100644 drivers/staging/si3226x/si3226x_hw.h
 create mode 100644 drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c
 create mode 100644 drivers/staging/si3226x/si3226x_setup.c
 create mode 100644 drivers/staging/si3226x/slic_dtmf_table.c
 create mode 100644 drivers/staging/si3226x/slic_si3226x.h

diff --git a/drivers/staging/si3226x/Kconfig b/drivers/staging/si3226x/Kconfig
new file mode 100644
index 0000000..a378dd1
--- /dev/null
+++ b/drivers/staging/si3226x/Kconfig
@@ -0,0 +1,9 @@
+config SI3226X
+	tristate "Silicon Labs SLIC driver si3226x"
+	depends on TDM && SPI
+	default n
+	help
+	  This driver supports SLIC chip si3226x.
+	  si3226x provide two FXS ports. For transport
+	  audiodata used tdm framework, for control used
+	  spi framework.
diff --git a/drivers/staging/si3226x/Makefile b/drivers/staging/si3226x/Makefile
new file mode 100644
index 0000000..279d813
--- /dev/null
+++ b/drivers/staging/si3226x/Makefile
@@ -0,0 +1,4 @@
+
+si3226x-y := si3226x_drv.o si3226x_hw.o
+
+obj-$(CONFIG_SI3226X) += si3226x.o
diff --git a/drivers/staging/si3226x/si3226x_drv.c b/drivers/staging/si3226x/si3226x_drv.c
new file mode 100644
index 0000000..daa703f
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_drv.c
@@ -0,0 +1,1323 @@
+/**********************************************************************
+ * Author: Michail Kurachkin
+ *
+ * Contact: michail.kurachkin@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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ioctl.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include "../tdm/tdm.h"
+#include "si3226x_drv.h"
+#include "si3226x_hw.h"
+#include "slic_si3226x.h"
+
+
+/**
+ * spi_dev_list necessary for
+ * accumulate corresponding devices while
+ * this registering. SLIC driver requirement one spi_device,
+ * two tdm_device, and platform_device
+ */
+static LIST_HEAD(slic_dev_list);
+static DEFINE_MUTEX(dev_list_lock);
+
+
+/*
+ * list all existing character devices
+ */
+static LIST_HEAD(slic_chr_dev_list);
+static DEFINE_MUTEX(slic_chr_dev_lock);
+
+/*
+ * class for character devices
+ */
+static struct class *slic_class;
+
+
+/**
+ * slic controls over ioctl
+ * @param slic - slic descriptor
+ * @param cmd - ioctl command
+ * @param arg - ioctl argument
+ * @return 0 - ok
+ */
+static int slic_control(struct si3226x_slic *slic, unsigned int cmd, unsigned long arg)
+{
+	int rc;
+	struct si3226x_line *line = slic->lines;
+	int i;
+
+	switch(cmd) {
+	case SI3226X_SET_COMPANDING_MODE:
+		if (arg != SI_ALAW && arg != SI_MLAW)
+			return -EINVAL;
+
+		for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++) {
+			if (line->state == SI_LINE_DISABLE)
+				continue;
+
+			line->companding_mode = arg;
+
+			rc = slic_setup_audio(line);
+			if (rc)
+			{
+				dev_err(&slic->pdev->dev, "Can`t setup slic audio settings\n");
+				return rc;
+			}
+		}
+		break;
+
+	case SI3226X_SET_CALLERID_MODE:
+		if(	arg != SI_FSK_BELLCORE && arg != SI_FSK_ETSI
+		    && arg != SI_CALLERID_DTMF)
+			return -EINVAL;
+
+		for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++) {
+			if (line->state == SI_LINE_DISABLE)
+				continue;
+
+			line->callerid_mode = arg;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+/**
+ * slic line controls over ioctl
+ * @param line - line descriptor
+ * @param cmd - command
+ * @param arg - argument
+ * @return 0 - ok
+ */
+static int line_control(struct si3226x_line *line, unsigned int cmd, unsigned long arg)
+{
+	int rc = 0;
+	u8 data;
+	struct si3226x_caller_id caller_id;
+	u8 callerid_buffer[CALLERID_BUF_SIZE];
+
+	switch(cmd) {
+	case SI3226X_SET_CALLERID_MODE:
+		if( arg != SI_FSK_BELLCORE && arg != SI_FSK_ETSI
+		    && arg != SI_CALLERID_DTMF && arg != SI_CALLERID_NONE)
+			return -EINVAL;
+
+		line->callerid_mode = arg;
+		break;
+
+	case SI3226X_SET_ECHO_CANCELATION:
+		if(arg)
+			rc = slic_enable_echo(line);
+		else
+			rc = slic_disable_echo(line);
+		break;
+
+	case SI3226X_SET_LINE_STATE:
+		rc = slic_set_line_state(line, arg);
+		break;
+
+	case SI3226X_CALL:
+		rc = copy_from_user(&caller_id, (__u8 __user *)arg, sizeof caller_id);
+		if (rc)
+			rc = -EFAULT;
+
+		if(caller_id.size > CALLERID_BUF_SIZE)
+			caller_id.size = CALLERID_BUF_SIZE;
+
+		rc = copy_from_user(callerid_buffer, (__u8 __user *)caller_id.data, caller_id.size);
+		if (rc)
+			rc = -EFAULT;
+
+		rc = slic_line_call(line, callerid_buffer, caller_id.size);
+		break;
+
+	case SI3226X_SEND_DTMF:
+		rc = slic_send_dtmf_digit(line, arg);
+		break;
+
+	case SI3226X_GET_HOOK_STATE:
+		rc = __put_user(line->hook_state, (__u8 __user *)arg);
+		if (rc)
+			rc = -EFAULT;
+		break;
+
+	case SI3226X_GET_DTMF_DIGIT:
+		rc = slic_get_dtmf_data(line, &data);
+		if (rc)
+			return -ENODATA;
+
+		rc = __put_user(data, (__u8 __user *)arg);
+		if (rc)
+			rc = -EFAULT;
+		break;
+
+	case SI3226X_GET_AUDIO_BLOCK_SIZE:
+		rc = __put_user(line->audio_buffer_size, (__u8 __user *)arg);
+		if (rc)
+			rc = -EFAULT;
+		break;
+
+	case SI3226X_SET_COMPANDING_MODE:
+		if(	arg != SI_ALAW && arg != SI_MLAW)
+			return -EINVAL;
+
+		rc = slic_set_companding_mode(line, arg);
+		if (rc)
+		{
+			dev_err(&line->tdm_dev->dev, "Can`t change companding mode\n");
+			return rc;
+		}
+
+		break;
+
+	case SI3226X_ENABLE_AUDIO:
+		if (line->tdm_dev == NULL)
+			return -ENODEV;
+
+		rc = tdm_run_audio(line->tdm_dev);
+		break;
+
+	case SI3226X_DISABLE_AUDIO:
+		if (line->tdm_dev == NULL)
+			return -ENODEV;
+
+		rc = tdm_stop_audio(line->tdm_dev);
+		break;
+	}
+
+	return rc;
+}
+
+static int slic_chr_open(struct inode *inode, struct file *file)
+{
+	struct slic_chr_dev *chr_dev;
+	struct si3226x_slic *slic;
+	struct si3226x_line *line;
+	struct tdm_device *tdm_dev;
+	struct tdm_voice_channel *ch;
+	int status = -ENXIO;
+
+	mutex_lock(&slic_chr_dev_lock);
+
+	list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+		smp_read_barrier_depends();
+
+		switch (chr_dev->type) {
+		case SLIC_CHR_DEV:
+			slic = dev_get_drvdata(chr_dev->dev);
+
+			if (slic->devt != inode->i_rdev)
+				continue;
+
+			if (slic->file_opened) {
+				status = -EBUSY;
+				goto out;
+			}
+
+			slic->file_opened = 1;
+			status = 0;
+			break;
+
+		case LINE_CHR_DEV:
+			line = dev_get_drvdata(chr_dev->dev);
+			tdm_dev = line->tdm_dev;
+
+			if (line->devt != inode->i_rdev)
+				continue;
+
+			if (line->file_opened) {
+				status = -EBUSY;
+				goto out;
+			}
+
+			line->audio_buffer_size = tdm_get_voice_block_size(tdm_dev);
+
+			line->rx_buffer = kzalloc(line->audio_buffer_size, GFP_KERNEL);
+			if (!line->rx_buffer) {
+				status = -ENOMEM;
+				goto out;
+			}
+
+			line->tx_buffer = kzalloc(line->audio_buffer_size, GFP_KERNEL);
+			if (!line->tx_buffer) {
+				kfree(line->rx_buffer);
+				status = -ENOMEM;
+				goto out;
+			}
+
+			/* store pointer to transmit wait_queue_head_t for use in poll() */
+			ch = tdm_dev->ch;
+			line->tx_wait_queue = &ch->tx_queue;
+			line->rx_wait_queue = &ch->rx_queue;
+
+			line->file_opened = 1;
+			status = 0;
+			break;
+		}
+
+		file->private_data = chr_dev;
+		status = 0;
+	}
+
+out:
+	smp_mb();
+	mutex_unlock(&slic_chr_dev_lock);
+	return status;
+}
+
+
+/*
+ * method "release" for character device
+ */
+static int slic_chr_release(struct inode *inode, struct file *file)
+{
+	struct slic_chr_dev *chr_dev = file->private_data;
+	struct si3226x_slic *slic;
+	struct si3226x_line *line;
+	struct tdm_device *tdm_dev;
+	int status = -ENXIO;
+
+	mutex_lock(&slic_chr_dev_lock);
+
+	switch (chr_dev->type) {
+	case SLIC_CHR_DEV:
+		slic = dev_get_drvdata(chr_dev->dev);
+
+		if(!slic->file_opened) {
+			status = -ENODEV;
+			goto out;
+		}
+
+		slic->file_opened = 0;
+		status = 0;
+		break;
+
+	case LINE_CHR_DEV:
+		line = dev_get_drvdata(chr_dev->dev);
+		tdm_dev = line->tdm_dev;
+
+		if(!line->file_opened) {
+			status = -ENODEV;
+			goto out;
+		}
+
+		kfree(line->rx_buffer);
+		kfree(line->tx_buffer);
+		line->file_opened = 0;
+		status = 0;
+	}
+
+	file->private_data = NULL;
+
+out:
+	mutex_unlock(&slic_chr_dev_lock);
+
+	return status;
+}
+
+/*
+ * method "ioctl" for character device
+ */
+static long
+slic_chr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct slic_chr_dev *chr_dev = file->private_data;
+	struct si3226x_slic *slic;
+	struct si3226x_line *line;
+	int status = -ENXIO;
+
+	if (_IOC_TYPE(cmd) != SI3226X_IOC_MAGIC)
+		return -ENOTTY;
+
+	mutex_lock(&slic_chr_dev_lock);
+
+	if (!file->private_data) {
+		status = -ENODEV;
+		goto out;
+	}
+
+	switch(chr_dev->type) {
+	case SLIC_CHR_DEV:
+		slic = dev_get_drvdata(chr_dev->dev);
+		if (!slic->file_opened) {
+			status = -ENODEV;
+			goto out;
+		}
+
+		status = slic_control(slic, cmd, arg);
+		break;
+
+	case LINE_CHR_DEV:
+		line = dev_get_drvdata(chr_dev->dev);
+		if (!line->file_opened) {
+			status = -ENODEV;
+			goto out;
+		}
+
+		status = line_control(line, cmd, arg);
+		break;
+	}
+
+out:
+	mutex_unlock(&slic_chr_dev_lock);
+	return status;
+}
+
+
+/*
+ * method "read" for character device
+ */
+static ssize_t
+slic_chr_read(struct file *file, char *buff, size_t count, loff_t *offp)
+{
+	struct slic_chr_dev *chr_dev = file->private_data;
+	struct si3226x_line *line;
+	struct tdm_device *tdm_dev;
+	int rc;
+
+	if (!file->private_data)
+		return -EPERM;
+
+	if(chr_dev->type != LINE_CHR_DEV)
+		return -EPERM;
+
+	mutex_lock(&slic_chr_dev_lock);
+
+	line = dev_get_drvdata(chr_dev->dev);
+	tdm_dev = line->tdm_dev;
+
+	if (count != line->audio_buffer_size) {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	rc = tdm_recv(tdm_dev, line->rx_buffer);
+	if (rc) {
+		rc = -EIO;
+		goto out;
+	}
+
+	rc = copy_to_user(buff, line->rx_buffer, count);
+	if (rc) {
+		rc = -EFAULT;
+		goto out;
+	}
+
+	rc = count;
+out:
+	mutex_unlock(&slic_chr_dev_lock);
+	return rc;
+}
+
+
+/*
+ * method "write" for character device
+ */
+static ssize_t
+slic_chr_write(struct file *file, const char *buf, size_t count, loff_t *offp)
+{
+	struct slic_chr_dev *chr_dev = file->private_data;
+	struct si3226x_line *line;
+	struct tdm_device *tdm_dev;
+	int rc;
+
+	if (!file->private_data)
+		return -EPERM;
+
+	if (chr_dev->type != LINE_CHR_DEV)
+		return -EPERM;
+
+	mutex_lock(&slic_chr_dev_lock);
+
+	line = dev_get_drvdata(chr_dev->dev);
+	tdm_dev = line->tdm_dev;
+
+	if (count != line->audio_buffer_size) {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	rc = copy_from_user(line->tx_buffer, buf, count);
+	if (rc) {
+		rc = -EFAULT;
+		goto out;
+	}
+
+	rc = tdm_send(tdm_dev, line->tx_buffer);
+	if (rc) {
+		rc = -EFAULT;
+		goto out;
+	}
+
+	rc = line->audio_buffer_size;
+out:
+	mutex_unlock(&slic_chr_dev_lock);
+	return rc;
+}
+
+
+/*
+ * method "poll" for character device
+ */
+static unsigned int slic_chr_poll(struct file *file, poll_table *wait)
+{
+	struct slic_chr_dev *chr_dev = file->private_data;
+	struct si3226x_line *line;
+	int mask = 0;
+
+	if (!file->private_data)
+		return -EPERM;
+
+	if (chr_dev->type != LINE_CHR_DEV)
+		return -EPERM;
+
+	line = dev_get_drvdata(chr_dev->dev);
+
+	poll_wait(file, line->rx_wait_queue,  wait);
+	poll_wait(file, line->tx_wait_queue,  wait);
+
+	if (tdm_poll_rx(line->tdm_dev))
+		mask |= POLLIN;
+
+	if (tdm_poll_tx(line->tdm_dev))
+		mask |= POLLOUT;
+
+	return mask;
+}
+
+
+/*
+ * file operations for slic character devices
+ */
+static struct file_operations slic_cnt_ops = {
+	.unlocked_ioctl = slic_chr_ioctl,
+	.open = slic_chr_open,
+	.read = slic_chr_read,
+	.write = slic_chr_write,
+	.poll = slic_chr_poll,
+	.release = slic_chr_release,
+	.llseek = no_llseek,
+	.owner = THIS_MODULE,
+};
+
+
+/**
+ * add to list slic character device.
+ * @param dev - character device
+ * @param type - type of character device
+ * @return 0 - ok
+ */
+static int add_slic_chr_dev(struct device *dev, enum chr_dev_type type)
+{
+	struct slic_chr_dev *chr_dev;
+
+	chr_dev = kzalloc(sizeof(*chr_dev), GFP_KERNEL);
+	if (!chr_dev)
+		return -ENOMEM;
+
+	mutex_lock(&slic_chr_dev_lock);
+	chr_dev->dev = dev;
+	chr_dev->type = type;
+	smp_wmb();
+	list_add(&chr_dev->list, &slic_chr_dev_list);
+	mutex_unlock(&slic_chr_dev_lock);
+
+	return 0;
+}
+
+
+/**
+ * delete slic character device form list
+ * @param dev - character device
+ * @return 0 - ok
+ */
+static int del_slic_chr_dev(struct device *dev)
+{
+	struct slic_chr_dev *chr_dev;
+
+	list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+		if (chr_dev->dev == dev) {
+			list_del(&chr_dev->list);
+			kfree(chr_dev);
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+
+/**
+ * Init slic driver
+ * @return 0 - ok
+ */
+static int init_slic_drv(struct si3226x_slic *slic)
+{
+	struct platform_device *pdev = slic->pdev;
+	struct si3226x_platform_data *plat = 	pdev->dev.platform_data;
+	struct si3226x_line *line = slic->lines;
+	struct device *chr_dev;
+	int rc;
+	int i;
+
+	dev_dbg(&pdev->dev, "run initialization slic driver\n");
+
+	/* set default companding_mode for all lines */
+	for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++)
+		line->companding_mode = plat->companding_mode;
+
+	/* request reset GPIO */
+	rc = gpio_request(slic->reset_gpio, DRIVER_NAME "_reset");
+	if (rc < 0) {
+		dev_err(&pdev->dev, "failed to request slic reset GPIO pin\n");
+		goto out0;
+	}
+	gpio_direction_output(slic->reset_gpio, 1);
+
+	/* request interrupt GPIO */
+	rc = gpio_request(slic->int_gpio, DRIVER_NAME "_irq");
+	if (rc < 0) {
+		dev_err(&pdev->dev, "failed to request slic irq GPIO pin\n");
+		goto out1;
+	}
+	gpio_direction_input(slic->int_gpio);
+
+	slic->irq = gpio_to_irq(slic->int_gpio);
+
+	INIT_WORK(&slic->irq_work, slic_irq_callback);
+
+#ifdef CONFIG_SI3226X_POLLING
+	INIT_DELAYED_WORK(&slic->delayed_work, slic_delayed_work);
+#endif
+
+
+	line = slic->lines;
+	for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++) {
+		if (plat->fxs_tdm_ch[i] < 0) {
+			line->state = SI_LINE_DISABLE;
+			continue;
+		}
+
+		line->state = SI_LINE_SILENCE;
+
+		/* added class device and create /dev/si3226x_fxsX.X */
+		line->devt = MKDEV(SI3226X_MAJOR, pdev->id + 1 + i);
+		chr_dev = device_create(slic_class, &pdev->dev, line->devt,
+		                        line, DRIVER_NAME "_fxs%d.%d", i, pdev->id);
+
+		if (IS_ERR(chr_dev)) {
+			dev_err(&pdev->dev, "can not added class device\n");
+			goto out4;
+		}
+
+		/*  added created character device into device list
+		    for use in file operations
+		*/
+		rc = add_slic_chr_dev(chr_dev, LINE_CHR_DEV);
+		if (rc) {
+			dev_err(&pdev->dev, "can't added character device\n");
+			goto out4;
+		}
+	}
+
+	rc = init_slic(slic);
+	if (rc) {
+		dev_err(&pdev->dev, "slic initialization fail\n");
+		goto out4;
+	}
+
+#ifdef CONFIG_SI3226X_POLLING
+	dev_info(&pdev->dev, "schedule delayed work\n");
+	schedule_delayed_work(&slic->delayed_work, msecs_to_jiffies(50));
+#else
+	dev_dbg(&pdev->dev, "request irq\n");
+
+	/* set interrupt callback slic_irq */
+	rc = request_irq(slic->irq, slic_irq,
+	                 IRQF_TRIGGER_FALLING | IRQF_DISABLED,
+	                 dev_name(&slic->pdev->dev), slic);
+
+	if (rc) {
+		dev_err(&pdev->dev, "can not request IRQ\n");
+		rc = -EINVAL;
+		goto out5;
+	}
+#endif
+
+	/* register slic character device */
+	rc = register_chrdev(SI3226X_MAJOR, DRIVER_NAME, &slic_cnt_ops);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "can not register character device\n");
+		goto out2;
+	}
+
+	/* added class device and create /dev/si3226x_cnt.X */
+	slic->devt = MKDEV(SI3226X_MAJOR, pdev->id);
+	chr_dev = device_create(slic_class, &pdev->dev, slic->devt,
+	                        slic, DRIVER_NAME "_cnt.%d", pdev->id);
+
+	if (IS_ERR(chr_dev)) {
+		dev_err(&pdev->dev, "can not added class device\n");
+		goto out3;
+	}
+
+	/*  added character device into device list
+	    for use in file operations */
+	rc = add_slic_chr_dev(chr_dev, SLIC_CHR_DEV);
+	if (rc) {
+		dev_err(&pdev->dev, "can not added character device\n");
+		goto out4;
+	}
+
+	dev_dbg(&pdev->dev, "success initialization slic driver\n");
+	return 0;
+
+out5:
+	free_irq(slic->irq, slic);
+	deinit_slic(slic);
+
+out4:
+	{
+		/* remove all character devices */
+		struct slic_chr_dev *chr_dev, *chr_dev_tmp;
+		list_for_each_entry_safe(chr_dev, chr_dev_tmp,
+		                         &slic_chr_dev_list, list) {
+			device_del(chr_dev->dev);
+			del_slic_chr_dev(chr_dev->dev);
+		}
+	}
+
+out3:
+	unregister_chrdev(SI3226X_MAJOR, DRIVER_NAME);
+
+out2:
+	gpio_free(slic->int_gpio);
+
+out1:
+	gpio_free(slic->reset_gpio);
+
+out0:
+	return rc;
+}
+
+
+
+/**
+ * DeInit slic driver
+ */
+void release_slic_drv(struct si3226x_slic *slic)
+{
+	struct platform_device *pdev = slic->pdev;
+	struct slic_chr_dev *chr_dev, *chr_dev_tmp;
+	int i;
+
+	dev_dbg(&pdev->dev, "release slic\n");
+
+	free_irq(slic->irq, slic);
+
+	/* delete slic character device */
+	list_for_each_entry_safe(chr_dev, chr_dev_tmp, &slic_chr_dev_list, list) {
+		struct si3226x_line *line;
+		struct si3226x_slic *s;
+		struct si3226x_line *l;
+
+		switch (chr_dev->type) {
+		case SLIC_CHR_DEV:
+			s = dev_get_drvdata(chr_dev->dev);
+			if (s == slic) {
+				device_del(chr_dev->dev);
+				list_del(&chr_dev->list);
+				kfree(chr_dev);
+			}
+			break;
+
+		case LINE_CHR_DEV:
+			l = dev_get_drvdata(chr_dev->dev);
+			for (i = 0, line = slic->lines;
+					i < SI3226X_MAX_CHANNELS; i++, line++)
+				if (l == line) {
+					device_del(chr_dev->dev);
+					list_del(&chr_dev->list);
+					kfree(chr_dev);
+				}
+			break;
+		}
+	}
+
+	unregister_chrdev(SI3226X_MAJOR, DRIVER_NAME);
+	gpio_free(slic->int_gpio);
+	gpio_free(slic->reset_gpio);
+
+	deinit_slic(slic);
+}
+
+
+
+/**
+ * Add device to slic devices list
+ * @param dev - device (spi, tdm or platform)
+ * @return 0 - ok
+ */
+static int add_to_slic_devices_list(struct device *dev, enum slic_dev_type type)
+{
+	struct slic_dev_list *slic_dev_item;
+
+	slic_dev_item = kzalloc(sizeof(*slic_dev_item), GFP_KERNEL);
+	if (!slic_dev_item)
+		return -ENOMEM;
+
+	mutex_lock(&dev_list_lock);
+	slic_dev_item->dev = dev;
+	slic_dev_item->type = type;
+	list_add_tail(&slic_dev_item->list, &slic_dev_list);
+	mutex_unlock(&dev_list_lock);
+
+	return 0;
+}
+
+
+/**
+ * remove device from slic devices list
+ * @param dev - device (spi, tdm or platform)
+ * @return 0 - ok
+ */
+static int remove_from_slic_devices_list(struct device *dev)
+{
+	struct slic_dev_list *slic_dev_item, *slic_dev_item_tmp;
+
+	list_for_each_entry_safe(slic_dev_item, slic_dev_item_tmp, &slic_dev_list, list)
+		if(slic_dev_item->dev == dev) {
+			list_del(&slic_dev_item->list);
+			kzfree(slic_dev_item);
+			return 0;
+		}
+
+	return -ENODEV;
+}
+
+
+/**
+ * allocate memory and configure slic descriptor.
+ * @param pdev - platform device
+ * @return allocated slic controller descriptor
+ */
+struct si3226x_slic *alloc_slic(struct platform_device *pdev) {
+	int i;
+	struct si3226x_slic 	*slic;
+	struct si3226x_line *line;
+
+	slic = kzalloc(sizeof *slic, GFP_KERNEL);
+	if (!slic)
+		return NULL;
+
+	platform_set_drvdata(pdev, slic);
+
+	slic->pdev = pdev;
+
+	line = slic->lines;
+	for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++)
+		line->ch = i;
+
+	return slic;
+}
+
+
+/**
+ * free memory allocated alloc_slic()
+ * @param slic - slic controller descriptor
+ * @return 0 - ok
+ */
+int free_slic(struct si3226x_slic *slic)
+{
+	if (!slic)
+		return -EINVAL;
+
+	kfree(slic);
+	return 0;
+}
+
+/**
+ * match all devices necessery for slic
+ * @param slic - pointer to pointer on slic private data
+ * @return 0 - ok, 1 - incomplete device list, 0< - any errors
+ */
+int slic_match_bus_devices(struct si3226x_slic **slic)
+{
+	struct slic_dev_list *plat_list_item;
+	struct slic_dev_list *spi_list_item;
+	struct slic_dev_list *tdm_list_item;
+	int i;
+
+
+	list_for_each_entry(plat_list_item, &slic_dev_list, list) {
+		struct platform_device *pdev =
+				to_platform_device(plat_list_item->dev);
+		struct si3226x_platform_data *plat = pdev->dev.platform_data;
+		struct tdm_device *found_tdm_devices[SI3226X_MAX_CHANNELS];
+		struct spi_device *found_spi_dev = NULL;
+		struct si3226x_line *slic_line;
+		int count_found_tdm;
+		int count_fxs = 0;
+
+		if (plat_list_item->type != PLAT_DEVICE)
+			continue;
+
+		/* increment count enabled fxs ports */
+		for (i = 0; i < SI3226X_MAX_CHANNELS; i++)
+			if(plat->fxs_tdm_ch[i] >= 0)
+				count_fxs++;
+
+		if (!count_fxs) {
+			dev_err(&pdev->dev, "Init slic fail. Disabled all FXS ports\n");
+			return -EINVAL;
+		}
+
+		dev_dbg(&pdev->dev, "count_fxs = %d\n", count_fxs);
+
+		/* foreach all spi devices and match with current slic */
+		list_for_each_entry(spi_list_item, &slic_dev_list, list) {
+			struct spi_device *spi_dev = to_spi_device(spi_list_item->dev);
+
+			if (spi_list_item->type != SPI_DEVICE)
+				continue;
+
+			if(plat->spi_chip_select != spi_dev->chip_select)
+				continue;
+
+			found_spi_dev = to_spi_device(spi_list_item->dev);
+		}
+
+		if (found_spi_dev == NULL) {
+			dev_dbg(&pdev->dev, "not found_spi_dev\n");
+			return 1;
+		}
+
+		dev_dbg(&pdev->dev, "found_spi_dev = %s\n",
+		        dev_name(&found_spi_dev->dev));
+
+		/* foreach all registered tdm devices for current slic */
+		/* and match tdm channel with fxs port tdm channel */
+		memset(found_tdm_devices, 0, sizeof *found_tdm_devices);
+		count_found_tdm = 0;
+		for (i = 0; i < SI3226X_MAX_CHANNELS; i++) {
+			if(plat->fxs_tdm_ch[i] < 0)
+				continue;
+
+			list_for_each_entry(tdm_list_item, &slic_dev_list, list) {
+				struct tdm_device *tdm_dev = to_tdm_device(tdm_list_item->dev);
+
+				if (tdm_list_item->type != TDM_DEVICE)
+					continue;
+
+				/* Match device tdm channel with corresponding fxs port */
+				if (plat->fxs_tdm_ch[i] == tdm_dev->tdm_channel_num) {
+					found_tdm_devices[i] = to_tdm_device(tdm_list_item->dev);
+					count_found_tdm++;
+				}
+			}
+		}
+
+		/* if found needed tdm devices for current slic */
+		if (count_fxs != count_found_tdm) {
+			dev_dbg(&pdev->dev, "tdm devices not found\n");
+			return 1;
+		}
+
+		*slic = alloc_slic(pdev);
+		if (!*slic) {
+			dev_err(&pdev->dev, "can't alloc slic\n");
+			return -ENOMEM;
+		}
+
+		(*slic)->spi_dev = found_spi_dev;
+		(*slic)->reset_gpio = plat->reset_gpio;
+		(*slic)->int_gpio = plat->int_gpio;
+
+
+		for (i = 0, slic_line = (*slic)->lines;
+		     i < SI3226X_MAX_CHANNELS; i++, slic_line++) {
+			slic_line->tdm_dev = found_tdm_devices[i];
+
+			/* Remove devices from list slic devices */
+			remove_from_slic_devices_list(&found_tdm_devices[i]->dev);
+		}
+
+		/* Remove devices from list slic devices */
+		remove_from_slic_devices_list(&found_spi_dev->dev);
+		remove_from_slic_devices_list(plat_list_item->dev);
+
+		return 0;
+	}
+
+	return 1;
+}
+
+
+
+/*
+ * probe spi driver
+ */
+static int probe_spi_slic(struct spi_device *spi_dev)
+{
+	struct si3226x_slic *slic;
+	int rc;
+
+	rc = add_to_slic_devices_list(&spi_dev->dev, SPI_DEVICE);
+	if (rc < 0) {
+		dev_err(&spi_dev->dev, "can't added spi device to slic list devices: %d\n", rc);
+		goto out;
+	}
+
+	rc = slic_match_bus_devices(&slic);
+	if (rc < 0) {
+		dev_err(&spi_dev->dev, "Error: %d\n", rc);
+		goto out;
+	}
+
+	if (rc) {
+		rc = 0;
+		goto out;
+	}
+
+	rc = init_slic_drv(slic);
+	if (rc)
+		free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+/*
+ * probe tdm driver
+ */
+static int probe_tdm_slic(struct tdm_device *tdm_dev)
+{
+	struct si3226x_slic *slic;
+	int rc;
+
+	mutex_lock(&dev_list_lock);
+
+	rc = add_to_slic_devices_list(&tdm_dev->dev, TDM_DEVICE);
+	if (rc < 0) {
+		dev_err(&tdm_dev->dev, "can't added tdm device to slic list devices: %d\n", rc);
+		goto out;
+	}
+
+	rc = slic_match_bus_devices(&slic);
+	if (rc < 0) {
+		dev_err(&tdm_dev->dev, "Error: %d\n", rc);
+		goto out;
+	}
+
+	if (rc) {
+		rc = 0;
+		goto out;
+	}
+
+	rc = init_slic_drv(slic);
+	if (rc)
+		free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+/*
+ * probe slic platform driver
+ */
+static int probe_slic(struct platform_device *pdev)
+{
+	struct si3226x_slic *slic;
+	int rc;
+
+	mutex_lock(&dev_list_lock);
+
+	rc = add_to_slic_devices_list(&pdev->dev, PLAT_DEVICE);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "can't added platform device to slic list devices: %d\n", rc);
+		goto out;
+	}
+
+	rc = slic_match_bus_devices(&slic);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "Error: %d\n", rc);
+		goto out;
+	}
+
+	if (rc) {
+		rc = 0;
+		goto out;
+	}
+
+	rc = init_slic_drv(slic);
+	if (rc)
+		free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+
+/*
+ * called when remove spi device
+ */
+static int __exit remove_spi_slic(struct spi_device *spi_dev)
+{
+	struct si3226x_slic *slic = NULL;
+	struct slic_chr_dev *chr_dev;
+	int rc = 0;
+
+	mutex_lock(&dev_list_lock);
+
+	/* find slic device supported current spi device */
+	list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+		struct si3226x_slic *p;
+		p = dev_get_drvdata(chr_dev->dev);
+
+		if (p->spi_dev == spi_dev) {
+			slic = p;
+			break;
+		}
+	}
+
+	/* If line device not found in character devices list */
+	if (!slic) {
+		remove_from_slic_devices_list(&spi_dev->dev);
+		goto out;
+	}
+
+	release_slic_drv(slic);
+	rc = free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+/*
+ * remove tdm driver
+ */
+static int __exit remove_tdm_slic(struct tdm_device *tdm_dev)
+{
+	struct si3226x_slic *slic = NULL;
+	struct si3226x_line *line = NULL;
+	struct slic_chr_dev *chr_dev;
+	int rc = 0;
+
+	mutex_lock(&dev_list_lock);
+
+	/* find slic device supported current tdm device */
+	list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+		struct si3226x_line *p;
+		p = dev_get_drvdata(chr_dev->dev);
+
+		if (p->tdm_dev == tdm_dev) {
+			line = p;
+			break;
+		}
+	}
+
+	/* If line device not found in character devices list */
+	if (!line) {
+		remove_from_slic_devices_list(&tdm_dev->dev);
+		goto out;
+	}
+
+	slic = to_si3226x_slic(line);
+
+	release_slic_drv(slic);
+	rc = free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+/*
+ * remove slic platform driver
+ */
+static int __exit remove_slic(struct platform_device *pdev)
+{
+	struct si3226x_slic *slic = NULL;
+	struct slic_chr_dev *chr_dev;
+	int rc = 0;
+
+	mutex_lock(&dev_list_lock);
+
+	/* find slic device supported current tdm device */
+	list_for_each_entry(chr_dev, &slic_chr_dev_list, list) {
+		struct si3226x_slic *p;
+		p = dev_get_drvdata(&pdev->dev);
+
+		if (p->pdev == pdev) {
+			slic = p;
+			break;
+		}
+	}
+
+	/* If line device not found in character devices list */
+	if (!slic) {
+		remove_from_slic_devices_list(&pdev->dev);
+		goto out;
+	}
+
+	release_slic_drv(slic);
+	rc = free_slic(slic);
+
+out:
+	mutex_unlock(&dev_list_lock);
+	return rc;
+}
+
+
+/*
+ * spi interface
+ */
+static struct spi_driver spi_slic_driver = {
+	.driver =
+	{
+		.name = "si3226x",
+		.bus    = &spi_bus_type,
+		.owner    = THIS_MODULE,
+	},
+	.probe    = probe_spi_slic,
+	.remove    = __exit_p(remove_spi_slic),
+};
+
+
+/*
+ * tdm Interface
+ */
+static struct tdm_driver tdm_slic_driver = {
+	.driver =
+	{
+		.name = "si3226x",
+		.bus    = &tdm_bus_type,
+		.owner    = THIS_MODULE,
+	},
+	.probe    = probe_tdm_slic,
+	.remove    = __exit_p(remove_tdm_slic),
+};
+
+
+/*
+ * slic platform interface
+ */
+static struct platform_driver slic_driver = {
+	.driver =
+	{
+		.name = "si3226x",
+		.bus    = &platform_bus_type,
+		.owner = THIS_MODULE,
+	},
+	.probe	= probe_slic,
+	.remove	= __exit_p(remove_slic),
+};
+
+
+/*
+ * init kernel module
+ */
+static int __init si3226x_mod_init(void)
+{
+	int rc = 0;
+
+	slic_class = class_create(THIS_MODULE, "slic");
+	if (IS_ERR(slic_class)) {
+		pr_debug("failed to register new class slic\n");
+		goto out0;
+	}
+
+	rc = spi_register_driver(&spi_slic_driver);
+	if (rc) {
+		pr_debug("failed to register SPI for SLIC, error: %d\n", rc);
+		goto out1;
+	}
+
+
+	rc = tdm_register_driver(&tdm_slic_driver);
+	if (rc) {
+		pr_debug("failed to register TDM for SLIC, error: %d\n", rc);
+		goto out2;
+	}
+
+
+	rc = platform_driver_register(&slic_driver);
+	if (rc) {
+		pr_debug("failed to register SLIC on platform, error: %d\n", rc);
+		goto out3;
+	}
+
+	return rc;
+
+out3:
+	tdm_unregister_driver(&tdm_slic_driver);
+
+out2:
+	spi_unregister_driver(&spi_slic_driver);
+out1:
+	class_destroy(slic_class);
+out0:
+	return rc;
+}
+
+
+/*
+ * deinit kernel module
+ */
+static void __exit si3226x_mod_exit(void)
+{
+	tdm_unregister_driver(&tdm_slic_driver);
+	spi_unregister_driver(&spi_slic_driver);
+	platform_driver_unregister(&slic_driver);
+	class_destroy(slic_class);
+}
+
+module_init(si3226x_mod_init);
+module_exit(si3226x_mod_exit);
+#define MODULE_NAME DRIVER_NAME
+//MODULE_NAME("si3226x");
+MODULE_AUTHOR("Michail Kurochkin <michail.kurachkin@promwad.com>");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/staging/si3226x/si3226x_drv.h b/drivers/staging/si3226x/si3226x_drv.h
new file mode 100644
index 0000000..8325984
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_drv.h
@@ -0,0 +1,188 @@
+/*
+ * si3226x_drv.h
+ * Driver SLIC Silabs si3226x
+ *
+ *  Created on: 01.03.2012
+ *      Author: Michail Kurochkin
+ */
+
+#ifndef SI3226X_DRV_H_
+#define SI3226X_DRV_H_
+
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include "../tdm/tdm.h"
+#include "slic_si3226x.h"
+
+#define DRIVER_NAME "si3226x"
+
+/**
+ * Calculate jiffies value for miliseconds
+ * @param ms - time in miliseconds
+ */
+#define RESET_SLIC_PERIOD 100 /*  100ms */
+#define DTMF_DIGIT_PERIOD 70 /*  DTMF tone delay */
+#define INCOMING_DTMF_BUF_SIZE 10 /*  Buffer size for incommint DTMF digits */
+#define CALLERID_BUF_SIZE 128 /*  buffer size for FSK caller id information */
+#define CALIBRATE_TIMEOUT 5000 /*  5s */
+#define ACCESS_TIMEOUT msecs_to_jiffies(1000) /*  register access timeout */
+#define WAIT_RING_TIMEOUT msecs_to_jiffies(3000) /*  waiting ring signal timeout */
+
+#define SI3226X_MAJOR 40
+/*
+ * Caller ID modes
+ */
+enum callerid_modes
+{
+	SI_FSK_BELLCORE,
+	SI_FSK_ETSI,
+	SI_CALLERID_DTMF,
+	SI_CALLERID_NONE,
+};
+
+
+/*
+ * high level line states
+ */
+enum line_states
+{
+	SI_LINE_DISABLE, /*  disable line and voltage */
+	SI_LINE_SILENCE, /*  hook on */
+	SI_LINE_WAIT, /*  generate long tone signals */
+	SI_LINE_INVITATION, /*  generate permanent tone to line */
+	SI_LINE_BUSY, /*  generate short tone signals to line */
+	SI_LINE_TALK, /*  voice transaction, audio enable */
+};
+
+/*
+ * current telephone hook states
+ */
+enum hook_states
+{
+	SI_HOOK_ON,
+	SI_HOOK_OFF,
+};
+
+/*
+ * universal circular fifo buffer
+ */
+struct circ_buffer {
+	u8 *buf;
+	int count;
+	int item_size;
+	int read_pointer;
+	int write_pointer;
+};
+
+
+/*
+ * si3226x line structure
+ */
+struct si3226x_line
+{
+	u8 ch; /*  number of fxs channel */
+
+	dev_t devt; /*  major/minor number of line device */
+
+	enum line_states state; /*  current line state */
+	enum hook_states hook_state; /*  current telephone hook state */
+
+	enum companding_modes companding_mode; /*  a-law or m-law */
+
+	/*  audio buffers */
+	u8 *tx_buffer; /*  private transmit buffer */
+	u8 *rx_buffer; /*  private receive buffer */
+	int audio_buffer_size; /*  audio buffer size */
+
+	wait_queue_head_t *rx_wait_queue;
+	wait_queue_head_t *tx_wait_queue;
+
+	struct tdm_device *tdm_dev; /*  tdm device for transfer audio data */
+
+	u8 file_opened;  /*  flag indicate opened device file */
+
+	enum callerid_modes callerid_mode; /*  SI_CALLERID_DTMF, SI_FSK_BELLCORE, SI_FSK_ETSI */
+	struct work_struct line_call_work; /*  work queue for line call and caller_id routine */
+	u8 caller_id_buf[255]; /*  caller_id data buffer for send in line */
+	int caller_id_buf_size; /*  caller_id size of data buffer */
+
+	struct circ_buffer dtmf; /* buffer for incomming all DTMF digits */
+	struct circ_buffer fsk; /* buffer for transmit fsk */
+};
+
+
+/*
+ * si3226x controller structure
+ */
+struct si3226x_slic
+{
+	dev_t devt;
+
+	struct platform_device *pdev;
+	struct spi_device *spi_dev;
+	struct si3226x_line lines[SI3226X_MAX_CHANNELS];
+
+	int irq; /*  IRQ number for slic */
+	int reset_gpio; /*  GPIO for reset slic */
+	int int_gpio; /*  GPIO interrupt for slic */
+	struct work_struct irq_work;
+
+#ifdef CONFIG_SI3226X_POLLING
+	struct delayed_work delayed_work;
+#endif
+
+	u8 file_opened : 1;  /*  flag indicate opened device file */
+};
+
+
+/*
+ * slic device types
+ */
+enum slic_dev_type
+{
+	PLAT_DEVICE,
+	SPI_DEVICE,
+	TDM_DEVICE,
+};
+
+/*
+ * container for slic list probed devices
+ */
+struct slic_dev_list
+{
+	struct list_head list;
+	struct device *dev;
+	enum slic_dev_type type;
+};
+
+/*
+ * device data types
+ */
+enum chr_dev_type
+{
+	SLIC_CHR_DEV, /*  type si3226x_slic */
+	LINE_CHR_DEV, /*  type si3226x_line */
+};
+
+/*
+ * container for list registred character devices
+ */
+struct slic_chr_dev
+{
+	struct list_head list;
+	struct device *dev; /*  character device; */
+	enum chr_dev_type type; /*  character device type; */
+};
+
+
+static inline struct si3226x_slic *to_si3226x_slic(struct si3226x_line *line)
+{
+	return line ? container_of(line - line->ch, struct si3226x_slic, lines[0]) : NULL;
+}
+
+void release_slic_drv(struct si3226x_slic *slic);
+int free_slic(struct si3226x_slic *slic);
+
+#endif /* SI3226X_DRV_H_ */
diff --git a/drivers/staging/si3226x/si3226x_hw.c b/drivers/staging/si3226x/si3226x_hw.c
new file mode 100644
index 0000000..6a3f81c
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_hw.c
@@ -0,0 +1,1691 @@
+/**********************************************************************
+ * Author: Michail Kurachkin
+ *
+ * Contact: michail.kurachkin@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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include "../tdm/tdm.h"
+#include "si3226x_drv.h"
+#include "si3226x_hw.h"
+#include "slic_si3226x.h"
+
+/*  Include patch for si3226x */
+#include "si3226x_patch_C_FB_2011MAY19.c"
+
+/*  Include generated early setup file */
+#include "si3226x_setup.c"
+
+/*  Include generated dtmf digit table */
+#include "slic_dtmf_table.c"
+
+static DEFINE_MUTEX(spi_add_lock);
+
+
+/*
+ * si3226x register configurations for line tones
+ */
+struct si3226x_timer_regs slic_tones[] = {
+	{   /*  signal invitation */
+		.osc_amp = 0x784000,
+		.osc_freq = 0x78F0000,
+		.o_talo = 0,
+		.o_tahi = 0,
+		.o_tilo = 0,
+		.o_tihi = 0,
+	},
+	{   /*  signal busy */
+		.osc_amp = 0x784000,
+		.osc_freq = 0x78F0000,
+		.o_talo = 0xF0,
+		.o_tahi = 0xA,
+		.o_tilo = 0xF0,
+		.o_tihi = 0xA,
+	},
+	{   /*  signal wait */
+		.osc_amp = 0x7FC000,
+		.osc_freq = 0x7810000,
+		.o_talo = 0xE0,
+		.o_tahi = 0x2E,
+		.o_tilo = 0x0,
+		.o_tihi = 0x7D,
+	},
+};
+
+
+
+
+
+/**
+ * Increment pointer in circular buffer
+ * @param pointer - pointer in circular buffer
+ * @param item_size - item size in bytes
+ * @param count_items - count items in fifo
+ */
+static void inc_pointer(int *pointer, int item_size, int count_items)
+{
+	(*pointer) += item_size;
+
+	if(*pointer >= (count_items * item_size))
+		*pointer = 0;
+}
+
+
+/**
+ * Init circular fifo buffer
+ * @param buf - buffer descriptor
+ * @param item_size - item size in bytes
+ * @param count - count items in buffer
+ * @return 0 - ok
+ */
+static int cb_init(struct circ_buffer *cb, int item_size, int count)
+{
+	cb->read_pointer = 0;
+	cb->write_pointer = 0;
+
+	cb->buf = kzalloc(item_size * count, GFP_KERNEL);
+	if (!cb->buf)
+		return -ENOMEM;
+
+	cb->count = count;
+	cb->item_size = item_size;
+	return 0;
+}
+
+
+/**
+ * free fifo buffer
+ * @param cb
+ */
+static void cb_free(struct circ_buffer *cb)
+{
+	if (!cb)
+		return;
+
+	if (cb->buf)
+		kfree(cb->buf);
+
+	kfree(cb);
+}
+
+
+/**
+ * Push into fifo
+ * @param cb - fifo descriptor
+ * @param item - pointer to item
+ * @return 0 - ok, 1 - item pushed and buffer is full
+ */
+static int cb_push(struct circ_buffer *cb, void *item)
+{
+	memcpy(cb->buf + cb->write_pointer, item, cb->item_size);
+
+	inc_pointer(&cb->write_pointer, cb->item_size, cb->count);
+
+	if(cb->write_pointer == cb->read_pointer) { // If fifo is full
+		inc_pointer(&cb->read_pointer, cb->item_size, cb->count);
+		return 1;
+	}
+
+	return 0;
+}
+
+
+
+/**
+ * Check fifo is empty
+ * @param cb - fifo descriptor
+ * @return 1 - buffer is empty
+ */
+static int cb_iis_empty(struct circ_buffer *cb)
+{
+	return cb->read_pointer == cb->write_pointer;
+}
+
+
+/**
+ * Pop from fifo buffer
+ * @param cb - fifo descriptor
+ * @param item - pointer to poped item
+ * @return 0 - ok, 1 - buffer is empty
+ */
+static int cb_pop(struct circ_buffer *cb, void *item)
+{
+	if(cb->read_pointer == cb->write_pointer)
+		return 1;
+
+	memcpy(item, cb->buf + cb->read_pointer, cb->item_size);
+	inc_pointer(&cb->read_pointer, cb->item_size, cb->count);
+	return 0;
+}
+
+
+
+
+
+/**
+ * convert heximal dtmf code to ASCII code
+ * @param dtmf - heximal dtmf code
+ * @return ASCII code
+ */
+static char conv_dtmf_to_char(u8 dtmf)
+{
+	return dtmf_codes[dtmf];
+}
+
+/**
+ * ASCII code to convert heximal dtmf code
+ * @param �� - ASCII code
+ * @return heximal dtmf code or -EINVAL
+ */
+static int conv_char_to_dtmf(char ch)
+{
+	int i;
+
+	for(i = 0; i < sizeof dtmf_codes - 1; i++)
+		if(dtmf_codes[i] == ch)
+			return i;
+
+	return -EINVAL;
+}
+
+
+/**
+ * Reset SLIC
+ */
+static int slic_reset(struct si3226x_slic *slic)
+{
+	gpio_set_value(slic->reset_gpio, 0);
+	msleep(RESET_SLIC_PERIOD);
+	gpio_set_value(slic->reset_gpio, 1);
+
+	return 0;
+}
+
+
+/**
+ * Write value into SLIC register
+ * @param channel - channel number
+ * @param addr - address SLIC register
+ * @param data - register value
+ * @return
+ * 		0 - OK
+ * 		1 - ERROR
+ */
+int slic_write_reg(struct si3226x_line *line, u8 addr, u8 data)
+{
+	u8 ch;
+	int rc;
+	int i;
+	struct si3226x_slic *slic = to_si3226x_slic(line);
+
+	mutex_lock(&spi_add_lock);
+
+	if (line && line->ch >= SI3226X_MAX_CHANNELS) {
+		mutex_unlock(&spi_add_lock);
+		dev_err(&line->tdm_dev->dev, "Incorrect slic line number\n");
+		return -EINVAL;
+	}
+
+	if(line == NULL)
+		ch = 0;
+	else
+		ch = line->ch;
+
+
+	/**
+	 * bit 7 - BRDCST - Indicates a broadcast operation that is intended
+	 * for all devices in the daisy chain. This is
+	 * only valid for write operations since it would cause contention
+	 * on the SDO pin during a read.
+		bit 6 - R/W - Read/Write Bit.
+			0 = Write operation.
+			1 = Read operation.
+		bit 5 - REG/RAM - Register/RAM Access Bit.
+			0 = RAM access.
+			1 = Register access.
+		bit 4:0 -  CID[4:0] - Indicates the channel that is targeted by
+		 	the operation. Note that the 4-bit channel value is
+		 	provided LSB first. The devices reside on the daisy
+		 	chain such that device 0 is nearest to the controller,
+		 	and device 15 is furthest down the SDI/SDU_THRU chain.
+		 	(See Figure 41.)
+			As the CID information propagates down the daisy chain,
+			each channel decrements the CID by 1. The SDI nodes
+			between devices reflect a decrement of 2 per device
+			since each device contains two channels. The device
+			receiving a value of 0 in the CID field responds
+			to the SPI transaction. (See Figure 42.) If a broadcast
+			to all devices connected to the chain is requested,
+			the CID does not  decrement. In this case, the same
+			8-bit or 16-bit data is pre-sented to all channels
+			regardless of the CID values.
+	 */
+	{
+
+		u8 write_transaction[] = {
+			(1 << 5) | ((ch & 1) << 4), /*  control byte */
+			addr,
+			data,
+		};
+
+		for(i = 0; i < sizeof write_transaction; i++) {
+			rc = spi_write(slic->spi_dev, write_transaction + i, 1);
+			if (rc) {
+				dev_err(&line->tdm_dev->dev,
+						"Can't write slic register addr: %d, value: %d\n",
+						addr, data);
+				mutex_unlock(&spi_add_lock);
+				return rc;
+			}
+		}
+	}
+
+	mutex_unlock(&spi_add_lock);
+	return rc;
+}
+
+
+/**
+ * Read value from SLIC register
+ * @param line - line descriptor or NULL if read GLOBAL register
+ * @param addr - address SLIC register
+ * @param data - pointer to read data
+ * @return 0 - ok
+ */
+static int slic_read_reg(struct si3226x_line *line, u8 addr, u8 *data)
+{
+	int rc;
+	u8 ch;
+	int i;
+	struct si3226x_slic *slic = to_si3226x_slic(line);
+
+	mutex_lock(&spi_add_lock);
+
+	if (line && line->ch >= SI3226X_MAX_CHANNELS) {
+		mutex_unlock(&spi_add_lock);
+		return -EINVAL;
+	}
+
+	ch = line->ch;
+
+	{
+		u8 read_transaction[] = {
+			(1 << 5) | (1 << 6) | ((ch & 1) << 4), /*  control byte */
+			addr,
+		};
+
+		for(i = 0; i < sizeof read_transaction; i++) {
+			rc = spi_write(slic->spi_dev, read_transaction + i, 1);
+			if (rc) {
+				mutex_unlock(&spi_add_lock);
+				return rc;
+			}
+		}
+	}
+
+	rc = spi_read(slic->spi_dev, data, 1);
+
+	mutex_unlock(&spi_add_lock);
+	return rc;
+}
+
+
+
+/**
+ * Write value into SLIC RAM
+ * @param channel - channel number
+ * @param addr - address SLIC RAM
+ * @param value - value of SLIC RAM
+ * @return
+ *
+ */
+int slic_write_ram(struct si3226x_line *line, u16 addr, u32 value)
+{
+	int rc;
+	unsigned long timeout;
+	u8 state;
+	u8 *write_data;
+	u32 val = value << 3;
+
+	write_data = (u8 *)&val;
+
+	if((!line) || line->ch >= SI3226X_MAX_CHANNELS)
+		return -EINVAL;
+
+	value <<= 3;
+
+	rc = slic_write_reg(line, SI_RAM_ADDR_HI, (addr >> 3) & 0xe0);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_RAM_DATA_B0,  write_data[0] & 0xF8);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_RAM_DATA_B1,  write_data[1]);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_RAM_DATA_B2,  write_data[2]);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_RAM_DATA_B3,  write_data[3]);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_RAM_ADDR_LO, addr & 0xFF);
+	if (rc)
+		return rc;
+
+	timeout = jiffies + ACCESS_TIMEOUT;
+	do {
+		rc = slic_read_reg(line, SI_REG_RAMSTAT, &state);
+		if (rc)
+			return rc;
+
+		state &= 1;
+		if(time_after(jiffies, timeout)) {
+			dev_err(&line->tdm_dev->dev, "Can`t write access to slic RAM\n");
+			return -EBUSY;
+		}
+	} while (state);
+
+	return 0;
+}
+
+
+
+/**
+ * Lock channel. To protect against undersirable/unintended
+ * system operation, there are certain critical register control
+ * bits that are protected by default against unintentional
+ * modification.
+ * @param channel - channel number
+ * @return 0 - ok
+ */
+int slic_lock_channel(struct si3226x_line *line)
+{
+	int rc;
+	int i;
+	u8 state;
+	unsigned long timeout;
+	u8 unlock_data[] = {0x2, 0x8, 0xE, 0};
+
+	timeout = jiffies + ACCESS_TIMEOUT;
+	do {
+		rc = slic_read_reg(line, SI_REG_UAM, &state);
+		if (rc)
+			return rc;
+
+		if((state & 1) == 0) /*  if channel already locked */
+			return 0;
+
+		for(i = 0; i < sizeof unlock_data; i++) {
+			rc = slic_write_reg(line, SI_REG_UAM, unlock_data[i]);
+			if (rc)
+				return rc;
+		}
+
+		rc = slic_read_reg(line, SI_REG_UAM, &state);
+		if (rc)
+			return rc;
+
+		if(time_after(jiffies, timeout)) {
+			dev_err(&line->tdm_dev->dev,
+				"can`t lock slic channel %d\n", line->ch);
+			return -EBUSY;
+		}
+	} while ((state & 1));
+
+	return 0;
+}
+
+
+/**
+ * Unlock channel. While in protected mode, any writes to
+ * protected register bits or protected RAM space are simply
+ * ignored by the si3226x.
+ * @param channel - channel number
+ * @return 0 - ok
+ */
+int slic_unlock_channel(struct si3226x_line *line)
+{
+	int rc;
+	int i;
+	u8 state;
+	unsigned long timeout;
+
+	/* unlock data consecution */
+	u8 unlock_data[] = {0x2, 0x8, 0xE, 0};
+
+	timeout = jiffies + ACCESS_TIMEOUT;
+	do {
+		rc = slic_read_reg(line, SI_REG_UAM, &state);
+		if (rc)
+			return rc;
+
+		if((state & 1) != 0) /*  if channel already unlocked */
+			return 0;
+
+		for(i = 0; i < sizeof unlock_data; i++) {
+			rc = slic_write_reg(line, SI_REG_UAM, unlock_data[i]);
+			if (rc)
+				return rc;
+		}
+
+		rc = slic_read_reg(line, SI_REG_UAM, &state);
+		if (rc)
+			return rc;
+
+		if(time_after(jiffies, timeout)) {
+			dev_err(&line->tdm_dev->dev,
+				"can`t unlock slic channel %d\n", line->ch);
+			return -EBUSY;
+		}
+	} while ((!(state & 1)));
+
+	return 0;
+}
+
+
+/**
+ * Calibrate slic
+ * @param slic - slic descriptor
+ * @return 0 - ok
+ */
+int slic_calibrate(struct si3226x_line *line)
+{
+	int rc;
+	u8 data;
+	unsigned long timeout;
+
+	rc = slic_write_reg(line, SI_REG_CALR3, SI_VAL_CAL_EN);
+	if (rc)
+		return rc;
+
+	timeout = jiffies + msecs_to_jiffies(CALIBRATE_TIMEOUT);
+	do {
+		rc = slic_read_reg(line, SI_REG_CALR3, &data);
+		if (rc)
+			return rc;
+
+		data &= SI_VAL_CAL_EN;
+
+		if(time_after(jiffies, timeout)) {
+			dev_err(&line->tdm_dev->dev,
+			        "Can`t calibrate slic line %d\n",
+			        line->ch);
+			return -EBUSY;
+		}
+	} while(data);
+
+
+	return 0;
+}
+
+
+/**
+ * Enable hardware echo cancelation
+ * @param line - line
+ * @return 0 - ok
+ */
+int slic_enable_echo(struct si3226x_line *line)
+{
+	int rc;
+	u8 data;
+
+	rc = slic_read_reg(line, SI_REG_DIGCON, &data);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_DIGCON, data & (~SI_VAL_HYB_DIS));
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+
+/**
+ * Disable hardware echo cancelation
+ * @param line - line descriptor
+ * @return 0 - ok
+ */
+int slic_disable_echo(struct si3226x_line *line)
+{
+	int rc;
+	u8 data;
+
+	rc = slic_read_reg(line, SI_REG_DIGCON, &data);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_DIGCON, data | SI_VAL_HYB_DIS);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+
+/**
+ * function send FSK data from fifo to FXS line.
+ * called by irq when hardware fsk buffer is empty
+ * @param line - line descriptor
+ */
+static void do_fsk(struct si3226x_line *line)
+{
+	u8 data;
+	int rc;
+
+	/*  put 8 byte into hardware fsk fifo */
+	rc = cb_pop(&line->fsk, &data);
+	if (rc) {
+		mdelay(300);
+		slic_write_reg(line, SI_REG_OCON, 0);
+		return;
+	}
+
+	slic_write_reg(line, SI_REG_FSKDAT, data);
+}
+
+
+/**
+ * Run sending fsk caller id procedure.
+ * asynchronous send fsk data by irq
+ * @param line - line descriptor
+ * @param mode - type of caller id. Declared in enum callerid_modes
+ * @param buf - caller id data
+ * @param size - caller id size
+ * @return 0 - ok
+ */
+static int
+slic_send_fsk(struct si3226x_line *line, int mode, u8 *buf, int size)
+{
+	int rc;
+	int i;
+	u8 data;
+
+	if (!size)
+		return -EINVAL;
+
+	if (!cb_iis_empty(&line->fsk)) {
+		rc = cb_init(&line->fsk, 1, CALLERID_BUF_SIZE);
+		if (rc)
+			return rc;
+	}
+
+	data = 0x55;
+	for (i = 0; i < 38; i++)
+		cb_push(&line->fsk, &data);
+
+	data = 0xFF;
+	for (i = 0; i < 19; i++)
+		cb_push(&line->fsk, &data);
+
+	for (i = 0; i < size; i++)
+		cb_push(&line->fsk, buf + i);
+
+	rc = slic_write_reg(line, SI_REG_OMODE,
+	                    SI_VAL_OSC1_FSK | SI_VAL_ROUTING_1_2);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_FSKDEPTH, SI_VAL_FSK_FLUSH);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_FSKDEPTH, 0);
+	if (rc)
+		return rc;
+
+
+	switch (mode) {
+	case SI_FSK_BELLCORE:
+		rc = slic_write_ram(line, SI_RAM_FSKAMP0, 0x105E000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKAMP1, 0x8BE000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKFREQ0, 0x6B60000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKFREQ1, 0x79C0000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSK01, 0x2232000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSK10, 0x77C2000);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_FSK_ETSI:
+		rc = slic_write_ram(line, SI_RAM_FSKAMP0, 0x340000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKAMP1, 0x1FA000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKFREQ0, 0x6D20000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSKFREQ1, 0x78B0000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSK01, 0x26E4000);
+		if (rc)
+			return rc;
+
+		rc = slic_write_ram(line, SI_RAM_FSK10, 0x694C000);
+		if (rc)
+			return rc;
+
+		break;
+	}
+
+	rc = slic_write_reg(line, SI_REG_O1TALO, 0x14);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_O1TAHI, 0);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_OCON, SI_VAL_OSC1_EN | SI_VAL_OSC1_TA_EN);
+	if (rc)
+		return rc;
+
+	do_fsk(line);
+	return 0;
+}
+
+
+/**
+ * Disable DTMF recognizing on FXS line
+ * @param line
+ * @return 0 - ok
+ */
+static int disable_dtmf_detect(struct si3226x_line *line)
+{
+	return slic_write_reg(line, SI_REG_TONEN, 0xe3);
+}
+
+/**
+ * Enable DTMF recognizing on FXS line
+ * @param line
+ * @return 0 - ok
+ */
+static int enable_dtmf_detect(struct si3226x_line *line)
+{
+	return slic_write_reg(line, SI_REG_TONEN, 0xe0);
+}
+
+/**
+ * Send callerID data
+ * @param line - line
+ * @param buf - callerID buffer
+ * @param size - buffer size
+ * @return 0 - ok
+ */
+static int slic_send_callerid(struct si3226x_line *line, char *buf, int size)
+{
+	int rc;
+	int i;
+
+	switch(line->callerid_mode) {
+	case SI_CALLERID_DTMF:
+		rc = disable_dtmf_detect(line);
+		if (rc)
+			return rc;
+
+		for(i = 0; i < size; i++) {
+			rc = slic_send_dtmf_digit(line, buf[i]);
+
+			if(rc || line->hook_state)
+				return enable_dtmf_detect(line);
+
+			msleep(70);
+		}
+
+		rc = enable_dtmf_detect(line);
+		if (rc)
+			return rc;
+
+		break;
+
+
+	case SI_FSK_BELLCORE:
+		rc = slic_send_fsk(line, SI_FSK_BELLCORE, buf, size);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_FSK_ETSI:
+		rc = slic_send_fsk(line, SI_FSK_ETSI, buf, size);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_CALLERID_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+
+
+/**
+ * Set hardware slic channel state
+ * @param line - line descriptor
+ * @param state - hardware line state
+ * @return 0 - ok
+ */
+static int
+slic_set_linefeed_state(struct si3226x_line *line, enum fxs_states state)
+{
+	int rc;
+
+	rc = slic_write_reg(line, SI_REG_LINEFEED, state);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+
+/**
+ * send ring signal to line and send callerid if not null.
+ * Functon is blocking while sending callerid
+ * @param line - line descriptor
+ * @param callerid_buf - buffer witch callerid
+ * @param size - caller id buffer size
+ * @return 0 - ok
+ */
+int slic_line_call(struct si3226x_line *line, u8 *callerid_buf, int size)
+{
+	int stored_size;
+
+	stored_size = min((int)sizeof(line->caller_id_buf) - 1, size);
+	memcpy(line->caller_id_buf, callerid_buf, stored_size);
+	line->caller_id_buf_size = stored_size;
+
+	schedule_work(&line->line_call_work);
+	return 0;
+}
+
+int slic_get_dtmf_data(struct si3226x_line *line, char *data)
+{
+	return cb_pop(&line->dtmf, data);
+}
+
+/**
+ * Work queue IRQ callback handled all slic events
+ * @param work - work queue item
+ */
+static void do_line_call(struct work_struct *work)
+{
+	struct si3226x_line *line = container_of(work, struct si3226x_line, line_call_work);
+	unsigned long timeout;
+	u8 data;
+	int rc;
+
+	rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_OHT);
+	if (rc)
+		return;
+
+	rc = slic_set_linefeed_state(line, SI_FXS_RINGING);
+	if (rc)
+		return;
+
+	if(line->callerid_mode == SI_CALLERID_NONE)
+		return;
+
+	msleep(30);
+
+	timeout = jiffies + WAIT_RING_TIMEOUT;
+	do {
+		rc = slic_read_reg(line, SI_REG_LINEFEED, &data);
+		if (rc)
+			return;
+
+		if(time_after(jiffies, timeout)) {
+			dev_err(&line->tdm_dev->dev,
+			        "ring signal not detected on line %d\n",
+			        line->ch);
+			return;
+		}
+		schedule();
+	} while (data & (1 << 6));
+
+	msleep(200);
+
+	rc = slic_send_callerid(line, line->caller_id_buf, line->caller_id_buf_size);
+	if (rc)
+		return;
+}
+
+/**
+ * Setup slic hardware timer
+ * @param line - line descriptor
+ * @param timer_num - number of timer
+ * @param timer_regs - list specified timer registers values
+ * @return 0 - ok
+ */
+static int
+slic_set_timer(struct si3226x_line *line, u8 timer_num,
+               struct si3226x_timer_regs *timer_regs)
+{
+	int rc;
+
+	if(timer_num > 1)
+		return -EINVAL;
+
+	rc = slic_write_ram(line, SI_RAM_OSC1AMP + timer_num * 3,
+	                    timer_regs->osc_amp);
+	if (rc)
+		return rc;
+
+	rc = slic_write_ram(line, SI_RAM_OSC1FREQ + timer_num * 3,
+	                    timer_regs->osc_freq);
+	if (rc)
+		return rc;
+
+	rc = slic_write_ram(line, SI_RAM_OSC1PHAS + timer_num * 3, 0);
+	if (rc)
+		return rc;
+
+	/*  configure timer delay */
+	rc = slic_write_reg(line, SI_REG_O1TALO + timer_num * 4,
+	                    timer_regs->o_talo);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_O1TAHI + timer_num * 4,
+	                    timer_regs->o_tahi);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_O1TILO + timer_num * 4,
+	                    timer_regs->o_tilo);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_O1TIHI + timer_num * 4,
+	                    timer_regs->o_tihi);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+
+/**
+ * Send tone signal to line
+ * @param line - line descriptor
+ * @param type - type of signal (defined in array slic_tones[])
+ * @return 0 - ok
+ */
+static int
+slic_set_signal_to_line(struct si3226x_line *line, enum tone_types type)
+{
+	int rc;
+
+	if (type >= ARRAY_SIZE(slic_tones))
+		return -EINVAL;
+
+	/*  disable tone if needed */
+	if(type == SI_TONE_NONE) {
+		rc = slic_write_reg(line, SI_REG_OCON, 0);
+		return rc;
+	}
+
+	rc = slic_set_timer(line, 0, slic_tones +(int)type);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_OMODE,
+	                    SI_VAL_ROUTING_1_3 | SI_VAL_ZERO_EN_1);
+	if (rc)
+		return rc;
+
+	switch(type) {
+	case SI_TONE_INVITATION:
+		rc = slic_write_reg(line, SI_REG_OCON, SI_VAL_OSC1_EN);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_TONE_BUSY:
+	case SI_TONE_WAIT:
+		rc = slic_write_reg(line, SI_REG_OCON,
+		                    SI_VAL_OSC1_EN | SI_VAL_OSC1_TA_EN | SI_VAL_OSC1_TI_EN);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_TONE_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Set high level line state
+ * @param line - line descriptor
+ * @param state - needed line state
+ * @return 0 - ok
+ */
+int slic_set_line_state(struct si3226x_line *line, int state)
+{
+	int rc;
+
+	if (state > SI_LINE_TALK)
+		return -EINVAL;
+
+	switch(state) {
+	case SI_LINE_DISABLE:
+		rc = slic_set_linefeed_state(line, SI_FXS_OPEN);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_LINE_SILENCE:
+		rc = slic_set_signal_to_line(line, SI_TONE_NONE);
+		if (rc)
+			return rc;
+
+		rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_ACTIVE);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_LINE_INVITATION:
+		rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_ACTIVE);
+		if (rc)
+			return rc;
+
+		rc = slic_set_signal_to_line(line, SI_TONE_INVITATION);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_LINE_WAIT:
+		rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_ACTIVE);
+		if (rc)
+			return rc;
+
+		rc = slic_set_signal_to_line(line, SI_TONE_WAIT);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_LINE_BUSY:
+		rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_ACTIVE);
+		if (rc)
+			return rc;
+
+		rc = slic_set_signal_to_line(line, SI_TONE_BUSY);
+		if (rc)
+			return rc;
+
+		break;
+
+	case SI_LINE_TALK:
+		rc = slic_set_signal_to_line(line, SI_TONE_NONE);
+		if (rc)
+			return rc;
+
+		rc = slic_set_linefeed_state(line, SI_FXS_FORWARD_ACTIVE);
+		if (rc)
+			return rc;
+
+		break;
+	}
+
+	return 0;
+}
+
+
+
+/**
+ * Send one digit by DTMF
+ * @param line - line
+ * @param ch - heximal DTMF code
+ * @return 0 - ok
+ */
+int slic_send_dtmf_digit(struct si3226x_line *line, char ch)
+{
+	int rc;
+	int i;
+	unsigned long timeout;
+	u8 data;
+	int dtmf_ch;
+	static struct si3226x_timer_regs timer_regs[2];
+	u16 dtmf_delay = DTMF_DIGIT_PERIOD * 1000 / 125;
+
+	dtmf_ch = conv_char_to_dtmf(ch);
+	if (dtmf_ch < 0)
+		return dtmf_ch;
+
+	if(dtmf_ch > 0xF)
+		return -EINVAL;
+
+	timer_regs[0].osc_amp = slic_dtmf_table[dtmf_ch].osc1amp;
+	timer_regs[0].osc_freq = slic_dtmf_table[dtmf_ch].osc1freq;
+	timer_regs[0].o_talo = (u8)(dtmf_delay & 0xFF);
+	timer_regs[0].o_tahi = (u8)((dtmf_delay >> 8) & 0xFF);
+
+	timer_regs[1].osc_amp = slic_dtmf_table[dtmf_ch].osc2amp;
+	timer_regs[1].osc_freq = slic_dtmf_table[dtmf_ch].osc2freq;
+	timer_regs[1].o_talo = (u8)(dtmf_delay & 0xFF);
+	timer_regs[1].o_tahi = (u8)((dtmf_delay >> 8) & 0xFF);
+
+	for(i = 0; i < 2; i++) {
+		rc = slic_set_timer(line, i, timer_regs + i);
+		if (rc)
+			return rc;
+	}
+
+	/*  Sending tone to */
+	rc = slic_write_reg(line, SI_REG_OMODE,
+	                    SI_VAL_ROUTING_1_3 | SI_VAL_ZERO_EN_1 |
+	                    SI_VAL_ROUTING_2_3 | SI_VAL_ZERO_EN_2);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_OCON,
+	                    SI_VAL_OSC1_EN | SI_VAL_OSC1_TA_EN |
+	                    SI_VAL_OSC2_EN | SI_VAL_OSC2_TA_EN);
+	if (rc)
+		return rc;
+
+	/*  sleeping by DTMF is signalling */
+	msleep(DTMF_DIGIT_PERIOD);
+
+	timeout = jiffies + ACCESS_TIMEOUT;
+	do {
+		rc = slic_read_reg(line, SI_REG_OCON, &data);
+		if (rc)
+			return rc;
+
+		data &= (SI_VAL_EN_SYNC_1 | SI_VAL_EN_SYNC_2);
+		if(time_after(jiffies, timeout))
+			return -EBUSY;
+	} while(data);
+
+	return 0;
+}
+
+
+/**
+ * Mask irq for slic line
+ * @param line - line descriptor
+ * @return 0 - ok
+ */
+static int slic_mask_irq(struct si3226x_line *line)
+{
+	int rc;
+	u8 data;
+	int i;
+
+	/*  Cleanup all irq flags */
+	for(i = SI_REG_IRQ0; i < SI_REG_IRQ4; i++) {
+		rc = slic_read_reg(line, i, &data);
+		if (rc)
+			return rc;
+	}
+
+	/*  FSK empty buffer */
+	rc = slic_write_reg(line, SI_REG_IRQEN1, SI_VAL_FSKBUF_AVAIL_IA);
+	if (rc)
+		return rc;
+
+	/*  RX,TX modem tone detector, DTMF, on/off hook */
+	rc = slic_write_reg(line, SI_REG_IRQEN2,
+	                    SI_VAL_TXMDM_IA | SI_VAL_RXMDM_IA | SI_VAL_DTMF_IA | SI_VAL_LCR_IA);
+	if (rc)
+		return rc;
+
+	/*  Termo alarm */
+	rc = slic_write_reg(line, SI_REG_IRQEN3, SI_VAL_P_TERM_IA);
+	if (rc)
+		return rc;
+
+	/*  disable unused irq */
+	rc = slic_write_reg(line, SI_REG_IRQEN4, 0);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+
+/**
+ * Upload patch for si3226x
+ * @param line - patch upload for line
+ * @return 0 - ok
+ */
+static int upload_patch(struct si3226x_line *line)
+{
+	int rc;
+	int i;
+
+	rc = slic_write_reg(line, SI_REG_JMPEN, 0);
+	if (rc)
+		return rc;
+
+	/*  load patch ram */
+	rc = slic_write_ram(line, SI_RAM_PRAM_ADDR, 0);
+	if (rc)
+		return rc;
+
+	for(i = 0; i < ARRAY_SIZE(si3226x_patch_data); i++) {
+		rc = slic_write_ram(line, SI_RAM_PRAM_DATA,
+		                    si3226x_patch_data[i]);
+		if (rc)
+			return rc;
+	}
+
+	/*  load jump table */
+	for(i = SI_REG_JMP0LO; i < SI_REG_JMP7HI; i++) {
+		rc = slic_write_reg(line, i,
+		                    si3226x_patch_entries[i - SI_REG_JMP0LO]);
+		if (rc)
+			return rc;
+	}
+
+	/*  load RAM */
+	for(i = 0; i < ARRAY_SIZE(si3226x_patch_support_addr); i++) {
+		rc = slic_write_ram(line, si3226x_patch_support_addr[i],
+		                    si3226x_patch_support_data[i]);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Setup audio companding mode
+ * @param line
+ * @param companding_mode
+ * @return 0 - ok
+ */
+int slic_set_companding_mode(struct si3226x_line *line, u8 companding_mode)
+{
+	u8 reg_val;
+	int rc = 0;
+	u8 bus_format = 0;
+
+	switch(companding_mode) {
+	case SI_ALAW:
+		bus_format = SI_VAL_PCM_FMT_0 | SI_VAL_PCM_ALAW_1;
+		break;
+
+	case SI_MLAW:
+		bus_format = SI_VAL_PCM_FMT_1;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	rc = slic_read_reg(line, SI_REG_PCMMODE, &reg_val);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_PCMMODE, reg_val | bus_format);
+	if (rc)
+		return rc;
+
+	line->companding_mode = companding_mode;
+	return rc;
+}
+
+
+/**
+ * Setup slic hardware audio settings
+ * @param line - line descriptor
+ * @return 0 - ok
+ */
+int slic_setup_audio(struct si3226x_line *line)
+{
+	struct tdm_device *tdm_dev = line->tdm_dev;
+	struct tdm_controller_hw_settings *tdm_controller_hw =
+			    tdm_dev->controller->settings;
+	u16 time_slot_pos =
+	    tdm_dev->tdm_channel_num * tdm_controller_hw->channel_size * 8;
+	u8 tx_edge;
+	int rc = 0;
+	u8 reg_val;
+
+	tx_edge = (tdm_controller_hw->data_polarity == TDM_POLAR_NEGATIVE) << 4;
+
+	rc = slic_write_reg(line, SI_REG_PCMTXLO, (u8)(time_slot_pos & 0xFF));
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_PCMTXHI,
+	                    (u8)((time_slot_pos >> 8) & 0x3) | tx_edge);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_PCMRXLO, (u8)(time_slot_pos & 0xFF));
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_PCMRXHI,
+	                    (u8)((time_slot_pos >> 8) & 0x3));
+	if (rc)
+		return rc;
+
+	rc = slic_set_companding_mode(line, line->companding_mode);
+	if (rc)
+		return rc;
+
+	rc = slic_read_reg(line, SI_REG_PCMMODE, &reg_val);
+	if (rc)
+		return rc;
+
+	rc = slic_write_reg(line, SI_REG_PCMMODE, reg_val | SI_VAL_PCM_EN);
+	if (rc)
+		return rc;
+
+	rc = tdm_run_audio(tdm_dev);
+	if (!rc)
+		dev_err(&line->tdm_dev->dev, "Can't run audio\n");
+
+	return rc;
+}
+
+
+/**
+ * Init hardware slic line
+ * @param line - line descriptor
+ * @return 0 - ok
+ */
+static int init_line(struct si3226x_line *line)
+{
+	int rc;
+
+	dev_info(&line->tdm_dev->dev, "run init line\n");
+
+	line->callerid_mode = SI_CALLERID_NONE;
+
+	INIT_WORK(&line->line_call_work, do_line_call);
+
+	rc = cb_init(&line->dtmf, 1, INCOMING_DTMF_BUF_SIZE);
+	if (rc)
+		return rc;
+
+	rc = cb_init(&line->fsk, 1, CALLERID_BUF_SIZE);
+	if (rc)
+		return rc;
+
+
+	rc = slic_setup_audio(line);
+	if (rc)
+		return rc;
+
+	rc = slic_set_line_state(line, SI_LINE_SILENCE);
+	if (rc)
+		return rc;
+
+	rc = slic_mask_irq(line);
+	if (rc)
+		return rc;
+
+	dev_dbg(&line->tdm_dev->dev, "init line done\n");
+	return 0;
+}
+
+
+/**
+ * Init hardware slic
+ * @param slic - slic descriptor
+ * @return 0 - ok
+ */
+int init_slic(struct si3226x_slic *slic)
+{
+	int rc;
+	int i;
+	struct si3226x_line *line = slic->lines;
+	struct platform_device *pdev = slic->pdev;
+	u8 chip_id;
+
+	dev_info(&pdev->dev, "run slic initialization\n");
+
+	rc = slic_reset(slic);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to reset SLIC\n");
+		return rc;
+	}
+
+	mdelay(50);
+
+	rc = slic_read_reg(line, 0, &chip_id);
+	if (rc)
+		return rc;
+
+	if (chip_id != 0xC3) {
+		dev_err(&pdev->dev, "Incorrect slic chip ID: 0x%X\n", chip_id);
+		return -ENODEV;
+	}
+
+
+
+	for (i = 0; i < SI3226X_MAX_CHANNELS; i++, line++) {
+		if (line->state == SI_LINE_DISABLE)
+			continue;
+
+		rc = slic_unlock_channel(line);
+		if (rc)
+			return rc;
+
+		rc = upload_patch(line);
+		if (rc)
+			return rc;
+
+		rc = slic_lock_channel(line);
+		if (rc)
+			return rc;
+
+		dev_info(&pdev->dev, "line: %d, patch uploaded\n", line->ch);
+	}
+
+	rc = slic_load_settings(slic);
+	if (rc)
+		return rc;
+
+	dev_info(&pdev->dev, "settings loaded\n");
+
+	for (i = 0, line = slic->lines; i < SI3226X_MAX_CHANNELS; i++, line++) {
+		if (line->state == SI_LINE_DISABLE)
+			continue;
+
+		rc = init_line(line);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+
+/**
+ * deInit hardware slic. stop slic.
+ * @param slic - slic descriptor
+ * @return 0 - ok
+ */
+int deinit_slic(struct si3226x_slic *slic)
+{
+	struct si3226x_line *line;
+	int i;
+
+	for (i = 0, line = slic->lines; i < SI3226X_MAX_CHANNELS; i++, line++) {
+		if (line->state == SI_LINE_DISABLE)
+			continue;
+
+		slic_set_line_state(line, SI_LINE_SILENCE);
+
+		cb_free(&line->dtmf);
+		cb_free(&line->fsk);
+	}
+
+	return 0;
+}
+
+
+
+/*
+ * IRQ event handlers - event_xxx...
+ */
+static void event_modem_detect(struct si3226x_line *line)
+{
+
+}
+
+static void event_dtmf_detect(struct si3226x_line *line)
+{
+	u8 ch;
+	u8 data = 0;
+
+	slic_read_reg(line, SI_REG_TONDTMF, &data);
+
+	/*  if dtmf not recognized */
+	if (!(data & SI_VAL_VALID))
+		return;
+
+	ch = conv_dtmf_to_char(data & 0xF);
+
+	cb_push(&line->dtmf, &ch);
+}
+
+static void event_hook_detect(struct si3226x_line *line)
+{
+	u8 data = 0;
+
+	slic_read_reg(line, SI_REG_LCRRTP, &data);
+	line->hook_state = !!(data & SI_VAL_LCR);
+}
+
+static void event_error_detect(struct si3226x_line *line, enum si3226x_errors err)
+{
+	struct si3226x_slic *slic = to_si3226x_slic(line);
+
+	switch(err) {
+	case SI_ERR_TERMAL_SHOCK:
+		dev_err(&slic->pdev->dev, "SLIC Termal shock! Stopped driver\n");
+		release_slic_drv(slic);
+		free_slic(slic);
+		break;
+	}
+}
+
+
+
+#ifdef CONFIG_SI3226X_POLLING
+void slic_delayed_work(struct delayed_work *work)
+{
+	struct si3226x_slic *slic =
+	    container_of(work, struct si3226x_slic, delayed_work);
+
+	slic_irq_callback(&slic->irq_work);
+
+	schedule_delayed_work(work, MSEC(50));
+}
+#endif
+
+/**
+ * Interrupt handler for slic
+ * @param irq - irq number
+ * @param context_data - slic private data
+ */
+irqreturn_t slic_irq(s32 irq, void *context_data)
+{
+	struct si3226x_slic *slic = context_data;
+	int value = gpio_get_value(slic->int_gpio);
+
+	if (value == 0)
+		schedule_work(&slic->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+
+/**
+ * Work queue IRQ callback handled all slic events
+ * @param work - work queue item
+ */
+void slic_irq_callback(struct work_struct *work)
+{
+	struct si3226x_slic *slic =
+	    container_of(work, struct si3226x_slic, irq_work);
+
+	struct si3226x_line *line = 0;
+	u8 global_irq_status;
+	u8 data;
+	int rc;
+	int i;
+
+	rc = slic_read_reg(slic->lines, SI_REG_IRQ0, &global_irq_status);
+	if (rc)
+		return;
+
+	/*  identificate irq reason */
+	for (i = 0; i < 8; i++) {
+		if (!(global_irq_status & (1 << i)))
+			continue;
+
+		switch (i) {
+		case 0:
+			line = slic->lines + 0;
+			rc = slic_read_reg(line, SI_REG_IRQ1, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_FSKBUF_AVAIL_IA)
+				do_fsk(line);
+			break;
+
+		case 1:
+			line = slic->lines + 0;
+			rc = slic_read_reg(line, SI_REG_IRQ2, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_RXMDM_IA)
+				event_modem_detect(line);
+
+			if (data & SI_VAL_TXMDM_IA)
+				event_modem_detect(line);
+
+			if (data & SI_VAL_DTMF_IA)
+				event_dtmf_detect(line);
+
+			if (data & SI_VAL_LCR_IA)
+				event_hook_detect(line);
+			break;
+
+		case 2:
+			line = slic->lines + 0;
+			rc = slic_read_reg(line, SI_REG_IRQ3, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_P_TERM_IA)
+				event_error_detect(line, SI_ERR_TERMAL_SHOCK);
+			break;
+
+		case 3:
+			line = slic->lines + 0;
+			rc = slic_read_reg(line, SI_REG_IRQ4, &data);
+			if (rc)
+				return;
+
+			break;
+
+		case 4:
+			line = slic->lines + 1;
+			rc = slic_read_reg(line, SI_REG_IRQ1, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_FSKBUF_AVAIL_IA)
+				do_fsk(line);
+			break;
+
+		case 5:
+			line = slic->lines + 1;
+			rc = slic_read_reg(line, SI_REG_IRQ2, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_RXMDM_IA)
+				event_modem_detect(line);
+
+			if (data & SI_VAL_TXMDM_IA)
+				event_modem_detect(line);
+
+			if (data & SI_VAL_DTMF_IA)
+				event_dtmf_detect(line);
+
+			if (data & SI_VAL_LCR_IA)
+				event_hook_detect(line);
+			break;
+
+		case 6:
+			line = slic->lines + 1;
+			rc = slic_read_reg(line, SI_REG_IRQ3, &data);
+			if (rc)
+				return;
+
+			if (data & SI_VAL_P_TERM_IA)
+				event_error_detect(line, 0);
+			break;
+
+		case 7:
+			line = slic->lines + 1;
+			rc = slic_read_reg(line, SI_REG_IRQ4, &data);
+			if (rc)
+				return;
+
+			break;
+		}
+	}
+}
+
+
diff --git a/drivers/staging/si3226x/si3226x_hw.h b/drivers/staging/si3226x/si3226x_hw.h
new file mode 100644
index 0000000..9900591
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_hw.h
@@ -0,0 +1,219 @@
+/*
+ * si3226x_hw.h
+ *
+ *  Created on: 14.03.2012
+ *      Author: Michail Kurochkin
+ */
+
+#ifndef SI3226X_HW_H_
+#define SI3226X_HW_H_
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include "si3226x_drv.h"
+
+/* define slic ram registers */
+#define SI_RAM_ADDR_HI 5
+#define SI_RAM_DATA_B0 6
+#define SI_RAM_DATA_B1 7
+#define SI_RAM_DATA_B2 8
+#define SI_RAM_DATA_B3 9
+#define SI_RAM_ADDR_LO 10
+
+#define SI_RAM_OSC1FREQ 26
+#define SI_RAM_OSC1AMP 27
+#define SI_RAM_OSC1PHAS 28
+#define SI_RAM_OSC2FREQ 29
+#define SI_RAM_OSC2AMP 30
+#define SI_RAM_OSC2PHAS 31
+
+#define SI_RAM_FSKAMP0 836
+#define SI_RAM_FSKAMP1 837
+#define SI_RAM_FSKFREQ0 834
+#define SI_RAM_FSKFREQ1 835
+#define SI_RAM_FSK01 838
+#define SI_RAM_FSK10 839
+
+#define SI_RAM_PRAM_ADDR 1358
+#define SI_RAM_PRAM_DATA 1359
+
+#define SI_RAM_PD_DCDC 1538
+
+/* define slic ram values */
+#define SI_RAM_VAL_REG (u32)(1 << 23)
+#define SI_RAM_VAL_OFF (u32)(1 << 20)
+
+/* define slic registers */
+#define SI_REG_MSTRSTAT 3
+#define SI_REG_RAMSTAT 4
+
+#define SI_REG_PCMMODE 11
+#define SI_REG_PCMTXLO 12
+#define SI_REG_PCMTXHI 13
+#define SI_REG_PCMRXLO 14
+#define SI_REG_PCMRXHI 15
+
+#define SI_REG_IRQ0 17
+#define SI_REG_IRQ1 18
+#define SI_REG_IRQ2 19
+#define SI_REG_IRQ3 20
+#define SI_REG_IRQ4 21
+
+#define SI_REG_IRQEN1 22
+#define SI_REG_IRQEN2 23
+#define SI_REG_IRQEN3 24
+#define SI_REG_IRQEN4 25
+
+#define SI_REG_CALR3 29
+
+#define SI_REG_LINEFEED 30
+
+#define SI_REG_LCRRTP 34
+
+#define SI_REG_DIGCON 44
+#define SI_REG_OMODE 48
+#define SI_REG_OCON 49
+#define SI_REG_TONEN 62
+
+#define SI_REG_O1TALO 50
+#define SI_REG_O1TAHI 51
+#define SI_REG_O1TILO 52
+#define SI_REG_O1TIHI 53
+#define SI_REG_O2TALO 54
+#define SI_REG_O2TAHI 55
+#define SI_REG_O2TILO 56
+#define SI_REG_O2TIHI 57
+
+#define SI_REG_FSKDAT 58
+#define SI_REG_FSKDEPTH 59
+#define SI_REG_TONDTMF 60
+
+#define SI_REG_JMPEN 81
+#define SI_REG_JMP0LO 82
+#define SI_REG_JMP7HI 97
+#define SI_REG_UAM 126
+
+
+/* define slic register values */
+#define SI_VAL_FSKBUF_AVAIL_IA ((u8)0x40)
+#define SI_VAL_RXMDM_IA ((u8)0x80)
+#define SI_VAL_TXMDM_IA ((u8)0x40)
+#define SI_VAL_DTMF_IA ((u8)0x10)
+#define SI_VAL_LCR_IA ((u8)0x2)
+#define SI_VAL_P_TERM_IA ((u8)0x2)
+#define SI_VAL_HYB_DIS ((u8)0x10)
+#define SI_VAL_CAL_EN ((u8)0x80)
+#define SI_VAL_ROUTING_1_3 ((u8)0x3)
+#define SI_VAL_ROUTING_1_2 ((u8)0x2)
+#define SI_VAL_ROUTING_2_3 ((u8)0x30)
+#define SI_VAL_OSC1_TA_EN ((u8)0x4)
+#define SI_VAL_OSC1_TI_EN ((u8)0x2)
+#define SI_VAL_OSC1_EN ((u8)0x1)
+#define SI_VAL_OSC2_TA_EN ((u8)0x40)
+#define SI_VAL_OSC2_TI_EN ((u8)0x20)
+#define SI_VAL_OSC2_EN ((u8)0x10)
+
+#define SI_VAL_VALID ((u8)0x20)
+
+#define SI_VAL_ZERO_EN_1 ((u8)0x4)
+#define SI_VAL_ZERO_EN_2 ((u8)0x40)
+
+#define SI_VAL_OSC1_FSK ((u8)0x8)
+
+#define SI_VAL_FSK_FLUSH ((u8)0x8)
+#define SI_VAL_FSKBUF_DEPTH_7 ((u8)0x7)
+
+#define SI_VAL_PCM_EN ((u8)0x10)
+#define SI_VAL_PCM_FMT_0 ((u8)0x0)
+#define SI_VAL_PCM_FMT_1 ((u8)0x1)
+
+#define SI_VAL_PCM_ALAW_0 ((u8)0x0)
+#define SI_VAL_PCM_ALAW_1 ((u8)0x4)
+#define SI_VAL_PCM_ALAW_2 ((u8)0x8)
+#define SI_VAL_PCM_ALAW_3 ((u8)0xC)
+
+#define SI_VAL_LCR ((u8)0x2)
+
+#define SI_VAL_EN_SYNC_1 ((u8)0x8)
+#define SI_VAL_EN_SYNC_2 ((u8)0x80)
+
+/*
+ * si3226x hardware line states
+ */
+enum fxs_states {
+        SI_FXS_OPEN,
+        SI_FXS_FORWARD_ACTIVE,
+        SI_FXS_FORWARD_OHT,
+        SI_FXS_TIP_OPEN,
+        SI_FXS_RINGING,
+        SI_FXS_REVERSE_ACTIVE,
+        SI_FXS_REVERSE_OHT,
+        SI_FXS_RING_OPEN,
+};
+
+/*
+ * registers content for generate one DTMF digit
+ */
+struct si3226x_dtmf_digit {
+        u32 osc1amp;
+        u32 osc2amp;
+        u32 osc1freq;
+        u32 osc2freq;
+};
+
+/*
+ * ATS answer tone register values
+ */
+struct si3226x_timer_regs {
+        u32 osc_amp;
+        u32 osc_freq;
+        u8 o_talo;
+        u8 o_tahi;
+        u8 o_tilo;
+        u8 o_tihi;
+        u8 o_con;
+};
+
+/*
+ * ATS answer tone types
+ */
+enum tone_types {
+        SI_TONE_INVITATION,
+        SI_TONE_BUSY,
+        SI_TONE_WAIT,
+        SI_TONE_NONE,
+};
+
+
+/*
+ * si3226x hardware errors
+ */
+enum si3226x_errors {
+        SI_ERR_TERMAL_SHOCK,
+};
+
+
+int init_slic(struct si3226x_slic *slic);
+int deinit_slic(struct si3226x_slic *slic);
+int slic_setup_audio(struct si3226x_line *line);
+int slic_set_companding_mode(struct si3226x_line *line, u8 companding_mode);
+int slic_set_line_state(struct si3226x_line *line, int state);
+int slic_line_call(struct si3226x_line *line, u8 *callerid_buf, int size);
+int slic_get_dtmf_data(struct si3226x_line *line, char *data);
+int slic_send_dtmf_digit(struct si3226x_line *line, char ch);
+int slic_calibrate(struct si3226x_line *line);
+int slic_enable_echo(struct si3226x_line *line);
+int slic_disable_echo(struct si3226x_line *line);
+irqreturn_t slic_irq(s32 irq, void *context_data);
+int slic_unlock_channel(struct si3226x_line *line);
+int slic_lock_channel(struct si3226x_line *line);
+int slic_write_ram(struct si3226x_line *line, u16 addr, u32 value);
+int slic_write_reg(struct si3226x_line *line, u8 addr, u8 data);
+void slic_irq_callback(struct work_struct *work);
+
+#ifdef CONFIG_SI3226X_POLLING
+void slic_delayed_work(struct delayed_work *work);
+#endif
+
+#endif /* SI3226X_HW_H_ */
diff --git a/drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c b/drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c
new file mode 100644
index 0000000..998bc8a
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_patch_C_FB_2011MAY19.c
@@ -0,0 +1,176 @@
+/*
+** Generated from si3226x_patch_C_FB_2011MAY19.dsp_prom
+** Based on design file si32260C_firmware_1_0_0_4_20110517
+** on 05-19-2011 at 14:58:49
+** Patch ID = 0x1004C000
+*/
+
+const u32 si3226x_patch_data[] =
+{
+	141541L,
+	540867L,
+	141541L,
+	543427L,
+	141541L,
+	553155L,
+	141541L,
+	577731L,
+	141541L,
+	579779L,
+	141541L,
+	581315L,
+	141541L,
+	581827L,
+	141541L,
+	582339L,
+	141541L,
+	582851L,
+	141541L,
+	583363L,
+	141541L,
+	583875L,
+	141541L,
+	584387L,
+	141541L,
+	584899L,
+	141541L,
+	585411L,
+	141541L,
+	585923L,
+	141541L,
+	586435L,
+	736L,
+	491712L,
+	452200L,
+	141541L,
+	491200L,
+	5733L,
+	524290L,
+	142565L,
+	550083L,
+	3685L,
+	519266L,
+	5220L,
+	144098L,
+	550083L,
+	3685L,
+	524291L,
+	141541L,
+	551619L,
+	5221L,
+	3682L,
+	524292L,
+	5L,
+	141541L,
+	135362L,
+	98021L,
+	727745L,
+	474213L,
+	17637L,
+	557251L,
+	101093L,
+	557251L,
+	473701L,
+	515653L,
+	843365L,
+	188002L,
+	843355L,
+	145125L,
+	560835L,
+	524290L,
+	660069L,
+	518053L,
+	517224L,
+	518244L,
+	142565L,
+	564419L,
+	524288L,
+	521733L,
+	843365L,
+	188002L,
+	524315L,
+	145125L,
+	568003L,
+	843365L,
+	522850L,
+	523387L,
+	147685L,
+	573123L,
+	522363L,
+	145125L,
+	575171L,
+	521826L,
+	141541L,
+	575683L,
+	518757L,
+	521826L,
+	141541L,
+	575683L,
+	521824L,
+	522245L,
+	522338L,
+	141541L,
+	716481L,
+	173669L,
+	523845L,
+	141541L,
+	730304L,
+	523877L,
+	141541L,
+	690368L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	524293L,
+	0L
+};
+
+const u16 si3226x_patch_entries[] =
+{
+	950,
+	4347,
+	3431,
+	1425,
+	1347,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0,
+	0
+};
+
+const u16 si3226x_patch_support_addr[] =
+{
+	925,
+	926,
+	1014,
+	1020,
+	1021,
+	1022,
+	0
+};
+
+const u32 si3226x_patch_support_data[] =
+{
+	0xA00000L,
+	0x1F00000L,
+	0x2D8000L,
+	0x0L,
+	0x1A9FBDAL,
+	0x1C28F4EL,
+	0x0L
+};
+
diff --git a/drivers/staging/si3226x/si3226x_setup.c b/drivers/staging/si3226x/si3226x_setup.c
new file mode 100644
index 0000000..a8a706b
--- /dev/null
+++ b/drivers/staging/si3226x/si3226x_setup.c
@@ -0,0 +1,1413 @@
+
+int slic_load_settings(struct si3226x_slic *slic)
+{
+	int rc = 0;
+	struct si3226x_line *lines = slic->lines;
+	
+	rc = slic_unlock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 47, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 80, 0x2F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 764, 0x0020C480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 768, 0x051EB82A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 767, 0x03D70A20);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 915, 0x0FFF0000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 916, 0x01999A00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 919, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 920, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 970, 0x00800000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1004, 0x00F18900);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1005, 0x00809D80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1006, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1007, 0x01C00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1540, 0x00400000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1541, 0x00400000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1542, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1545, 0x00500000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1546, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1547, 0x00500000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1553, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1554, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1558, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1560, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1585, 0x00300000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1586, 0x00300000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1587, 0x00100000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1588, 0x00FFC000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1589, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1590, 0x0FDA4000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 759, 0x07FEB800);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 756, 0x005B05B2);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 73, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 967, 0x03A2E8BA);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1018, 0x03000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1017, 0x05000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1013, 0x01000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1012, 0x03700000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1011, 0x04B80200);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1010, 0x00823000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 68, 0x60);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 98, 0x80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 533, 0x71EB851);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 626, 0x723F235);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 627, 0x57A9804);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 918, 0x0036000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1616, 0x1100000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 973, 0xFFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 975, 0xE49BA5);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 516, 0x10038D);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 513, 0x4EDDB9);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 514, 0x0806D6);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1641, 0x200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1643, 0x000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1565, 0xC00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 750, 0x206280);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 971, 0x1F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 972, 0x51EB80);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 47, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 80, 0x2F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 764, 0x0020C480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 768, 0x051EB82A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 767, 0x03D70A20);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 915, 0x0FFF0000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 916, 0x01999A00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 919, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 920, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 970, 0x00800000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1004, 0x00F18900);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1005, 0x00809D80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1006, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1007, 0x01C00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1540, 0x00400000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1541, 0x00400000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1542, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1545, 0x00500000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1546, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1547, 0x00500000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1553, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1554, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1558, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1560, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1585, 0x00300000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1586, 0x00300000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1587, 0x00100000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1588, 0x00FFC000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1589, 0x00F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1590, 0x0FDA4000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 759, 0x07FEB800);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 756, 0x005B05B2);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 73, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 967, 0x03A2E8BA);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1018, 0x03000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1017, 0x05000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1013, 0x01000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1012, 0x03700000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1011, 0x04B80200);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1010, 0x00823000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 68, 0x60);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 98, 0x80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 533, 0x71EB851);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 626, 0x723F235);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 627, 0x57A9804);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 918, 0x0036000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1616, 0x1100000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 973, 0xFFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 975, 0xE49BA5);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 516, 0x10038D);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 513, 0x4EDDB9);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 514, 0x0806D6);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1641, 0x200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1643, 0x000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1565, 0xC00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 750, 0x206280);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 971, 0x1F00000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 972, 0x51EB80);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 27, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 28, 0x01);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 27, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 28, 0x01);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1548, 0x00800000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 30, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 47, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1538, 0x700000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1555, 0x100000);
+	if (rc)
+		return rc;
+	
+    msleep(100);
+    
+	rc = slic_write_ram(lines + 0, 1538, 0x600000);
+	if (rc)
+		return rc;
+	
+    msleep(500);
+    
+	rc = slic_write_ram(lines + 0, 1551, 0x000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1538, 0x400000);
+	if (rc)
+		return rc;
+	
+    msleep(500);
+    
+	rc = slic_lock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1548, 0x00800000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 30, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 47, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1538, 0x700000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1555, 0x100000);
+	if (rc)
+		return rc;
+	
+    msleep(100);
+    
+	rc = slic_write_ram(lines + 1, 1538, 0x600000);
+	if (rc)
+		return rc;
+	
+    msleep(500);
+    
+	rc = slic_write_ram(lines + 1, 1551, 0x000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1538, 0x400000);
+	if (rc)
+		return rc;
+	
+    msleep(500);
+    
+	rc = slic_lock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 27, 0xC0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 28, 0x18);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 27, 0xC0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 28, 0x18);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 47, 0x10);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 80, 0x3F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 46, 0x04);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 47, 0x10);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 80, 0x3F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 46, 0x04);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 540, 0x07F97D80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 541, 0x0006CC00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 542, 0x1FFC1480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 543, 0x1FFC7B80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 546, 0x07F36B80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 547, 0x000A8E00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 548, 0x1FF90F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 549, 0x1FFAE500);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 563, 0x001AF400);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 564, 0x1FC86A80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 565, 0x01E9AE00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 566, 0x00652F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 567, 0x01F4AF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 568, 0x1F57E000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 569, 0x00485E00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 570, 0x1FF3A680);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 571, 0x1FF83700);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 572, 0x00011D00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 573, 0x01706980);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 574, 0x066A8480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 653, 0x00920F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 654, 0x1EE31980);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 655, 0x008ADF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 656, 0x0F92E500);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 657, 0x186CE880);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 45, 0x53);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 544, 0x085C6880);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 906, 0x013E3100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 545, 0x013E3100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 658, 0x07AF6F80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 659, 0x18509100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 660, 0x075EDF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 27, 0x40);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 28, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 540, 0x07F97D80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 541, 0x0006CC00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 542, 0x1FFC1480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 543, 0x1FFC7B80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 546, 0x07F36B80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 547, 0x000A8E00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 548, 0x1FF90F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 549, 0x1FFAE500);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 563, 0x001AF400);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 564, 0x1FC86A80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 565, 0x01E9AE00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 566, 0x00652F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 567, 0x01F4AF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 568, 0x1F57E000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 569, 0x00485E00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 570, 0x1FF3A680);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 571, 0x1FF83700);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 572, 0x00011D00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 573, 0x01706980);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 574, 0x066A8480);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 653, 0x00920F00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 654, 0x1EE31980);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 655, 0x008ADF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 656, 0x0F92E500);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 657, 0x186CE880);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 45, 0x53);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 544, 0x085C6880);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 906, 0x013E3100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 545, 0x013E3100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 658, 0x07AF6F80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 659, 0x18509100);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 660, 0x075EDF00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 26, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 27, 0x40);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 28, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_calibrate(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 755, 0x00050000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 844, 0x07EFE000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 845, 0x001B9F2E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 846, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 843, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 637, 0x15E5200E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 860, 0x00D16348);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 848, 0x0068E9B4);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 847, 0x0FFFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 850, 0x00006000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 849, 0x00006000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 753, 0x00C49BA0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 896, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 768, 0x051EB82A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 39, 0x80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 40, 0x3E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 41, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 42, 0x7D);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 920, 0x01893740);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 38, 0x98);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 66, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 749, 0x02AC55FE);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 482, 0x02AC55FE);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 483, 0x003126E8);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 972, 0x00FFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 974, 0x0083126A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 975, 0x009374B8);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 1560, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 755, 0x00050000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 844, 0x07EFE000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 845, 0x001B9F2E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 846, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 843, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 637, 0x15E5200E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 860, 0x00D16348);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 848, 0x0068E9B4);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 847, 0x0FFFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 850, 0x00006000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 849, 0x00006000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 753, 0x00C49BA0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 896, 0x00000000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 768, 0x051EB82A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 39, 0x80);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 40, 0x3E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 41, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 42, 0x7D);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 920, 0x01893740);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 38, 0x98);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 66, 0x00);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 749, 0x02AC55FE);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 482, 0x02AC55FE);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 483, 0x003126E8);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 972, 0x00FFFFFF);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 974, 0x0083126A);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 975, 0x009374B8);
+	if (rc)
+		return rc;
+	
+	rc = slic_unlock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 1560, 0x00200000);
+	if (rc)
+		return rc;
+	
+	rc = slic_lock_channel(lines + 1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 634, 0x1C8A024C);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 635, 0x1F909679);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 636, 0x0040A0E0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 638, 0x1D5B21A9);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 639, 0x1DD87A3E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 640, 0x05A38633);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 641, 0x050D2839);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 642, 0x03FE7F0F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 643, 0x00B4F3C3);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 644, 0x005D0FA6);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 645, 0x002D8D96);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 853, 0x005B0AFB);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 852, 0x006D4060);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 701, 0x00008000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 858, 0x0048D595);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 859, 0x003FBAE2);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 702, 0x00008000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 854, 0x000F0000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 855, 0x00080000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 856, 0x00140000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 857, 0x00140000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 748, 0x01BA5E35);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 752, 0x0051EB85);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 0, 751, 0x00418937);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 634, 0x1C8A024C);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 635, 0x1F909679);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 636, 0x0040A0E0);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 638, 0x1D5B21A9);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 639, 0x1DD87A3E);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 640, 0x05A38633);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 641, 0x050D2839);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 642, 0x03FE7F0F);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 643, 0x00B4F3C3);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 644, 0x005D0FA6);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 645, 0x002D8D96);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 853, 0x005B0AFB);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 852, 0x006D4060);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 701, 0x00008000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 858, 0x0048D595);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 859, 0x003FBAE2);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 702, 0x00008000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 854, 0x000F0000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 855, 0x00080000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 856, 0x00140000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 857, 0x00140000);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 748, 0x01BA5E35);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 752, 0x0051EB85);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_ram(lines + 1, 751, 0x00418937);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 0, 30, 0x1);
+	if (rc)
+		return rc;
+	
+	rc = slic_write_reg(lines + 1, 30, 0x1);
+	if (rc)
+		return rc;
+	
+	return rc;
+}
+
diff --git a/drivers/staging/si3226x/slic_dtmf_table.c b/drivers/staging/si3226x/slic_dtmf_table.c
new file mode 100644
index 0000000..e0923db
--- /dev/null
+++ b/drivers/staging/si3226x/slic_dtmf_table.c
@@ -0,0 +1,127 @@
+
+/*
+ * dtmf table for convert DTMF - ASCII
+ */
+char dtmf_codes[] = {"D1234567890*#ABC"};
+
+
+/*
+ * si3226x DTMF tone registers configure per digit
+ */
+struct si3226x_dtmf_digit slic_dtmf_table[] =
+{
+	
+    {  // D
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0xb2c000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x5e9c000,
+    },
+	
+    {  // 1
+        .osc1amp = 0xed2000,
+        .osc2amp = 0x818000,
+        .osc1freq = 0x4a82000,
+        .osc2freq = 0x6d4c000,
+    },
+	
+    {  // 2
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0x818000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x6d4c000,
+    },
+	
+    {  // 3
+        .osc1amp = 0x12e4000,
+        .osc2amp = 0x818000,
+        .osc1freq = 0x331e000,
+        .osc2freq = 0x6d4c000,
+    },
+	
+    {  // 4
+        .osc1amp = 0xed2000,
+        .osc2amp = 0x900000,
+        .osc1freq = 0x4a82000,
+        .osc2freq = 0x694e000,
+    },
+	
+    {  // 5
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0x900000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x694e000,
+    },
+	
+    {  // 6
+        .osc1amp = 0x12e4000,
+        .osc2amp = 0x900000,
+        .osc1freq = 0x331e000,
+        .osc2freq = 0x694e000,
+    },
+	
+    {  // 7
+        .osc1amp = 0xed2000,
+        .osc2amp = 0xa06000,
+        .osc1freq = 0x4a82000,
+        .osc2freq = 0x6466000,
+    },
+	
+    {  // 8
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0xa06000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x6466000,
+    },
+	
+    {  // 9
+        .osc1amp = 0x12e4000,
+        .osc2amp = 0xa06000,
+        .osc1freq = 0x331e000,
+        .osc2freq = 0x6466000,
+    },
+	
+    {  // 0
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0xb2c000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x5e9c000,
+    },
+	
+    {  // *
+        .osc1amp = 0xed2000,
+        .osc2amp = 0xb2c000,
+        .osc1freq = 0x4a82000,
+        .osc2freq = 0x5e9c000,
+    },
+	
+    {  // #
+        .osc1amp = 0x10b0000,
+        .osc2amp = 0xb2c000,
+        .osc1freq = 0x3fc6000,
+        .osc2freq = 0x5e9c000,
+    },
+	
+    {  // A
+        .osc1amp = 0x1586000,
+        .osc2amp = 0x818000,
+        .osc1freq = 0x2464000,
+        .osc2freq = 0x6d4c000,
+    },
+	
+    {  // B
+        .osc1amp = 0x1586000,
+        .osc2amp = 0x900000,
+        .osc1freq = 0x2464000,
+        .osc2freq = 0x694e000,
+    },
+	
+    {  // C
+        .osc1amp = 0x1586000,
+        .osc2amp = 0xa06000,
+        .osc1freq = 0x2464000,
+        .osc2freq = 0x6466000,
+    },
+	
+};
+
diff --git a/drivers/staging/si3226x/slic_si3226x.h b/drivers/staging/si3226x/slic_si3226x.h
new file mode 100644
index 0000000..e046140
--- /dev/null
+++ b/drivers/staging/si3226x/slic_si3226x.h
@@ -0,0 +1,75 @@
+/*
+ * si3226x.h
+ *
+ *  Created on: 02.03.2012
+ *      Author: Michail Kurochkin
+ */
+
+#ifndef SLIC_SI3226X_H_
+#define SLIC_SI3226X_H_
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#define SI3226X_MAX_CHANNELS 2 // Count channels
+
+#define SI3226X_IOC_MAGIC 0xde
+
+// SLIC control methods
+#define SI3226X_SET_COMPANDING_MODE  _IO(SI3226X_IOC_MAGIC, 0)
+#define SI3226X_SET_CALLERID_MODE		_IO(SI3226X_IOC_MAGIC, 1)
+
+// Line control methods
+#define SI3226X_SET_ECHO_CANCELATION _IO(SI3226X_IOC_MAGIC, 10)
+#define SI3226X_SET_LINE_STATE 		_IO(SI3226X_IOC_MAGIC, 11)
+#define SI3226X_CALL					 		_IO(SI3226X_IOC_MAGIC, 12)
+#define SI3226X_SEND_DTMF		 		_IO(SI3226X_IOC_MAGIC, 13)
+#define SI3226X_GET_HOOK_STATE 		_IO(SI3226X_IOC_MAGIC, 14)
+#define SI3226X_GET_DTMF_DIGIT 		_IO(SI3226X_IOC_MAGIC, 15)
+#define SI3226X_GET_AUDIO_BLOCK_SIZE _IO(SI3226X_IOC_MAGIC, 16)
+#define SI3226X_ENABLE_AUDIO 			_IO(SI3226X_IOC_MAGIC, 17)
+#define SI3226X_DISABLE_AUDIO 		_IO(SI3226X_IOC_MAGIC, 18)
+
+/*
+ * conteiner for transfer caller id data over ioctl
+ */
+struct si3226x_caller_id {
+	u8 *data;
+	int size;
+};
+
+
+/*
+ * audio compaunding modes
+ */
+enum companding_modes {
+	SI_ALAW,
+	SI_MLAW,
+};
+
+
+/*
+ * Board specific data for setup driver SLIC Silabs si3226x
+ */
+struct si3226x_platform_data {
+	int reset_gpio; // number GPIO for reset SLIC
+	int int_gpio; // number GPIO for interrupt SLIC
+
+	/*
+	 *  fxs_tdm_ch array FXS channels.
+	 *  one item is a number of tdm channel number for
+	 *  corresponding FXS channel.
+	 *  if fxs_tdm_ch[x] == -1 then disable this FXS port
+	 */
+	int fxs_tdm_ch[SI3226X_MAX_CHANNELS];
+
+	/**
+	 * spi chip select requested for si3226x
+	 */
+	u16 spi_chip_select;
+
+	enum companding_modes companding_mode;
+};
+
+#endif /* SLIC_SI3226X_H_ */
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 04/11] staging: added TODO file for si3226x
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (2 preceding siblings ...)
  2013-03-01 10:54   ` [PATCH v2 03/11] staging: Initial commit of SLIC si3226x driver Michail Kurachkin
@ 2013-03-01 10:56   ` Michail Kurachkin
  2013-03-01 10:57   ` [PATCH v2 05/11] staging: refactoring request/free voice channels Michail Kurachkin
                     ` (6 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:56 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurochkin

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

Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
---
 drivers/staging/si3226x/TODO |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/si3226x/TODO

diff --git a/drivers/staging/si3226x/TODO b/drivers/staging/si3226x/TODO
new file mode 100644
index 0000000..83c5958
--- /dev/null
+++ b/drivers/staging/si3226x/TODO
@@ -0,0 +1,2 @@
+1) mutex_lock(&slic_chr_dev_lock);
+This locking is very heavy handed. You are holding it across the entire open, close, read, write, ioctl, and it is protecting a bunch of different things.
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 05/11] staging: refactoring request/free voice channels
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (3 preceding siblings ...)
  2013-03-01 10:56   ` [PATCH v2 04/11] staging: added TODO file for si3226x Michail Kurachkin
@ 2013-03-01 10:57   ` Michail Kurachkin
  2013-03-02 22:56     ` Ryan Mallon
  2013-03-01 10:58   ` [PATCH v2 06/11] staging: remove device_attribute Michail Kurachkin
                     ` (5 subsequent siblings)
  10 siblings, 1 reply; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:57 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurochkin

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

Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/kirkwood_tdm.c |  162 +++++++++++++++++++++++++-----------
 drivers/staging/tdm/kirkwood_tdm.h |   10 ++-
 drivers/staging/tdm/tdm.h          |    5 +-
 drivers/staging/tdm/tdm_core.c     |   65 +++++++--------
 4 files changed, 154 insertions(+), 88 deletions(-)

diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
index 4366d38..e4a9106 100644
--- a/drivers/staging/tdm/kirkwood_tdm.c
+++ b/drivers/staging/tdm/kirkwood_tdm.c
@@ -75,7 +75,7 @@ static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
 	u32 control_val = 0;
 	u32 pclk_freq;
 	unsigned long flags;
-	spinlock_t lock;
+	static spinlock_t lock;
 	spin_lock_init(&lock);
 
 
@@ -192,13 +192,14 @@ static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
 }
 
 /**
- * Setup hardware voice channel
+ * Initialization hardware voice channel
+ * for specify TDM device
+ * @param tdm_dev - specify TDM device
  * @param ch - voice hardware channel
  * @return 0 - OK
  */
-int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
+static int kirkwood_init_voice_channel(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch)
 {
-	struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
 	struct tdm_controller *tdm = tdm_dev->controller;
 	struct tdm_controller_hw_settings *hw = tdm->settings;
 	struct kirkwood_tdm *onchip_tdm =
@@ -208,15 +209,15 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 	unsigned long timeout;
 	u32 state;
 	int i;
-
+	int rc = 0;
 	u8 voice_num = ch->channel_num;
 
 	/* calculate buffer size */
 	ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
 
 	/* Request timeslot for voice channel */
-	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
-	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
+	writeb(tdm_dev->tdm_channel_num, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
+	writeb(tdm_dev->tdm_channel_num, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
 
 	/* FIXME: move coherent_dma_mask to board specific */
 	tdm_dev->dev.coherent_dma_mask = 0xffffffff;
@@ -226,11 +227,12 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 		/* allocate memory for DMA receiver */
 		onchip_ch->rx_buf[i] =
 		        dma_alloc_coherent(&tdm_dev->dev,
-		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
+		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_KERNEL);
 
 		if (onchip_ch->rx_buf == NULL) {
 			dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
-			return -ENOMEM;
+			rc = -ENOMEM;
+			goto out1;
 		}
 		memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
 
@@ -238,20 +240,16 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 		/* allocate memory for DMA transmitter */
 		onchip_ch->tx_buf[i] =
 		        dma_alloc_coherent(&tdm_dev->dev,
-		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
+		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_KERNEL);
 
 		if (onchip_ch->tx_buf == NULL) {
 			dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
-			return -ENOMEM;
+			rc = -ENOMEM;
+			goto out1;
 		}
 		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
 	}
 
-	atomic_set(&onchip_ch->write_rx_buf_num, 0);
-	atomic_set(&onchip_ch->write_tx_buf_num, 0);
-	atomic_set(&onchip_ch->read_rx_buf_num, 0);
-	atomic_set(&onchip_ch->read_tx_buf_num, 0);
-
 	/* Set length for DMA */
 	writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
 	       &regs->chan0_total_sample + voice_num);
@@ -262,8 +260,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100;
 		if(time_after(jiffies, timeout)) {
 			dev_err(ch->dev, "Can`t program DMA tx buffer\n");
-			return -EBUSY;
+			rc = -EBUSY;
+			goto out1;
 		}
+		schedule();
 	} while(state);
 
 	/* Set DMA buffers fo transmitter */
@@ -277,8 +277,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1;
 		if(time_after(jiffies, timeout)) {
 			dev_err(ch->dev, "Can`t program DMA rx buffer\n");
-			return -EBUSY;
+			rc = -EBUSY;
+			goto out1;
 		}
+		schedule();
 	} while(state);
 
 	/* Set DMA buffers for receiver */
@@ -286,11 +288,51 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
 	    &regs->chan0_receive_start_addr + 4 * voice_num);
 	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) );
 
-	return 0;
+out0:
+	return rc;
+
+out1:
+	kirkwood_reset_voice_channel(tdm_dev);
+	return rc;
 }
 
 
 /**
+ * Release and reset hardware voice channel
+ * free DMA buffers.
+ * @param tdm_dev - TDM device early requested voice channel
+ * @return 0 - OK
+ */
+int kirkwood_release_voice_channel(struct tdm_device *tdm_dev)
+{
+	struct tdm_voice_channel* ch = tdm_dev->ch;
+	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+	int i;
+
+	if (!tdm_dev->ch)
+		return -ENODEV;
+
+	for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
+	{
+		if (onchip_ch->rx_buf[i])
+		{
+			dma_free_coherent(&ch->dev,
+			        ch->buffer_len, onchip_ch->rx_buf[i], onchip_ch->rx_buff_phy + i);
+			onchip_ch->rx_buf[i] = 0;
+		}
+
+		if (onchip_ch->tx_buf[i])
+		{
+			dma_free_coherent(&ch->dev,
+			        ch->buffer_len, onchip_ch->tx_buf[i], onchip_ch->tx_buff_phy + i);
+			onchip_ch->tx_buf[i] = 0;
+		}
+	}
+
+	return 0;
+}
+
+/**
  * Run tdm transmitter and receiver
  * @param tdm_dev - tdm device
  * @return 0 - ok
@@ -304,8 +346,8 @@ int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
 	struct tdm_voice_channel *ch = tdm_dev->ch;
 	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
 
-	memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
-	memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
+	for (i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
+		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
 
 	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
 	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );
@@ -364,11 +406,18 @@ int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
  */
 int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
 {
-	if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
-		return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);
+	unsigned long flags;
+	int latency;
+
+	spin_lock_irqsave(&onchip_ch->lock, flags);
+	if (onchip_ch->read_tx_buf_num <= onchip_ch->write_tx_buf_num)
+		latency = onchip_ch->write_tx_buf_num - onchip_ch->read_tx_buf_num;
 	else
-		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
-		       + atomic_read(&onchip_ch->write_tx_buf_num);
+		latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_tx_buf_num
+		       + onchip_ch->write_tx_buf_num;
+	spin_unlock_irqrestore(&onchip_ch->lock, flags);
+
+	return latency;
 }
 
 
@@ -379,11 +428,18 @@ int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
  */
 int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
 {
-	if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
-		return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
+	unsigned long flags;
+	int latency;
+
+	spin_lock_irqsave(&onchip_ch->lock, flags);
+	if (onchip_ch->read_rx_buf_num <= onchip_ch->write_rx_buf_num)
+		latency = onchip_ch->write_rx_buf_num - onchip_ch->read_rx_buf_num;
 	else
-		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
-		       + atomic_read(&onchip_ch->write_rx_buf_num);
+		latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_rx_buf_num
+		       + onchip_ch->write_rx_buf_num;
+	spin_unlock_irqrestore(&onchip_ch->lock, flags);
+
+	return latency;
 }
 
 
@@ -421,7 +477,7 @@ int kirkwood_poll_tx(struct tdm_device *tdm_dev)
  * @param num - current buffer number
  * @return next buffer number
  */
-static int inc_next_dma_buf_num(int num)
+static inline int inc_next_dma_buf_num(int num)
 {
 	num ++;
 	if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
@@ -445,6 +501,7 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
 {
 	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
 	int rc = 0;
+	unsigned long flags;
 
 	rc = wait_event_interruptible(ch->tx_queue,
 	                         get_tx_latency(onchip_ch) > 1);
@@ -452,10 +509,12 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
 	if (rc)
 		return rc;
 
-	memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
+	spin_lock_irqsave(&onchip_ch->lock, flags);
+	memcpy(onchip_ch->tx_buf[onchip_ch->read_tx_buf_num], data,
 	       ch->buffer_len);
-	atomic_set(&onchip_ch->read_tx_buf_num,
-		inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+	onchip_ch->read_tx_buf_num =
+		inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
+	spin_unlock_irqrestore(&onchip_ch->lock, flags);
 	return rc;
 }
 
@@ -473,6 +532,7 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
 {
 	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
 	int rc = 0;
+	unsigned long flags;
 
 	rc = wait_event_interruptible(ch->rx_queue,
 	                         get_rx_latency(onchip_ch) > 1);
@@ -480,10 +540,12 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
 	if (rc)
 		return rc;
 
-	memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
+	spin_lock_irqsave(&onchip_ch->lock, flags);
+	memcpy(data, onchip_ch->rx_buf[onchip_ch->read_rx_buf_num],
 	       ch->buffer_len);
-	atomic_set(&onchip_ch->read_rx_buf_num,
-		   inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+	onchip_ch->read_rx_buf_num =
+		   inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
+	spin_unlock_irqrestore(&onchip_ch->lock, flags);
 	return rc;
 }
 
@@ -595,11 +657,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
 		switch(mode) {
 		case IRQ_RECEIVE: {
 			/* get next buffer number, and move write/read pointer */
-			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
-			atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
-			if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
-				atomic_set(&onchip_ch->read_rx_buf_num,
-				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+			next_buf_num = inc_next_dma_buf_num(onchip_ch->write_rx_buf_num);
+			onchip_ch->write_rx_buf_num = next_buf_num;
+			if(next_buf_num == onchip_ch->read_rx_buf_num)
+				onchip_ch->read_rx_buf_num =
+				           inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
 
 			/* if receive overflow event */
 			if (overflow) {
@@ -631,11 +693,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
 
 		case IRQ_TRANSMIT: {
 			/* get next buffer number, and move write/read pointer */
-			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
-			atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
-			if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
-				atomic_set(&onchip_ch->read_tx_buf_num,
-				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+			next_buf_num = inc_next_dma_buf_num(onchip_ch->write_tx_buf_num);
+			onchip_ch->write_tx_buf_num=  next_buf_num;
+			if(next_buf_num == onchip_ch->read_tx_buf_num)
+				onchip_ch->read_tx_buf_num =
+				           inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
 
 			/* if transmit overflow event */
 			if (overflow) {
@@ -777,7 +839,8 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
 	/* Set controller data */
 	tdm->bus_num = pdev->id;
 	tdm->settings = hw;
-	tdm->setup_voice_channel = kirkwood_setup_voice_channel;
+	tdm->init_voice_channel = kirkwood_init_voice_channel;
+	tdm->release_voice_channel = kirkwood_release_voice_channel;
 	tdm->recv = kirkwood_recv;
 	tdm->send = kirkwood_send;
 	tdm->run_audio = kirkwood_tdm_run_audio;
@@ -787,13 +850,16 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
 
 	for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
 	{
+			struct kirkwood_tdm_voice *oncip_ch = onchip_tdm->voice_channels + i;
+
+	        spin_lock_init(&oncip_ch->lock);
 	        ch = tdm_alloc_voice_channel();
 	        if (ch == NULL) {
 	                dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
 	                goto out1;
 	        }
 
-            tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
+            tdm_register_new_voice_channel(tdm, ch, (void *)oncip_ch);
 	}
 
 
diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
index 6c7f34d..fe7aadf 100644
--- a/drivers/staging/tdm/kirkwood_tdm.h
+++ b/drivers/staging/tdm/kirkwood_tdm.h
@@ -58,6 +58,8 @@ struct kirkwood_tdm_regs {
  * Data specified for kirkwood hardware voice channel
  */
 struct kirkwood_tdm_voice {
+	spinlock_t		lock;
+
 	/* Transmitter and receiver buffers split to half.
 	 * While first half buffer is filling by DMA controller,
 	 * second half buffer is used by consumer and etc.
@@ -67,10 +69,10 @@ struct kirkwood_tdm_voice {
 
 	dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to tx_buf */
 	dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to rx_buf */
-	atomic_t write_tx_buf_num; /*  current writing transmit buffer number */
-	atomic_t read_tx_buf_num; /*  current reading transmit buffer number */
-	atomic_t write_rx_buf_num; /*  current writing receive buffer number */
-	atomic_t read_rx_buf_num; /*  current reading receive buffer number */
+	u8 write_tx_buf_num; /*  current writing transmit buffer number */
+	u8 read_tx_buf_num; /*  current reading transmit buffer number */
+	u8 write_rx_buf_num; /*  current writing receive buffer number */
+	u8 read_rx_buf_num; /*  current reading receive buffer number */
 };
 
 
diff --git a/drivers/staging/tdm/tdm.h b/drivers/staging/tdm/tdm.h
index ee7b5bf..80c1460 100644
--- a/drivers/staging/tdm/tdm.h
+++ b/drivers/staging/tdm/tdm.h
@@ -53,7 +53,7 @@ struct tdm_voice_channel {
 	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 list_head list; /*  Union all tdm voice channels by one controller */
 
 	struct device *dev; /*  device requested voice channel */
 };
@@ -106,7 +106,8 @@ struct tdm_controller {
 	/*  TDM hardware settings */
 	struct tdm_controller_hw_settings *settings;
 
-	int (*setup_voice_channel)(struct tdm_voice_channel *ch);
+	int (*init_voice_channel)(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch);
+	int (*release_voice_channel)(struct tdm_device *tdm_dev);
 	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);
diff --git a/drivers/staging/tdm/tdm_core.c b/drivers/staging/tdm/tdm_core.c
index 0182a4f..f3ac6fc 100644
--- a/drivers/staging/tdm/tdm_core.c
+++ b/drivers/staging/tdm/tdm_core.c
@@ -174,53 +174,64 @@ EXPORT_SYMBOL_GPL(tdm_unregister_driver);
 
 
 /**
- * Request unused voice channel
+ * Request and init 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;
+	struct tdm_controller *tdm = tdm_dev->controller;
+	struct tdm_voice_channel *ch;
+	unsigned long flags;
+	int rc;
 
-        spin_lock_irqsave(&tdm->lock, 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;
+                        ch->mode_wideband = tdm_dev->mode_wideband;
+                        ch->tdm_channel = tdm_dev->tdm_channel_num;
                         tdm_dev->ch = ch;
                         spin_unlock_irqrestore(&tdm->lock, flags);
+                        rc = tdm->init_voice_channel(tdm_dev, ch);
+                        if (rc)
+                        {
+                            dev_err(&tdm_dev->dev, "Can't init TDM voice channel\n");
+                        	return NULL;
+                        }
+
                         return ch;
                 }
 
-        spin_unlock_irqrestore(&tdm->lock, flags);
-        return NULL;
+	spin_unlock_irqrestore(&tdm->lock, flags);
+	return NULL;
 }
 
 /**
- * Release requested voice channel
+ * Release requested early 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;
+	struct tdm_controller *tdm = tdm_dev->controller;
+	struct tdm_voice_channel *ch;
+	unsigned long flags;
 
-        spin_lock_irqsave(&tdm->lock, flags);
+	spin_lock_irqsave(&tdm->lock, flags);
 
-        tdm_dev->ch = NULL;
+	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;
-                }
+			if (ch->dev == &tdm_dev->dev) {
+					tdm->release_voice_channel(tdm_dev);
+					ch->dev = NULL;
+					spin_unlock_irqrestore(&tdm->lock, flags);
+					return 0;
+			}
 
-        spin_unlock_irqrestore(&tdm->lock, flags);
+	spin_unlock_irqrestore(&tdm->lock, flags);
 
-        return -ENODEV;
+	return -ENODEV;
 }
 
 
@@ -326,7 +337,6 @@ struct tdm_voice_channel *tdm_alloc_voice_channel(void)
         if (!ch)
                 return NULL;
 
-        memset(ch, 0, sizeof *ch);
         init_waitqueue_head(&ch->tx_queue);
         init_waitqueue_head(&ch->rx_queue);
 
@@ -614,19 +624,6 @@ int tdm_add_device(struct tdm_device *tdm_dev)
                 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)
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 06/11] staging: remove device_attribute
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (4 preceding siblings ...)
  2013-03-01 10:57   ` [PATCH v2 05/11] staging: refactoring request/free voice channels Michail Kurachkin
@ 2013-03-01 10:58   ` Michail Kurachkin
  2013-03-01 11:00   ` [PATCH v2 07/11] staging: added issues description in TODO file Michail Kurachkin
                     ` (4 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 10:58 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/tdm_core.c |   16 +---------------
 1 files changed, 1 insertions(+), 15 deletions(-)

diff --git a/drivers/staging/tdm/tdm_core.c b/drivers/staging/tdm/tdm_core.c
index f3ac6fc..cf8c26a 100644
--- a/drivers/staging/tdm/tdm_core.c
+++ b/drivers/staging/tdm/tdm_core.c
@@ -50,20 +50,6 @@ static void tdmdev_release(struct device *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);
@@ -104,7 +90,7 @@ static struct class tdm_class = {
  */
 struct bus_type tdm_bus_type = {
         .name           = "tdm",
-        .dev_attrs      = tdm_dev_attrs,
+        .dev_attrs      = NULL,
         .match          = tdm_match_device,
         .uevent         = tdm_uevent,
         .suspend        = NULL,
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 07/11] staging: added issues description in TODO file
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (5 preceding siblings ...)
  2013-03-01 10:58   ` [PATCH v2 06/11] staging: remove device_attribute Michail Kurachkin
@ 2013-03-01 11:00   ` Michail Kurachkin
  2013-03-01 11:00   ` [PATCH v2 08/11] staging: removing of buffer filling flag and also reverting old buffer related fix which is not really effective Michail Kurachkin
                     ` (3 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 11:00 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/si3226x/TODO |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)

diff --git a/drivers/staging/si3226x/TODO b/drivers/staging/si3226x/TODO
index 83c5958..b2cb27e 100644
--- a/drivers/staging/si3226x/TODO
+++ b/drivers/staging/si3226x/TODO
@@ -1,2 +1,8 @@
 1) mutex_lock(&slic_chr_dev_lock);
-This locking is very heavy handed. You are holding it across the entire open, close, read, write, ioctl, and it is protecting a bunch of different things.
+This locking is very heavy handed and should be reworked
+
+2) SLIC driver works unstable when compiled as module, loaded, then unloaded and finally reloaded again.
+
+3) Current version of Si3226x SLIC driver is limited. It implements only base functionality such as Answer, Hangup, receive DTMF, send DTMF, send Caller ID.
+
+
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 08/11] staging: removing of buffer filling flag and also reverting old buffer related fix which is not really effective
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (6 preceding siblings ...)
  2013-03-01 11:00   ` [PATCH v2 07/11] staging: added issues description in TODO file Michail Kurachkin
@ 2013-03-01 11:00   ` Michail Kurachkin
  2013-03-01 11:02   ` [PATCH v2 09/11] staging: fixed e-mail address Michail Kurachkin
                     ` (2 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 11:00 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/kirkwood_tdm.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
index e4a9106..57c8925 100644
--- a/drivers/staging/tdm/kirkwood_tdm.c
+++ b/drivers/staging/tdm/kirkwood_tdm.c
@@ -678,7 +678,7 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
 			}
 
 			/* waiting while dma providing access to buffer */
-			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1);
+			/* while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1); */
 
 			/* set next buffer address */
 			writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
@@ -714,7 +714,7 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
 			}
 
 			/* waiting while dma providing access to buffer */
-			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100);
+			/* while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100); */
 
 			/* set next buffer address */
 			writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 09/11] staging: fixed e-mail address
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (7 preceding siblings ...)
  2013-03-01 11:00   ` [PATCH v2 08/11] staging: removing of buffer filling flag and also reverting old buffer related fix which is not really effective Michail Kurachkin
@ 2013-03-01 11:02   ` Michail Kurachkin
  2013-03-01 11:03   ` [PATCH v2 10/11] staging: add issuses in TODO Michail Kurachkin
  2013-03-01 11:04   ` [PATCH v2 11/11] " Michail Kurachkin
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 11:02 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/kirkwood_tdm.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
index 57c8925..4d254bc 100644
--- a/drivers/staging/tdm/kirkwood_tdm.c
+++ b/drivers/staging/tdm/kirkwood_tdm.c
@@ -991,7 +991,7 @@ static void __exit kirkwood_exit_tdm(void)
 
 module_init(kirkwood_init_tdm);
 module_exit(kirkwood_exit_tdm);
-MODULE_AUTHOR("Michail Kurochkin <stelhs@yandex.ru>");
+MODULE_AUTHOR("Michail Kurochkin <michail.kurachkin@promwad.com>");
 MODULE_DESCRIPTION("TDM controller driver for Marvel kirkwood arch.");
 MODULE_LICENSE("GPL");
 
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 10/11] staging: add issuses in TODO
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (8 preceding siblings ...)
  2013-03-01 11:02   ` [PATCH v2 09/11] staging: fixed e-mail address Michail Kurachkin
@ 2013-03-01 11:03   ` Michail Kurachkin
  2013-03-01 11:04   ` [PATCH v2 11/11] " Michail Kurachkin
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 11:03 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/si3226x/TODO |   15 +++++++++++++--
 1 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/staging/si3226x/TODO b/drivers/staging/si3226x/TODO
index b2cb27e..8d81465 100644
--- a/drivers/staging/si3226x/TODO
+++ b/drivers/staging/si3226x/TODO
@@ -1,8 +1,19 @@
+Needed to be done to get the code out of the staging tree:
 1) mutex_lock(&slic_chr_dev_lock);
 This locking is very heavy handed and should be reworked
 
-2) SLIC driver works unstable when compiled as module, loaded, then unloaded and finally reloaded again.
+2) SLIC driver works unstable when compiled as module,
+loaded, then unloaded and finally reloaded again.
 
-3) Current version of Si3226x SLIC driver is limited. It implements only base functionality such as Answer, Hangup, receive DTMF, send DTMF, send Caller ID.
+3) ensure stability of current driver, define several test scenarios
 
+4) Current version of Si3226x SLIC driver is limited. It implements
+only base functionality such as Answer, Hangup, receive DTMF,
+send DTMF, send Caller ID.
+
+
+Mainteiners:
+Michail Kurachkin <Michail.Kurachkin@promwad.com> <stelhs@yandex.ru>
+Ivan Kuten <ivan.kuten@promwad.com>
+Promwad company <info@promwad.com>
 
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 11/11] staging: add issuses in TODO
  2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
                     ` (9 preceding siblings ...)
  2013-03-01 11:03   ` [PATCH v2 10/11] staging: add issuses in TODO Michail Kurachkin
@ 2013-03-01 11:04   ` Michail Kurachkin
  10 siblings, 0 replies; 17+ messages in thread
From: Michail Kurachkin @ 2013-03-01 11:04 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum, Ryan Mallon
  Cc: Michail Kurachkin

Signed-off-by: Michail Kurachkin <michail.kurachkin@promwad.com>
---
 drivers/staging/tdm/TODO |   16 ++++++++++++++++
 1 files changed, 16 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/tdm/TODO

diff --git a/drivers/staging/tdm/TODO b/drivers/staging/tdm/TODO
new file mode 100644
index 0000000..9e5fe80
--- /dev/null
+++ b/drivers/staging/tdm/TODO
@@ -0,0 +1,16 @@
+Needed to be done to get the code out of the staging tree:
+1) Marvell Kirkwood TDM driver works unstable when compiled as module,
+loaded, then unloaded and finally reloaded again.
+
+2) Add Documentation/tdm-bus.txt
+
+3) ensure stability of current driver, define several test scenarios
+
+4) look into Freescale TDM https://patchwork.kernel.org/patch/1249881/ , adopt Freescale bits into current TDM framework
+
+
+Mainteiners:
+Michail Kurachkin <Michail.Kurachkin@promwad.com> <stelhs@yandex.ru>
+Ivan Kuten <ivan.kuten@promwad.com>
+Promwad company <info@promwad.com>
+
-- 
1.7.5.4


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 01/11] staging: Initial commit of TDM core
  2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
@ 2013-03-01 22:54     ` Ryan Mallon
  2013-03-11 17:17     ` Greg Kroah-Hartman
  1 sibling, 0 replies; 17+ messages in thread
From: Ryan Mallon @ 2013-03-01 22:54 UTC (permalink / raw)
  To: Michail Kurachkin
  Cc: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum

On 01/03/13 21:50, Michail Kurachkin wrote:

> From: Michail Kurochkin <michail.kurachkin@promwad.com>
> 
> Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>


Hi Michail, 

Quick review below. I'll try to find some time to look through
the other patches later.

~Ryan

> ---
>  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;
> +
> +
> +


Nitpick - Please just leave a single blank line between functions,
globals, etc. Same applies in many other places.

> +/**
> + * 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 */


These could be:

	bool clock_is_out;
	bool fs_clock_is_out;

and then remove the defines?

> +
> +	/*  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;


Same here:

	bool fs_polarity_negative;
	bool data_polarity_negative;

?

> +
> +	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 */

Indentation is messed here. Same is true in a few other places. Running
through checkpatch should point them all out.

> +	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;


A comment explaining what fields the lock protects would be nice.

> +
> +	struct list_head list; /*  Union all TDM controllers */
> +
> +	s16 bus_num; /*  Number of controller, use -1 for auto numeration */


Do you really need to use fixed width types for some of these values?
Generally fixed width types should only be used for things which map
directly to the hardware, otherwise it is fine to use int here.

> +
> +/* 	struct tdm_voice_channel *voice_channels; // Hardware or software voice channels transport */
> +/* 	u8 count_voice_channels; // count voice channels supported by current TDM controller */

Remove commented out code?

> +	/* 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;
> +}


Do you really need the dev check? Why would a caller be doing 
to_tdm_controller on a NULL device?

> +
> +
> +/**
> + * 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);
> +}


Nitpick - This helper function is actually more verbose than the
function it calls :-). 

> +
> +
> +/**
> + * 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);
> +}


This function really shouldn't have a check for dev, since presumably
you had to get the device in able to put it, so hopefully it isn't
NULL. 

> +
> +
> +/**
> + * 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


Why is this ifdef here, don't you need CONFIG_TDM for all of the 
above also?

> +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.kurachkin@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,
> +        };


Indentation is all messed here.

> +
> +
> +
> +/**
> + * 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,


Nitpick - You can ommit the NULL fields, they will automatically
be initialised to 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;

You don't need the checks here, since you will just end up
assigning NULL if the various callbacks are not set.

> +        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);


All usage of tdm->lock use irqsave/irqrestore, but I can't see an
interrupt handlers in this patch. irqsave/restore should be used
when you don't know if the callsite will be run from interrupt
context or not. Is that the case here? Otherwise you should be
using normal spin_lock, or spin_lock_irq if this callsite runs
in process context, but other uses of the lock run in irq 
context.

> +
> +	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);
> +}


This function is confusingly named. It's name suggested it should return
true if the boardinfo matches the device, but instead it goes and
allocates a new device on match. It also doesn't propogate any errors
back to the caller if the device allocation fails.

> +
> +
> +
> +/**
> + * 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);


Nitpick:

	sizeof(*tdm)

is more typical.

> +        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);

Is kzfree, rather than just plain kfree, really necessary?

> +        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);


Remove the memset. kzalloc already clears the memory.

> +        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;


Should this list access be protected by tdm->lock? If the caller
needs to hold the lock then that should be documented.

> +
> +	ch->channel_num = last_num + 1;
> +	ch->private_data = driver_private;
> +	list_add_tail(&ch->list, &tdm->voice_channels);


Same here, this probably needs to be protected by tdm->lock.

> +
> +	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);
> +        }


Locking? If it is the callers responsibility, document it.

> +
> +        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;


Locking.

> +
> +	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);


I haven't looked at the other patches yet, but this is the only place
that dyn_bys_id is used in this patch. Does it really need to be
atomic, or can it just be a plain int protected by a mutex? Atomics
can be somewhat difficult to get correct since atomic != concurrency
safe.

> +                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;

Nitpick - If you initialise status to 0 above then you can remove
this line.

> +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_for_each_child is not __must_check, so you can remove the
dummy variable.

> +        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");


You don't need to print errors on kalloc failures, you will
automatically get a stack trace.

> +                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);


Ick, please move this out of the function.

> +        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 &&


You just rejected mode_wideband devices above?

> +            (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;


Missing put_device.

> +        }
> +
> +        printk("ch = %s\n", dev_name(ch->dev));

Left over debugging? Remove or change to dev_dbg.

> +        /* 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;


Missing put_device.

> +        }
> +
> +        /* 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();


Is it possible to check this inside the function and return
-EINVAL if it is not true, so that dodgy callers can't cause
an overflow?

> + * @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);


Use kcalloc.

> +        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);
> +



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver
  2013-03-01 10:52   ` [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver Michail Kurachkin
@ 2013-03-01 23:54     ` Ryan Mallon
  0 siblings, 0 replies; 17+ messages in thread
From: Ryan Mallon @ 2013-03-01 23:54 UTC (permalink / raw)
  To: Michail Kurachkin
  Cc: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum

On 01/03/13 21:52, Michail Kurachkin wrote:

> From: Michail Kurochkin <michail.kurachkin@promwad.com>
> 
> Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
> ---
>  drivers/staging/tdm/kirkwood_tdm.c |  932 ++++++++++++++++++++++++++++++++++++
>  drivers/staging/tdm/kirkwood_tdm.h |  110 +++++
>  2 files changed, 1042 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/staging/tdm/kirkwood_tdm.c
>  create mode 100644 drivers/staging/tdm/kirkwood_tdm.h
> 
> diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
> new file mode 100644
> index 0000000..4366d38
> --- /dev/null
> +++ b/drivers/staging/tdm/kirkwood_tdm.c
> @@ -0,0 +1,932 @@
> +/**********************************************************************
> + * Author: Michail Kurachkin
> + *
> + * Contact: michail.kurachkin@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/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/gpio.h>
> +#include <linux/init.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/in.h>
> +#include <linux/ip.h>
> +#include <linux/tcp.h>
> +#include <linux/udp.h>
> +#include <linux/etherdevice.h>
> +#include <linux/delay.h>
> +#include <linux/ethtool.h>
> +#include <linux/platform_device.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/phy.h>
> +#include <linux/io.h>
> +#include <linux/types.h>
> +#include <linux/inet_lro.h>
> +#include <linux/slab.h>
> +#include <asm/system.h>


This file is marked on arm as to be deleted.

> +#include <linux/io.h>
> +#include <asm/uaccess.h>


Use <linux/uaccess.h>

> +#include <asm/mach-types.h>
> +#include <asm/mach/arch.h>


Drivers should really avoid including <asm/mach-types.h> if they
can avoid it.

> +#include <linux/mbus.h>
> +#include <linux/miscdevice.h>
> +#include <linux/platform_device.h>
> +
> +#include "tdm.h"
> +#include "kirkwood_tdm.h"
> +
> +
> +/**
> + * Init hardware tdm controller
> + * @param tdm - tdm_controller descriptor
> + * @return
> + */
> +static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
> +{
> +	struct kirkwood_tdm *onchip_tdm =
> +	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);


Don't need the cast.

> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +	struct tdm_controller_hw_settings *hw = tdm->settings;
> +	u32 control_val = 0;
> +	u32 pclk_freq;
> +	unsigned long flags;
> +	spinlock_t lock;
> +	spin_lock_init(&lock);


What use is a local spin lock? Any concurrent callers will be
using different locks. If you just need to disable interrupts
you can use local_irq_save and local_irq_restore (or
local_irq_disable/enable if you know that interrupts are
already enabled).

> +
> +
> +	// Errata GL-CODEC-10: resolve problem witch FS
> +	spin_lock_irqsave(&lock, flags);
> +	writel(0x03ffff00, &regs->pcm_ctrl_reg + 0x1000);
> +	writel(5, &regs->pcm_ctrl_reg + 0x101C);
> +	spin_unlock_irqrestore(&lock, flags);
> +
> +
> +	writel(0, &regs->int_reset_sel);
> +	writel(0x3ffff, &regs->int_event_mask);
> +	writel(0, &regs->int_status_mask);
> +	writel(0, &regs->int_status);
> +
> +	writeb(0x41, &regs->pcm_ctrl_reg + 0xC41);
> +	writeb(0x10, &regs->pcm_ctrl_reg + 0xC40);
> +
> +	// Configuring PCLK and FS frequency
> +	pclk_freq = hw->count_time_slots * 8 * hw->fs_freq;
> +	switch(pclk_freq) {
> +	case 256:
> +		writel(0x1, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x4, &regs->num_time_slot);
> +		break;
> +
> +	case 512:
> +		writel(0x2, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x8, &regs->num_time_slot);
> +		break;
> +
> +	case 1024:
> +		writel(0x4, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x10, &regs->num_time_slot);
> +		break;
> +
> +	case 2048:
> +		writel(0x8, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x20, &regs->num_time_slot);
> +		break;
> +
> +	case 4096:
> +		writel(0x10, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x40, &regs->num_time_slot);
> +		break;
> +
> +	case 8192:
> +		writel(0x20, &regs->tdm_pcm_clock_div);
> +		writel(0x0, &regs->dummy_rx_write);
> +		writel(0x80, &regs->num_time_slot);
> +		break;
> +
> +	default:
> +		dev_err(&tdm->dev, "Incorrect count of time slots parameter\n");
> +		return -EINVAL;
> +	}


Same #defines for all the magic values above would be good.
Same elsewhere, there seem to be a lot of magic values in this
file.

> +
> +	control_val = readl(&regs->pcm_ctrl_reg);
> +
> +	if(hw->clock_direction == TDM_CLOCK_OUTPUT)
> +		control_val &= (u32)~(1 << 0);


You don't need the cast here. Same elsewhere.

> +	else
> +		control_val |= (1 << 0);
> +
> +
> +	if(hw->fs_clock_direction == TDM_CLOCK_OUTPUT)
> +		control_val &= (u32) ~(1 << 1);
> +	else
> +		control_val |= (1 << 1);
> +
> +
> +	if(hw->fs_polarity == TDM_POLAR_POSITIV)
> +		control_val &= (u32) ~(1 << 4);
> +	else
> +		control_val |= (1 << 4);
> +
> +
> +	if(hw->data_polarity == TDM_POLAR_POSITIV)
> +		control_val &= (u32) ~(1 << 2);
> +	else
> +		control_val |= (1 << 2);
> +
> +
> +	if(hw->channel_size == 1)
> +		control_val &= (u32) ~(1 << 6);
> +	else
> +		control_val |= (1 << 6);
> +
> +	writel(control_val, &regs->pcm_ctrl_reg);
> +	writel(0, &regs->chan_time_slot_ctrl);
> +	writel(0, &regs->chan0_enable_disable);
> +	writel(0, &regs->chan0_enable_disable + 4);
> +
> +	return 0;
> +}
> +
> +/**
> + * deInitialization hardware tdm controller
> + * @param tdm - tdm_controller descriptor
> + */
> +static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
> +{
> +	struct kirkwood_tdm *onchip_tdm =
> +	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +
> +	writel(0, &regs->pcm_ctrl_reg);
> +	writel(0, &regs->chan_time_slot_ctrl);
> +}
> +
> +/**
> + * Setup hardware voice channel
> + * @param ch - voice hardware channel
> + * @return 0 - OK
> + */
> +int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)


static?

> +{
> +	struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
> +	struct tdm_controller *tdm = tdm_dev->controller;
> +	struct tdm_controller_hw_settings *hw = tdm->settings;
> +	struct kirkwood_tdm *onchip_tdm =
> +	    (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +	unsigned long timeout;
> +	u32 state;
> +	int i;
> +
> +	u8 voice_num = ch->channel_num;
> +
> +	/* calculate buffer size */
> +	ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
> +
> +	/* Request timeslot for voice channel */
> +	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
> +	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
> +
> +	/* FIXME: move coherent_dma_mask to board specific */
> +	tdm_dev->dev.coherent_dma_mask = 0xffffffff;
> +
> +	/* Allocate rx and tx buffers */
> +	for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++) {
> +		/* allocate memory for DMA receiver */
> +		onchip_ch->rx_buf[i] =
> +		        dma_alloc_coherent(&tdm_dev->dev,
> +		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
> +
> +		if (onchip_ch->rx_buf == NULL) {
> +			dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
> +			return -ENOMEM;
> +		}
> +		memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
> +
> +
> +		/* allocate memory for DMA transmitter */
> +		onchip_ch->tx_buf[i] =
> +		        dma_alloc_coherent(&tdm_dev->dev,
> +		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
> +
> +		if (onchip_ch->tx_buf == NULL) {
> +			dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
> +			return -ENOMEM;
> +		}
> +		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
> +	}
> +
> +	atomic_set(&onchip_ch->write_rx_buf_num, 0);
> +	atomic_set(&onchip_ch->write_tx_buf_num, 0);
> +	atomic_set(&onchip_ch->read_rx_buf_num, 0);
> +	atomic_set(&onchip_ch->read_tx_buf_num, 0);
> +
> +	/* Set length for DMA */
> +	writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
> +	       &regs->chan0_total_sample + voice_num);
> +
> +	/* Waiting for program transmit DMA */
> +	timeout = jiffies + HZ;
> +	do {
> +		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100;
> +		if(time_after(jiffies, timeout)) {
> +			dev_err(ch->dev, "Can`t program DMA tx buffer\n");
> +			return -EBUSY;
> +		}
> +	} while(state);
> +
> +	/* Set DMA buffers fo transmitter */
> +	writel((u32)onchip_ch->tx_buff_phy[0],
> +	    &regs->chan0_transmit_start_addr + 4 * voice_num);
> +	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> +	/* Waiting for program received DMA */
> +	timeout = jiffies + HZ;
> +	do {
> +		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1;
> +		if(time_after(jiffies, timeout)) {
> +			dev_err(ch->dev, "Can`t program DMA rx buffer\n");
> +			return -EBUSY;
> +		}
> +	} while(state);


Can this be wrapped up as a helper function somehow so it doesn't
need to be open coded everywhere?

> +
> +	/* Set DMA buffers for receiver */
> +	writel((u32)onchip_ch->rx_buff_phy[0],
> +	    &regs->chan0_receive_start_addr + 4 * voice_num);
> +	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) );
> +
> +	return 0;
> +}
> +
> +
> +/**
> + * Run tdm transmitter and receiver
> + * @param tdm_dev - tdm device
> + * @return 0 - ok
> + */
> +int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
> +{
> +	struct tdm_controller *tdm = tdm_dev->controller;
> +	struct kirkwood_tdm *onchip_tdm =
> +	        (struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +	struct tdm_voice_channel *ch = tdm_dev->ch;
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> +	memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
> +	memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
> +
> +	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
> +	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );
> +
> +	/* enable Tx interrupts */
> +	writel(0, &regs->int_status);
> +	writel((readl(&regs->int_status_mask) | TDM_INT_TX(ch->channel_num)),
> +	        &regs->int_status_mask);
> +
> +	/* enable Rx interrupts */
> +	writel(0, &regs->int_status);
> +	writel((readl(&regs->int_status_mask) | TDM_INT_RX(ch->channel_num)),
> +	        &regs->int_status_mask);
> +
> +	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * ch->channel_num));
> +	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * ch->channel_num) + 1);
> +
> +	return 0;
> +}
> +
> +
> +/**
> + * Stop tdm transmitter and receiver
> + * @param tdm_dev - tdm device
> + * @return 0 - ok
> + */
> +int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
> +{
> +	struct tdm_controller *tdm = tdm_dev->controller;
> +	struct kirkwood_tdm *onchip_tdm =
> +			(struct kirkwood_tdm *)dev_get_drvdata(&tdm->dev);
> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +	struct tdm_voice_channel *ch = tdm_dev->ch;
> +
> +	/* disable Tx interrupts */
> +	writel((readl(&regs->int_status_mask) & (~TDM_INT_TX(ch->channel_num))),
> +	    &regs->int_status_mask);
> +	writel(0, &regs->int_status);
> +
> +	/* disable Rx interrupts */
> +	writel((readl(&regs->int_status_mask) & (~TDM_INT_RX(ch->channel_num))),
> +	    &regs->int_status_mask);
> +	writel(0, &regs->int_status);
> +
> +	writeb(0x0, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
> +	writeb(0x0, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );

This gets done a lot. Maybe have a function to get a channel register,
which takes the base channel register and the channel number, rather
than repeating the calculation everywhere.

> +	return 0;
> +}
> +
> +
> +/**
> + * Get DMA tx buffers latency
> + * @param onchip_ch - kirkwood based voice channel
> + * @return latency in buffers
> + */
> +int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
> +{
> +	if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
> +		return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);


The values of read/write_tx_buf_num change potentially change between
doing the if check, and returning the value. they can also change
between the individual atomic reads. Is that all safe? I think maybe
you want to replace all of the atomics with a mutex/spinlock which
protects these variables. 

> +	else
> +		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
> +		       + atomic_read(&onchip_ch->write_tx_buf_num);
> +}
> +
> +
> +/**
> + * Get DMA rx buffers latency
> + * @param onchip_ch - kirkwood based voice channel
> + * @return latency in buffers
> + */
> +int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
> +{
> +	if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
> +		return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
> +	else
> +		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
> +		       + atomic_read(&onchip_ch->write_rx_buf_num);
> +}
> +
> +
> +/**
> + * 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 kirkwood_poll_rx(struct tdm_device *tdm_dev)
> +{
> +	struct tdm_voice_channel *ch = tdm_dev->ch;
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> +	return get_rx_latency(onchip_ch) > 1;


Again, the values of the atomics may have changed since you called
get_rx_latency, so the return value here could be wrong.

> +}
> +
> +
> +/**
> + * 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 kirkwood_poll_tx(struct tdm_device *tdm_dev)
> +{
> +	struct tdm_voice_channel *ch = tdm_dev->ch;
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +
> +	return get_tx_latency(onchip_ch) > 1;
> +}
> +
> +
> +
> +/**
> + * Get next dma buffer number
> + * @param num - current buffer number
> + * @return next buffer number
> + */
> +static int inc_next_dma_buf_num(int num)
> +{
> +	num ++;
> +	if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
> +		num = 0;
> +
> +	return num;
> +}
> +
> +
> +
> +/**
> + * Send voice data block to tdm voice channel controller.
> + * @param ch - voice channel attendant to transmit data in TDM frame
> + * @param data - data to be transmit. Length of data must be equal to
> + * 		value returned by get_voice_block_size()
> + *
> + * Context: can sleep
> + * @return 0 on success; negative errno on failure
> + */
> +static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
> +{
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +	int rc = 0;
> +
> +	rc = wait_event_interruptible(ch->tx_queue,
> +	                         get_tx_latency(onchip_ch) > 1);
> +
> +	if (rc)
> +		return rc;
> +
> +	memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
> +	       ch->buffer_len);
> +	atomic_set(&onchip_ch->read_tx_buf_num,
> +		inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> +	return rc;
> +}
> +
> +
> +/**
> + * Receive voice data block from TDM voice channel controller.
> + * @param ch - voice channel attendant to transmit data in TDM frame
> + * @param data - pointer to read data received by DMA.
> +                 Length data for read equal to value returned by get_tdm_voice_block_size()
> + *
> + * Context: can sleep
> + * @return 0 on success; negative errno on failure
> + */
> +static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
> +{
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +	int rc = 0;
> +
> +	rc = wait_event_interruptible(ch->rx_queue,
> +	                         get_rx_latency(onchip_ch) > 1);
> +
> +	if (rc)
> +		return rc;
> +
> +	memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
> +	       ch->buffer_len);
> +	atomic_set(&onchip_ch->read_rx_buf_num,
> +		   inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> +	return rc;
> +}
> +
> +
> +
> +/**
> + * kirkwood_tdm_irq - IRQ handler for Kirkwood TDM
> + * @irq: IRQ number for this TDM controller
> + * @context_data: structure for TDM controller kirkwood_tdm
> + * Context: can not sleep
> + */
> +static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)


int irq.

> +{
> +	struct kirkwood_tdm *onchip_tdm = context_data;
> +	struct kirkwood_tdm_regs *regs = onchip_tdm->regs;
> +	struct tdm_controller *tdm = to_tdm_controller(onchip_tdm->controller_dev);
> +	struct tdm_voice_channel *ch;
> +	struct kirkwood_tdm_voice *onchip_ch;
> +	struct tdm_device *tdm_dev;
> +
> +	irqreturn_t ret = IRQ_NONE;
> +	u32 status;
> +	u8 i;
> +
> +	int voice_num; /* current voice channel */
> +	int next_buf_num; /* number of next buffer */
> +	int mode; /* irq event mode: */
> +	int overflow = 0;
> +	int full = 0;
> +
> +	enum irq_event_mode {
> +		IRQ_RECEIVE,
> +		IRQ_TRANSMIT,
> +	};
> +
> +	status = readl(&regs->int_status);
> +
> +	if ((status & 0xFF) == 0)
> +		return ret;
> +
> +	/*  Check first 8 bit in status mask register for detect event type */
> +	for(i = 0; i < 8; i++) {
> +		if((status & (1 << i)) == 0)
> +			continue;
> +
> +		writel(status & ~(1 << i), &regs->int_status);
> +
> +		switch(i) {
> +		case 0:
> +			mode = IRQ_RECEIVE;
> +			voice_num = 0;
> +			overflow = 1;
> +			break;
> +
> +		case 1:
> +			mode = IRQ_TRANSMIT;
> +			voice_num = 0;
> +			overflow = 1;
> +			break;
> +
> +		case 2:
> +			mode = IRQ_RECEIVE;
> +			voice_num = 1;
> +			overflow = 1;
> +			break;
> +
> +		case 3:
> +			mode = IRQ_TRANSMIT;
> +			voice_num = 1;
> +			overflow = 1;
> +			break;
> +
> +		case 4:
> +			mode = IRQ_RECEIVE;
> +			voice_num = 0;
> +			overflow = 0;
> +			full = 0;
> +			break;
> +
> +		case 5:
> +			mode = IRQ_TRANSMIT;
> +			voice_num = 0;
> +			overflow = 0;
> +			full = 0;
> +			break;
> +
> +		case 6:
> +			mode = IRQ_RECEIVE;
> +			voice_num = 1;
> +			overflow = 0;
> +			full = 0;
> +			break;
> +
> +		case 7:
> +			mode = IRQ_TRANSMIT;
> +			voice_num = 1;
> +			overflow = 0;
> +			full = 0;
> +			break;
> +		}
> +
> +		/* �urrent voice channel struct */
> +		ch = get_voice_channel_by_num(tdm, voice_num);
> +		onchip_ch = ch->private_data;
> +
> +		/* TDM device attached to current voice channel */
> +		tdm_dev = to_tdm_device(ch->dev);
> +
> +		switch(mode) {
> +		case IRQ_RECEIVE: {
> +			/* get next buffer number, and move write/read pointer */
> +			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
> +			atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
> +			if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
> +				atomic_set(&onchip_ch->read_rx_buf_num,
> +				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> +
> +			/* if receive overflow event */
> +			if (overflow) {
> +				/* set next buffer address */
> +				writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
> +				       &regs->chan0_receive_start_addr + 4 * voice_num);
> +				writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num));
> +
> +				/* enable receiver */
> +				writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * voice_num));
> +
> +				ret = IRQ_HANDLED;
> +				break;
> +			}
> +
> +			/* waiting while dma providing access to buffer */
> +			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1);
> +
> +			/* set next buffer address */
> +			writel((u32)onchip_ch->rx_buff_phy[next_buf_num],
> +			       &regs->chan0_receive_start_addr + 4 * voice_num);
> +			writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num));
> +
> +			wake_up_interruptible(&ch->rx_queue);
> +
> +			ret = IRQ_HANDLED;
> +		}
> +		break;
> +
> +		case IRQ_TRANSMIT: {
> +			/* get next buffer number, and move write/read pointer */
> +			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
> +			atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
> +			if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
> +				atomic_set(&onchip_ch->read_tx_buf_num,
> +				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> +
> +			/* if transmit overflow event */
> +			if (overflow) {
> +				/* set next buffer address */
> +				writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
> +				       &regs->chan0_transmit_start_addr + 4 * voice_num);
> +				writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> +				/* enable transmitter */
> +				writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * voice_num) + 1);
> +
> +				ret = IRQ_HANDLED;
> +				break;
> +			}
> +
> +			/* waiting while dma providing access to buffer */
> +			while(readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100);
> +
> +			/* set next buffer address */
> +			writel((u32)onchip_ch->tx_buff_phy[next_buf_num],
> +			       &regs->chan0_transmit_start_addr + 4 * voice_num);
> +			writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) + 1);
> +
> +			wake_up_interruptible(&ch->tx_queue);
> +
> +			ret = IRQ_HANDLED;
> +		}
> +		break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +
> +/**
> + * Configuring mbus windows for correct access to DMA memory
> + * @param base_regs - base address for tdm registers
> + * @param dram - dram settings
> + */
> +static void kirkwood_tdm_mbus_windows(void *base_regs,
> +                                      struct mbus_dram_target_info *dram)
> +{
> +	int i;
> +
> +	for (i = 0; i < 4; i++) {
> +		writel(0, (u8 *)base_regs + TDM_WINDOW_CTRL(i));
> +		writel(0, (u8 *)base_regs + TDM_WINDOW_BASE(i));
> +	}
> +
> +	for (i = 0; i < dram->num_cs; i++) {
> +		struct mbus_dram_window *cs = dram->cs + i;
> +
> +		writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) |
> +		       (dram->mbus_dram_target_id << 4) | 1,
> +		       (u8 *)base_regs + TDM_WINDOW_CTRL(i));
> +
> +		writel(cs->base, (u8 *)base_regs + TDM_WINDOW_BASE(i));
> +	}
> +}
> +
> +
> +
> +static int __init kirkwood_tdm_probe(struct platform_device *pdev)


probe functions should not be marked __init.

> +{
> +	int err = 0;
> +	struct tdm_controller *tdm;
> +	struct kirkwood_tdm *onchip_tdm = NULL;
> +	struct resource *res;
> +	struct tdm_controller_hw_settings *hw = pdev->dev.platform_data;
> +	struct tdm_voice_channel *ch;
> +	int i;
> +
> +	tdm = tdm_alloc_controller(&pdev->dev, sizeof(struct kirkwood_tdm));
> +	if (tdm == NULL) {
> +		dev_err(&pdev->dev, "Can`t alloc memory\n");


Don't need the error message, kalloc failures give a stack trace.

> +		err = -ENOMEM;
> +		goto out0;
> +	}
> +
> +	platform_set_drvdata(pdev, tdm);
> +	onchip_tdm = tdm_controller_get_devdata(tdm);
> +
> +	if (pdev->id != -1)
> +		tdm->bus_num = pdev->id;
> +
> +	/* Check hardware settings */
> +	if (hw->fs_freq != 8 && hw->fs_freq != 16) {
> +		dev_err(&pdev->dev, "Fs frequency may be 8kHz o 16kHz. "
> +		    "Frequency %d is not incorrect\n", hw->fs_freq);
> +		err = -EINVAL;
> +		goto out1;
> +	}
> +
> +	if (hw->count_time_slots > 64) {
> +		dev_err(&pdev->dev, "Incorrect count time slots. "
> +		    "No more than 64 timeslots per one FS. "
> +		    "Current set %d\n", hw->count_time_slots);


Don't split string over multiple lines, they are an exception to the
80 column rule for easy grepability.

> +		err = -EINVAL;
> +		goto out1;
> +	}
> +
> +	if (hw->channel_size > 2 || hw->channel_size == 0) {
> +		dev_err(&pdev->dev, "Incorrect count time slots. "
> +		    "No more than 64 timeslots per one FS. "
> +		    "Current set %d\n", hw->count_time_slots);


Error message looks wrong.

> +		err = -EINVAL;
> +		goto out1;
> +	}
> +
> +	if (hw->clock_direction != TDM_CLOCK_INPUT &&
> +	    hw->clock_direction != TDM_CLOCK_OUTPUT) {
> +		dev_err(&pdev->dev, "Incorrect PCLK clock direction value\n");
> +		err = -EINVAL;
> +		goto out1;
> +	}


Using a bool for this value, as suggested in the other patch would
remove the need for this check.

> +
> +	if (hw->fs_clock_direction != TDM_CLOCK_INPUT &&
> +	    hw->fs_clock_direction != TDM_CLOCK_OUTPUT) {
> +		dev_err(&pdev->dev, "Incorrect FS clock direction value\n");
> +		err = -EINVAL;
> +		goto out1;
> +	}

> +
> +	if (hw->fs_polarity != TDM_POLAR_NEGATIVE &&
> +	    hw->fs_polarity != TDM_POLAR_POSITIV) {
> +		dev_err(&pdev->dev, "Incorrect FS polarity value\n");
> +		err = -EINVAL;
> +		goto out1;
> +	}
> +
> +	if (hw->data_polarity != TDM_POLAR_NEGATIVE &&
> +	    hw->data_polarity != TDM_POLAR_POSITIV) {
> +		dev_err(&pdev->dev, "Incorrect data polarity value\n");
> +		err = -EINVAL;
> +		goto out1;
> +	}
> +
> +	/* Set controller data */
> +	tdm->bus_num = pdev->id;
> +	tdm->settings = hw;
> +	tdm->setup_voice_channel = kirkwood_setup_voice_channel;
> +	tdm->recv = kirkwood_recv;
> +	tdm->send = kirkwood_send;
> +	tdm->run_audio = kirkwood_tdm_run_audio;
> +	tdm->stop_audio = kirkwood_tdm_stop_audio;
> +	tdm->poll_rx = kirkwood_poll_rx;
> +	tdm->poll_tx = kirkwood_poll_tx;
> +
> +	for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
> +	{
> +	        ch = tdm_alloc_voice_channel();
> +	        if (ch == NULL) {
> +	                dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
> +	                goto out1;
> +	        }


If you get partway through this loop before failing, then you
will leak a bunch of voice channels.

> +
> +            tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
> +	}
> +
> +
> +	/* Get resources(memory, IRQ) associated with the device */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (res == NULL) {
> +		dev_err(&pdev->dev, "Can`t request registers memory\n");
> +		err = -ENODEV;
> +		goto out2;
> +	}
> +
> +	onchip_tdm->regs = ioremap(res->start, resource_size(res));
> +	if (!onchip_tdm->regs) {
> +		dev_err(&pdev->dev, "Incorrect setup registers memory area\n");
> +		err = -EINVAL;
> +		goto out2;
> +	}
> +
> +	/* If need remap mbus windows */
> +	if (hw->dram != NULL)
> +		kirkwood_tdm_mbus_windows(onchip_tdm->regs, hw->dram);
> +
> +	/* Get IRQ number for all controllers events */
> +	onchip_tdm->irq = platform_get_irq(pdev, 0);
> +	if (onchip_tdm->irq < 0) {
> +		dev_err(&pdev->dev, "Can`t request IRQ\n");
> +		err = onchip_tdm->irq;
> +		goto out3;
> +	}
> +
> +	/* Set interrupt callback kirkwood_tdm_irq */
> +	err = request_irq(onchip_tdm->irq, kirkwood_tdm_irq, IRQF_DISABLED,
> +	                  dev_name(&pdev->dev), onchip_tdm);
> +	if (err) {
> +		dev_err(&pdev->dev, "Can`t setup IRQ callback\n");
> +		err = -EINVAL;
> +		goto out4;
> +	}


At this point you can now receive interrupts, but you do
kirkwood_tdm_hw_init below. Is it safe to get interrupted at this
point?

> +
> +	onchip_tdm->controller_dev = &tdm->dev;
> +
> +	/* Initialization TDM controller */
> +	err = kirkwood_tdm_hw_init(tdm);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "Can't initialization tdm controller, %d\n",
> +		        err);
> +		goto out4;
> +	}
> +
> +	err = tdm_controller_register(tdm);
> +	if(err) {
> +		dev_err(&pdev->dev, "cannot register tdm controller, %d\n", err);
> +		goto out5;
> +	}
> +
> +	printk("Init ok");


Remove.

> +
> +	dev_dbg(&tdm->dev, "tdm controller registred sucessfully\n");
> +	return 0;
> +
> +
> +out5:
> +	kirkwood_tdm_hw_deinit(tdm);
> +
> +out4:
> +	free_irq(onchip_tdm->irq, onchip_tdm);
> +
> +out3:
> +	if (onchip_tdm->regs)
> +		iounmap(onchip_tdm->regs);
> +
> +out2:
> +	tdm_free_voice_channels(tdm);
> +
> +out1:
> +	tdm_free_controller(tdm);
> +
> +out0:
> +	return err;
> +}
> +
> +
> +static int __exit kirkwood_tdm_remove(struct platform_device *pdev)


Don't mark as __exit.

> +{
> +	struct tdm_controller *tdm = NULL;
> +	struct kirkwood_tdm *onchip_tdm = NULL;
> +
> +	tdm = platform_get_drvdata(pdev);
> +	if (tdm == NULL)
> +		goto out0;
> +
> +	onchip_tdm = tdm_controller_get_devdata(tdm);
> +
> +	kirkwood_tdm_hw_deinit(tdm);
> +	free_irq(onchip_tdm->irq, onchip_tdm);
> +
> +	tdm_free_voice_channels(tdm);
> +	tdm_controller_unregister(tdm);
> +	if (onchip_tdm->regs)
> +		iounmap(onchip_tdm->regs);
> +
> +	tdm_free_controller(tdm);
> +out0:
> +	return 0;
> +}
> +
> +
> +static struct platform_driver kirkwood_tdm_driver = {
> +	.probe	= kirkwood_tdm_probe,
> +	.remove	= __exit_p(kirkwood_tdm_remove),
> +	.driver = {
> +		.name = "kirkwood_tdm",
> +		.owner = THIS_MODULE,
> +	},
> +	.suspend = NULL,
> +	.resume  = NULL,
> +};
> +
> +
> +static int __init kirkwood_init_tdm(void)
> +{
> +	return platform_driver_register(&kirkwood_tdm_driver);
> +}
> +
> +static void __exit kirkwood_exit_tdm(void)
> +{
> +	platform_driver_unregister(&kirkwood_tdm_driver);
> +}
> +
> +module_init(kirkwood_init_tdm);
> +module_exit(kirkwood_exit_tdm);
> +MODULE_AUTHOR("Michail Kurochkin <stelhs@yandex.ru>");
> +MODULE_DESCRIPTION("TDM controller driver for Marvel kirkwood arch.");
> +MODULE_LICENSE("GPL");
> +
> +
> diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
> new file mode 100644
> index 0000000..6c7f34d
> --- /dev/null
> +++ b/drivers/staging/tdm/kirkwood_tdm.h
> @@ -0,0 +1,110 @@
> +/*
> + * kirkwood_tdm.h
> + *
> + *  Created on: 25.01.2012
> + *      Author: Michail Kurochkin
> + */
> +
> +#ifndef KIRKWOOD_TDM_H_
> +#define KIRKWOOD_TDM_H_
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +
> +struct kirkwood_tdm_regs {
> +	u32 pcm_ctrl_reg; /*  0x0 PCM control register */
> +	u32 chan_time_slot_ctrl; /*  0x4 Channel Time Slot Control Register */
> +	u32 chan0_delay_ctrl; /*  0x8 Channel 0 Delay Control Register */
> +	u32 chan1_delay_ctrl; /*  0xC Channel 1 Delay Control Register */
> +	u32 chan0_enable_disable; /*  0x10 Channel 0 Enable and Disable Register */
> +	u32 chan0_buff_ownership; /*  0x14 Channel 0 Buffer Ownership Register */
> +	u32 chan0_transmit_start_addr; /*  0x18 Channel 0 Transmit Data Start Address Register */
> +	u32 chan0_receive_start_addr; /*  0x1C Channel 0 Receive Data Start Address Register */
> +	u32 chan1_enable_disable; /*  0x20 Channel 1 Enable and Disable Register */
> +	u32 chan1_buff_ownership; /*  0x24 Channel 1 Buffer Ownership Register */
> +	u32 chan1_transmit_start_addr; /*  0x28 Channel 1 Transmit Data Start Address Register */
> +	u32 chan1_receive_start_addr; /*  0x2C Channel 1 Receive Data Start Address Register */
> +	u32 chan0_total_sample; /*  0x30 Channel 0 Total Sample Count Register */
> +	u32 chan1_total_sample; /*  0x34 Channel 1 Total Sample Count Register */
> +	u32 num_time_slot; /*  0x38 Number of Time Slot Register */
> +	u32 tdm_pcm_clock_div; /*  0x3C TDM PCM Clock Rate Divisor Register */
> +	u32 int_event_mask; /*  0x40 Interrupt Event Mask Register */
> +	u32 reserved_44h; /*  0x44 */
> +	u32 int_status_mask; /*  0x48 Interrupt Status Mask Register */
> +	u32 int_reset_sel; /*  0x4C Interrupt Reset Selection Register */
> +	u32 int_status; /*  0x50 Interrupt Status Register */
> +	u32 dummy_rx_write;/*  0x54 Dummy Data for Dummy RX Write Register */
> +	u32 misc_control; /*  0x58 Miscellaneous Control Register */
> +	u32 reserved_5Ch; /*  0x5C */
> +	u32 chan0_current_tx_addr; /*  0x60 Channel 0 Transmit Data Current Address Register (for DMA) */
> +	u32 chan0_current_rx_addr;  /*  0x64 Channel 0 Receive Data Current Address Register (for DMA) */
> +	u32 chan1_current_tx_addr; /*  0x68 Channel 1 Transmit Data Current Address Register (for DMA) */
> +	u32 chan1_current_rx_addr; /*  0x6C Channel 1 Receive Data Current Address Register (for DMA) */
> +	u32 curr_time_slot; /*  0x70 Current Time Slot Register */
> +	u32 revision; /*  0x74 TDM Revision Register */
> +	u32 chan0_debug; /*  0x78 TDM Channel 0 Debug Register */
> +	u32 chan1_debug; /*  0x7C TDM Channel 1 Debug Register */
> +	u32 tdm_dma_abort_1; /*  0x80 TDM DMA Abort Register 1 */
> +	u32 tdm_dma_abort_2; /*  0x84 TDM DMA Abort Register 2 */
> +	u32 chan0_wideband_delay_ctrl; /*  0x88 TDM Channel 0 Wideband Delay Control Register */
> +	u32 chan1_wideband_delay_ctrl; /*  0x8C TDM Channel 1 Wideband Delay Control Register */
> +};
> +
> +
> +#define KIRKWOOD_MAX_VOICE_CHANNELS 2 /*  Max count hardware channels */
> +#define COUNT_DMA_BUFFERS_PER_CHANNEL 6 /*  Count of dma buffers for tx or rx path by one channel */
> +
> +/*
> + * Data specified for kirkwood hardware voice channel
> + */
> +struct kirkwood_tdm_voice {
> +	/* Transmitter and receiver buffers split to half.
> +	 * While first half buffer is filling by DMA controller,
> +	 * second half buffer is used by consumer and etc.
> +	*/
> +	u8 *tx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  transmitter voice buffers */
> +	u8 *rx_buf[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  receiver voice buffers pointer */
> +
> +	dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to tx_buf */
> +	dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to rx_buf */
> +	atomic_t write_tx_buf_num; /*  current writing transmit buffer number */
> +	atomic_t read_tx_buf_num; /*  current reading transmit buffer number */
> +	atomic_t write_rx_buf_num; /*  current writing receive buffer number */
> +	atomic_t read_rx_buf_num; /*  current reading receive buffer number */
> +};
> +
> +
> +/*
> + * Data specified for kirkwood tdm controller
> + */
> +struct kirkwood_tdm {
> +	struct kirkwood_tdm_regs *regs; /*  Registers for hardware TDM */
> +	u32			irq; /*  Irq number for all TDM operations */
> +
> +	struct kirkwood_tdm_voice voice_channels[KIRKWOOD_MAX_VOICE_CHANNELS];
> +	struct device *controller_dev;
> +};
> +
> +
> +#define TDM_WINDOW_CTRL(i)		(0x4030 + ((i) << 4))
> +#define TDM_WINDOW_BASE(i)	(0x4034 + ((i) << 4))
> +
> +
> +/* INT_STATUS_REG bits */
> +#define RX_OVERFLOW_BIT(ch)	(1<<(0+(ch)*2))
> +#define TX_UNDERFLOW_BIT(ch)	(1<<(1+((ch)*2)))
> +#define RX_BIT(ch)		(1<<(4+((ch)*2)))
> +#define TX_BIT(ch)		(1<<(5+((ch)*2)))
> +#define RX_IDLE_BIT(ch)		(1<<(8+((ch)*2)))
> +#define TX_IDLE_BIT(ch)		(1<<(9+((ch)*2)))
> +#define RX_FIFO_FULL(ch)	(1<<(12+((ch)*2)))
> +#define TX_FIFO_EMPTY(ch)	(1<<(13+((ch)*2)))
> +#define DMA_ABORT_BIT		(1<<16)
> +#define SLIC_INT_BIT		(1<<17)
> +#define TDM_INT_TX(ch)	(TX_UNDERFLOW_BIT(ch) | TX_BIT(ch))
> +#define TDM_INT_RX(ch)	(RX_OVERFLOW_BIT(ch) | RX_BIT(ch))
> +
> +
> +#endif /* KIRKWOOD_TDM_H_ */
> +
> +



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 05/11] staging: refactoring request/free voice channels
  2013-03-01 10:57   ` [PATCH v2 05/11] staging: refactoring request/free voice channels Michail Kurachkin
@ 2013-03-02 22:56     ` Ryan Mallon
  0 siblings, 0 replies; 17+ messages in thread
From: Ryan Mallon @ 2013-03-02 22:56 UTC (permalink / raw)
  To: Michail Kurachkin
  Cc: linux-kernel, Greg Kroah-Hartman, Kuten Ivan, benavi,
	Palstsiuk Viktar, Dmitriy Gorokh, Oliver Neukum

On 01/03/13 21:57, Michail Kurachkin wrote:

> From: Michail Kurochkin <michail.kurachkin@promwad.com>
> 
> Signed-off-by: Michail Kurochkin <michail.kurachkin@promwad.com>
> ---
>  drivers/staging/tdm/kirkwood_tdm.c |  162 +++++++++++++++++++++++++-----------
>  drivers/staging/tdm/kirkwood_tdm.h |   10 ++-
>  drivers/staging/tdm/tdm.h          |    5 +-
>  drivers/staging/tdm/tdm_core.c     |   65 +++++++--------
>  4 files changed, 154 insertions(+), 88 deletions(-)
> 
> diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
> index 4366d38..e4a9106 100644
> --- a/drivers/staging/tdm/kirkwood_tdm.c
> +++ b/drivers/staging/tdm/kirkwood_tdm.c
> @@ -75,7 +75,7 @@ static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
>  	u32 control_val = 0;
>  	u32 pclk_freq;
>  	unsigned long flags;
> -	spinlock_t lock;
> +	static spinlock_t lock;
>  	spin_lock_init(&lock);


Global spinlocks should be declared outside of functions using the
spinlock initialisers, eg:

	static DEFINE_SPINLOCK(lock_name);

Having a global spinlock just called 'lock' is not very informative.
I also can't see any new uses of this lock in this patch, so why
is it being made global, and what use was it in the first place?

>  
>  
> @@ -192,13 +192,14 @@ static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
>  }
>  
>  /**
> - * Setup hardware voice channel
> + * Initialization hardware voice channel
> + * for specify TDM device
> + * @param tdm_dev - specify TDM device
>   * @param ch - voice hardware channel
>   * @return 0 - OK
>   */
> -int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
> +static int kirkwood_init_voice_channel(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch)
>  {
> -	struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
>  	struct tdm_controller *tdm = tdm_dev->controller;
>  	struct tdm_controller_hw_settings *hw = tdm->settings;
>  	struct kirkwood_tdm *onchip_tdm =
> @@ -208,15 +209,15 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  	unsigned long timeout;
>  	u32 state;
>  	int i;
> -
> +	int rc = 0;
>  	u8 voice_num = ch->channel_num;
>  
>  	/* calculate buffer size */
>  	ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
>  
>  	/* Request timeslot for voice channel */
> -	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
> -	writeb(ch->tdm_channel, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
> +	writeb(tdm_dev->tdm_channel_num, (u8*)&regs->chan_time_slot_ctrl + 2 * voice_num);
> +	writeb(tdm_dev->tdm_channel_num, (u8*)&regs->chan_time_slot_ctrl + 1 + 2 * voice_num);
>  
>  	/* FIXME: move coherent_dma_mask to board specific */
>  	tdm_dev->dev.coherent_dma_mask = 0xffffffff;
> @@ -226,11 +227,12 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  		/* allocate memory for DMA receiver */
>  		onchip_ch->rx_buf[i] =
>  		        dma_alloc_coherent(&tdm_dev->dev,
> -		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
> +		        ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_KERNEL);


Why are refactors like this happening as a separate patch? Why not
just fold these changes into the first patch so that it is correct
from the beginning (and easier to review)?

>  
>  		if (onchip_ch->rx_buf == NULL) {
>  			dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
> -			return -ENOMEM;
> +			rc = -ENOMEM;
> +			goto out1;
>  		}
>  		memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
>  
> @@ -238,20 +240,16 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  		/* allocate memory for DMA transmitter */
>  		onchip_ch->tx_buf[i] =
>  		        dma_alloc_coherent(&tdm_dev->dev,
> -		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
> +		        ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_KERNEL);
>  
>  		if (onchip_ch->tx_buf == NULL) {
>  			dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
> -			return -ENOMEM;
> +			rc = -ENOMEM;
> +			goto out1;
>  		}
>  		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
>  	}
>  
> -	atomic_set(&onchip_ch->write_rx_buf_num, 0);
> -	atomic_set(&onchip_ch->write_tx_buf_num, 0);
> -	atomic_set(&onchip_ch->read_rx_buf_num, 0);
> -	atomic_set(&onchip_ch->read_tx_buf_num, 0);
> -
>  	/* Set length for DMA */
>  	writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
>  	       &regs->chan0_total_sample + voice_num);
> @@ -262,8 +260,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 0x100;
>  		if(time_after(jiffies, timeout)) {
>  			dev_err(ch->dev, "Can`t program DMA tx buffer\n");
> -			return -EBUSY;
> +			rc = -EBUSY;
> +			goto out1;
>  		}
> +		schedule();
>  	} while(state);
>  
>  	/* Set DMA buffers fo transmitter */
> @@ -277,8 +277,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  		state = readl(&regs->chan0_buff_ownership + 4 * voice_num) & 1;
>  		if(time_after(jiffies, timeout)) {
>  			dev_err(ch->dev, "Can`t program DMA rx buffer\n");
> -			return -EBUSY;
> +			rc = -EBUSY;
> +			goto out1;
>  		}
> +		schedule();
>  	} while(state);


Does the controller have an interrupt for notifying of a state change?
If so, you could turn this into a wait_event_timeout, and have the
interrupt handler issue the wakeup.

>  
>  	/* Set DMA buffers for receiver */
> @@ -286,11 +288,51 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
>  	    &regs->chan0_receive_start_addr + 4 * voice_num);
>  	writeb(0x1, (u8*)(&regs->chan0_buff_ownership + 4 * voice_num) );
>  
> -	return 0;
> +out0:
> +	return rc;
> +
> +out1:
> +	kirkwood_reset_voice_channel(tdm_dev);
> +	return rc;
>  }
>  
>  
>  /**
> + * Release and reset hardware voice channel
> + * free DMA buffers.
> + * @param tdm_dev - TDM device early requested voice channel
> + * @return 0 - OK
> + */
> +int kirkwood_release_voice_channel(struct tdm_device *tdm_dev)
> +{
> +	struct tdm_voice_channel* ch = tdm_dev->ch;
> +	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
> +	int i;
> +
> +	if (!tdm_dev->ch)
> +		return -ENODEV;
> +
> +	for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
> +	{
> +		if (onchip_ch->rx_buf[i])
> +		{
> +			dma_free_coherent(&ch->dev,
> +			        ch->buffer_len, onchip_ch->rx_buf[i], onchip_ch->rx_buff_phy + i);
> +			onchip_ch->rx_buf[i] = 0;
> +		}
> +
> +		if (onchip_ch->tx_buf[i])
> +		{
> +			dma_free_coherent(&ch->dev,
> +			        ch->buffer_len, onchip_ch->tx_buf[i], onchip_ch->tx_buff_phy + i);
> +			onchip_ch->tx_buf[i] = 0;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/**
>   * Run tdm transmitter and receiver
>   * @param tdm_dev - tdm device
>   * @return 0 - ok
> @@ -304,8 +346,8 @@ int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
>  	struct tdm_voice_channel *ch = tdm_dev->ch;
>  	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
>  
> -	memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
> -	memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
> +	for (i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
> +		memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
>  
>  	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) + 1);
>  	writeb(0x1, (u8 *)(&regs->chan0_enable_disable + 4 * ch->channel_num) );
> @@ -364,11 +406,18 @@ int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
>   */
>  int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
>  {
> -	if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
> -		return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);
> +	unsigned long flags;
> +	int latency;
> +
> +	spin_lock_irqsave(&onchip_ch->lock, flags);
> +	if (onchip_ch->read_tx_buf_num <= onchip_ch->write_tx_buf_num)
> +		latency = onchip_ch->write_tx_buf_num - onchip_ch->read_tx_buf_num;
>  	else
> -		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
> -		       + atomic_read(&onchip_ch->write_tx_buf_num);
> +		latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_tx_buf_num
> +		       + onchip_ch->write_tx_buf_num;
> +	spin_unlock_irqrestore(&onchip_ch->lock, flags);

You may need the caller to hold the lock instead. As soon as you
release the lock here, the values in the above calculation can
change, and so you may return a value which is no longer current.
Is that safe to do?

> +	return latency;
>  }
>  
>  
> @@ -379,11 +428,18 @@ int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
>   */
>  int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
>  {
> -	if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
> -		return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
> +	unsigned long flags;
> +	int latency;
> +
> +	spin_lock_irqsave(&onchip_ch->lock, flags);
> +	if (onchip_ch->read_rx_buf_num <= onchip_ch->write_rx_buf_num)
> +		latency = onchip_ch->write_rx_buf_num - onchip_ch->read_rx_buf_num;
>  	else
> -		return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
> -		       + atomic_read(&onchip_ch->write_rx_buf_num);
> +		latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_rx_buf_num
> +		       + onchip_ch->write_rx_buf_num;
> +	spin_unlock_irqrestore(&onchip_ch->lock, flags);
> +
> +	return latency;
>  }
>  
>  
> @@ -421,7 +477,7 @@ int kirkwood_poll_tx(struct tdm_device *tdm_dev)
>   * @param num - current buffer number
>   * @return next buffer number
>   */
> -static int inc_next_dma_buf_num(int num)
> +static inline int inc_next_dma_buf_num(int num)
>  {
>  	num ++;
>  	if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
> @@ -445,6 +501,7 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
>  {
>  	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
>  	int rc = 0;
> +	unsigned long flags;
>  
>  	rc = wait_event_interruptible(ch->tx_queue,
>  	                         get_tx_latency(onchip_ch) > 1);
> @@ -452,10 +509,12 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
>  	if (rc)
>  		return rc;
>  
> -	memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
> +	spin_lock_irqsave(&onchip_ch->lock, flags);
> +	memcpy(onchip_ch->tx_buf[onchip_ch->read_tx_buf_num], data,
>  	       ch->buffer_len);
> -	atomic_set(&onchip_ch->read_tx_buf_num,
> -		inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> +	onchip_ch->read_tx_buf_num =
> +		inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
> +	spin_unlock_irqrestore(&onchip_ch->lock, flags);
>  	return rc;
>  }
>  
> @@ -473,6 +532,7 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
>  {
>  	struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
>  	int rc = 0;
> +	unsigned long flags;
>  
>  	rc = wait_event_interruptible(ch->rx_queue,
>  	                         get_rx_latency(onchip_ch) > 1);
> @@ -480,10 +540,12 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
>  	if (rc)
>  		return rc;
>  
> -	memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
> +	spin_lock_irqsave(&onchip_ch->lock, flags);
> +	memcpy(data, onchip_ch->rx_buf[onchip_ch->read_rx_buf_num],
>  	       ch->buffer_len);
> -	atomic_set(&onchip_ch->read_rx_buf_num,
> -		   inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> +	onchip_ch->read_rx_buf_num =
> +		   inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
> +	spin_unlock_irqrestore(&onchip_ch->lock, flags);
>  	return rc;
>  }
>  
> @@ -595,11 +657,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
>  		switch(mode) {
>  		case IRQ_RECEIVE: {
>  			/* get next buffer number, and move write/read pointer */
> -			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
> -			atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
> -			if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
> -				atomic_set(&onchip_ch->read_rx_buf_num,
> -				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
> +			next_buf_num = inc_next_dma_buf_num(onchip_ch->write_rx_buf_num);
> +			onchip_ch->write_rx_buf_num = next_buf_num;
> +			if(next_buf_num == onchip_ch->read_rx_buf_num)
> +				onchip_ch->read_rx_buf_num =
> +				           inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
>  
>  			/* if receive overflow event */
>  			if (overflow) {
> @@ -631,11 +693,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
>  
>  		case IRQ_TRANSMIT: {
>  			/* get next buffer number, and move write/read pointer */
> -			next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
> -			atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
> -			if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
> -				atomic_set(&onchip_ch->read_tx_buf_num,
> -				           inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
> +			next_buf_num = inc_next_dma_buf_num(onchip_ch->write_tx_buf_num);
> +			onchip_ch->write_tx_buf_num=  next_buf_num;
> +			if(next_buf_num == onchip_ch->read_tx_buf_num)
> +				onchip_ch->read_tx_buf_num =
> +				           inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
>  
>  			/* if transmit overflow event */
>  			if (overflow) {
> @@ -777,7 +839,8 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
>  	/* Set controller data */
>  	tdm->bus_num = pdev->id;
>  	tdm->settings = hw;
> -	tdm->setup_voice_channel = kirkwood_setup_voice_channel;
> +	tdm->init_voice_channel = kirkwood_init_voice_channel;
> +	tdm->release_voice_channel = kirkwood_release_voice_channel;
>  	tdm->recv = kirkwood_recv;
>  	tdm->send = kirkwood_send;
>  	tdm->run_audio = kirkwood_tdm_run_audio;
> @@ -787,13 +850,16 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
>  
>  	for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
>  	{
> +			struct kirkwood_tdm_voice *oncip_ch = onchip_tdm->voice_channels + i;
> +
> +	        spin_lock_init(&oncip_ch->lock);
>  	        ch = tdm_alloc_voice_channel();
>  	        if (ch == NULL) {
>  	                dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
>  	                goto out1;
>  	        }
>  
> -            tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
> +            tdm_register_new_voice_channel(tdm, ch, (void *)oncip_ch);
>  	}
>  
>  
> diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
> index 6c7f34d..fe7aadf 100644
> --- a/drivers/staging/tdm/kirkwood_tdm.h
> +++ b/drivers/staging/tdm/kirkwood_tdm.h
> @@ -58,6 +58,8 @@ struct kirkwood_tdm_regs {
>   * Data specified for kirkwood hardware voice channel
>   */
>  struct kirkwood_tdm_voice {
> +	spinlock_t		lock;
> +
>  	/* Transmitter and receiver buffers split to half.
>  	 * While first half buffer is filling by DMA controller,
>  	 * second half buffer is used by consumer and etc.
> @@ -67,10 +69,10 @@ struct kirkwood_tdm_voice {
>  
>  	dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to tx_buf */
>  	dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /*  two physical pointers to rx_buf */
> -	atomic_t write_tx_buf_num; /*  current writing transmit buffer number */
> -	atomic_t read_tx_buf_num; /*  current reading transmit buffer number */
> -	atomic_t write_rx_buf_num; /*  current writing receive buffer number */
> -	atomic_t read_rx_buf_num; /*  current reading receive buffer number */
> +	u8 write_tx_buf_num; /*  current writing transmit buffer number */
> +	u8 read_tx_buf_num; /*  current reading transmit buffer number */
> +	u8 write_rx_buf_num; /*  current writing receive buffer number */
> +	u8 read_rx_buf_num; /*  current reading receive buffer number */
>  };
>  
>  
> diff --git a/drivers/staging/tdm/tdm.h b/drivers/staging/tdm/tdm.h
> index ee7b5bf..80c1460 100644
> --- a/drivers/staging/tdm/tdm.h
> +++ b/drivers/staging/tdm/tdm.h
> @@ -53,7 +53,7 @@ struct tdm_voice_channel {
>  	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 list_head list; /*  Union all tdm voice channels by one controller */


Whitespace fixes like this should definetly be folded into the
first patch.

>  
>  	struct device *dev; /*  device requested voice channel */
>  };
> @@ -106,7 +106,8 @@ struct tdm_controller {
>  	/*  TDM hardware settings */
>  	struct tdm_controller_hw_settings *settings;
>  
> -	int (*setup_voice_channel)(struct tdm_voice_channel *ch);
> +	int (*init_voice_channel)(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch);
> +	int (*release_voice_channel)(struct tdm_device *tdm_dev);
>  	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);
> diff --git a/drivers/staging/tdm/tdm_core.c b/drivers/staging/tdm/tdm_core.c
> index 0182a4f..f3ac6fc 100644
> --- a/drivers/staging/tdm/tdm_core.c
> +++ b/drivers/staging/tdm/tdm_core.c
> @@ -174,53 +174,64 @@ EXPORT_SYMBOL_GPL(tdm_unregister_driver);
>  
>  
>  /**
> - * Request unused voice channel
> + * Request and init 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;
> +	struct tdm_controller *tdm = tdm_dev->controller;
> +	struct tdm_voice_channel *ch;
> +	unsigned long flags;
> +	int rc;
>  
> -        spin_lock_irqsave(&tdm->lock, 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;
> +                        ch->mode_wideband = tdm_dev->mode_wideband;
> +                        ch->tdm_channel = tdm_dev->tdm_channel_num;
>                          tdm_dev->ch = ch;
>                          spin_unlock_irqrestore(&tdm->lock, flags);
> +                        rc = tdm->init_voice_channel(tdm_dev, ch);
> +                        if (rc)
> +                        {
> +                            dev_err(&tdm_dev->dev, "Can't init TDM voice channel\n");
> +                        	return NULL;
> +                        }
> +
>                          return ch;
>                  }
>  
> -        spin_unlock_irqrestore(&tdm->lock, flags);
> -        return NULL;
> +	spin_unlock_irqrestore(&tdm->lock, flags);
> +	return NULL;
>  }
>  
>  /**
> - * Release requested voice channel
> + * Release requested early 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;
> +	struct tdm_controller *tdm = tdm_dev->controller;
> +	struct tdm_voice_channel *ch;
> +	unsigned long flags;
>  
> -        spin_lock_irqsave(&tdm->lock, flags);
> +	spin_lock_irqsave(&tdm->lock, flags);
>  
> -        tdm_dev->ch = NULL;
> +	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;
> -                }
> +			if (ch->dev == &tdm_dev->dev) {
> +					tdm->release_voice_channel(tdm_dev);
> +					ch->dev = NULL;
> +					spin_unlock_irqrestore(&tdm->lock, flags);
> +					return 0;
> +			}
>  
> -        spin_unlock_irqrestore(&tdm->lock, flags);
> +	spin_unlock_irqrestore(&tdm->lock, flags);
>  
> -        return -ENODEV;
> +	return -ENODEV;
>  }
>  
>  
> @@ -326,7 +337,6 @@ struct tdm_voice_channel *tdm_alloc_voice_channel(void)
>          if (!ch)
>                  return NULL;
>  
> -        memset(ch, 0, sizeof *ch);
>          init_waitqueue_head(&ch->tx_queue);
>          init_waitqueue_head(&ch->rx_queue);
>  
> @@ -614,19 +624,6 @@ int tdm_add_device(struct tdm_device *tdm_dev)
>                  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)



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 01/11] staging: Initial commit of TDM core
  2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
  2013-03-01 22:54     ` Ryan Mallon
@ 2013-03-11 17:17     ` Greg Kroah-Hartman
  1 sibling, 0 replies; 17+ messages in thread
From: Greg Kroah-Hartman @ 2013-03-11 17:17 UTC (permalink / raw)
  To: Michail Kurachkin
  Cc: linux-kernel, Kuten Ivan, benavi, Palstsiuk Viktar,
	Dmitriy Gorokh, Oliver Neukum, Ryan Mallon

On Fri, Mar 01, 2013 at 01:50:37PM +0300, Michail Kurachkin wrote:
> From: Michail Kurochkin <michail.kurachkin@promwad.com>
> 
> Signed-off-by: Michail Kurochkin <michail.kurachkin@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"

You just broke the build with this patch :(

Please, EVERY patch has to be buildable at every step of the way.  I
can't take patches that break the build, especially ones that do so in
such obvious ways.

Please resend this whole series, fixing this problem, and changing the
 Subject: lines like I asked for last time as well.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2013-03-11 17:17 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-02-27 16:22 [PATCH 0/9] Support of TDM bus, TDM driver for Marvell Kirkwood and SLIC driver for Silabs Si3226x Michail Kurachkin
2013-03-01 10:42 ` [PATCH v2 00/11] staging: " Michail Kurachkin
2013-03-01 10:50   ` [PATCH v2 01/11] staging: Initial commit of TDM core Michail Kurachkin
2013-03-01 22:54     ` Ryan Mallon
2013-03-11 17:17     ` Greg Kroah-Hartman
2013-03-01 10:52   ` [PATCH v2 02/11] staging: Initial commit of Kirkwood TDM driver Michail Kurachkin
2013-03-01 23:54     ` Ryan Mallon
2013-03-01 10:54   ` [PATCH v2 03/11] staging: Initial commit of SLIC si3226x driver Michail Kurachkin
2013-03-01 10:56   ` [PATCH v2 04/11] staging: added TODO file for si3226x Michail Kurachkin
2013-03-01 10:57   ` [PATCH v2 05/11] staging: refactoring request/free voice channels Michail Kurachkin
2013-03-02 22:56     ` Ryan Mallon
2013-03-01 10:58   ` [PATCH v2 06/11] staging: remove device_attribute Michail Kurachkin
2013-03-01 11:00   ` [PATCH v2 07/11] staging: added issues description in TODO file Michail Kurachkin
2013-03-01 11:00   ` [PATCH v2 08/11] staging: removing of buffer filling flag and also reverting old buffer related fix which is not really effective Michail Kurachkin
2013-03-01 11:02   ` [PATCH v2 09/11] staging: fixed e-mail address Michail Kurachkin
2013-03-01 11:03   ` [PATCH v2 10/11] staging: add issuses in TODO Michail Kurachkin
2013-03-01 11:04   ` [PATCH v2 11/11] " Michail Kurachkin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).