aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
authorLouis LE GALL <louis.le.gall@intel.com>2010-12-09 10:37:40 +0000
committerAlan Cox <alan@linux.intel.com>2010-12-09 10:37:40 +0000
commit8b052df870fdf763e4acc9cb08fc9d1d2200b006 (patch)
tree610b6964db560858821c1c5ee38f82533ada7623 /sound
parent44c2514121c88ea77f8a754531b38fe4c3393598 (diff)
downloadmrst-s0i3-test-8b052df870fdf763e4acc9cb08fc9d1d2200b006.tar.gz
mrst-s0i3-test-8b052df870fdf763e4acc9cb08fc9d1d2200b006.tar.xz
mrst-s0i3-test-8b052df870fdf763e4acc9cb08fc9d1d2200b006.zip
This patch adds the I2S SSP common driver that supports I2S audio on
Intel MID platforms. It is used for sending/receiving I2S audio samples It support new DMA interface of kernel 2.6.37 Below is a simple diagram that explain where is the intel_mid_i2s in software architecture: +-----------+ | | |Pulse Audio| | | | | +-----------+ | | +------------+------------+ | | | | | | +-----------+ +-----------+ | | |Lib cmt | |Alsa Lib |------+ |speech |------+ | | | |data | | | | | | | | +-----------+ | +-----------+ | | | | | | | User ......|............|.........................|................................. | | | Kernel | | | | | | | | | | | | | | | | | | \|/ \|/ \|/ V V V +---------+ ++---------++ ++---------++ | | ||Alsa snd || ||cmt || |Intel SST| ||driver || ||speech || | "MAD" | ||SSP || ||driver || | | ||BT/FM || || || +---------+ ++---------++ ++---------++ | | | | | | | | | | | | \|/ | \|/ V | V +-----------+ | ++---------++ | | | \ ||intel mid|| |LPE Driver | +------------------>||i2s || | | / ||unified || | | ||driver || +-----------+ ++---------++ | | | | Kernel ......|......................................|................................. | | Hardware \|/ \|/ V V +-- --+ +-- --+ | | | | |MSIC | |SSP in I2S | | | |config | | | | | +-- --+ +-- --+ Signed-off-by: Louis LE GALL <louis.le.gall@intel.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/pci/Kconfig18
-rw-r--r--sound/pci/Makefile3
-rw-r--r--sound/pci/intel_mid_i2s/Makefile18
-rw-r--r--sound/pci/intel_mid_i2s/intel_mid_i2s.c1548
-rw-r--r--sound/pci/intel_mid_i2s/intel_mid_i2s.h502
5 files changed, 2088 insertions, 1 deletions
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 12e34653b8a..0b0a5245a89 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -853,4 +853,22 @@ config SND_YMFPCI
To compile this driver as a module, choose M here: the module
will be called snd-ymfpci.
+config SND_INTEL_MID_I2S
+ tristate "Intel mid I2S hardware driver"
+ depends on EXPERIMENTAL && PCI && INTEL_MID_DMAC
+ default n
+ help
+ Say Y here if you want to build low level driver to support
+ sending/receving I2S audio samples on Intel MID SSP device.
+ This interface is mostly used on Intel MID platforms and provide
+ the low level interface for some upper layer drivers such as
+ Alsa SoC, char device interfaces... depending of peripheral connected.
+ PCI Header should have ADID field set to I2S BT_FM or
+ I2S MODEM to be used by this driver (so it know connected peripheral).
+ Note this is a prototype driver and support for continuous
+ flow is still working-in-progress.
+ This driver can also be built as a module. If so, the module
+ will be called intel_mid_i2s.ko
+ If unsure, say N here.
+
endif # SND_PCI
diff --git a/sound/pci/Makefile b/sound/pci/Makefile
index 9cf4348ec13..ff2664afd20 100644
--- a/sound/pci/Makefile
+++ b/sound/pci/Makefile
@@ -78,4 +78,5 @@ obj-$(CONFIG_SND) += \
rme9652/ \
trident/ \
ymfpci/ \
- vx222/
+ vx222/ \
+ intel_mid_i2s/
diff --git a/sound/pci/intel_mid_i2s/Makefile b/sound/pci/intel_mid_i2s/Makefile
new file mode 100644
index 00000000000..7a24d98d045
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/Makefile
@@ -0,0 +1,18 @@
+# SSP I2S audio drivers for intel Mid platforms
+# Copyright (c) 2010, Intel Corporation.
+
+# This program is free software; you can redistribute it and/or modify it
+# under the terms and conditions of the GNU General Public License,
+# version 2, as published by the Free Software Foundation.
+
+# This program is distributed in the hope it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+
+obj-$(CONFIG_SND_INTEL_MID_I2S) += intel_mid_i2s.o
+
diff --git a/sound/pci/intel_mid_i2s/intel_mid_i2s.c b/sound/pci/intel_mid_i2s/intel_mid_i2s.c
new file mode 100644
index 00000000000..4de7c966b90
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_mid_i2s.c
@@ -0,0 +1,1548 @@
+/*
+ * <Driver for I2S protocol on SSP (Moorestown and Medfield hardware)>
+ * Copyright (c) 2010, Intel Corporation.
+ * Louis LE GALL <louis.le.gall intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without evenp the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/pci_regs.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include <linux/device.h>
+
+#include <linux/intel_mid_i2s_if.h>
+#include "intel_mid_i2s.h"
+
+MODULE_AUTHOR("Louis LE GALL <louis.le.gall intel.com>");
+MODULE_DESCRIPTION("Intel MID I2S/PCM SSP Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0.0");
+
+
+/*
+ * structures for pci probing
+ */
+#ifdef CONFIG_PM
+static const struct dev_pm_ops intel_mid_i2s_pm_ops = {
+ .runtime_suspend = intel_mid_i2s_runtime_suspend,
+ .runtime_resume = intel_mid_i2s_runtime_resume,
+};
+#endif
+static DEFINE_PCI_DEVICE_TABLE(pci_ids) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, MFLD_SSP1_DEVICE_ID) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, MFLD_SSP0_DEVICE_ID) },
+ { 0, }, /* terminate list */
+};
+static struct pci_driver intel_mid_i2s_driver = {
+#ifdef CONFIG_PM
+ .driver = {
+ .pm = &intel_mid_i2s_pm_ops,
+ },
+#endif
+ .name = DRIVER_NAME,
+ .id_table = pci_ids,
+ .probe = intel_mid_i2s_probe,
+ .remove = __devexit_p(intel_mid_i2s_remove),
+#ifdef CONFIG_PM
+ .suspend = intel_mid_i2s_driver_suspend,
+ .resume = intel_mid_i2s_driver_resume,
+#endif
+};
+
+
+/*
+ * POWER MANAGEMENT FUNCTIONS
+ */
+
+#ifdef CONFIG_PM
+/**
+ * intel_mid_i2s_driver_suspend - driver power management suspend activity
+ * @device_ptr : pointer of the device to resume
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+static int intel_mid_i2s_driver_suspend(struct pci_dev *dev, pm_message_t state)
+{
+ struct intel_mid_i2s_hdl *drv_data = pci_get_drvdata(dev);
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return 0;
+ dev_dbg(&drv_data->pdev->dev, "SUSPEND SSP ID %d\n", drv_data->pdev->device);
+ pci_save_state(dev);
+ pci_disable_device(dev);
+ pci_set_power_state(dev, PCI_D3hot);
+ return 0;
+}
+
+/**
+ * intel_mid_i2s_driver_resume - driver power management suspend activity
+ * @device_ptr : pointer of the device to resume
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+static int intel_mid_i2s_driver_resume(struct pci_dev *dev)
+{
+ int err;
+ int ret = 0;
+ struct intel_mid_i2s_hdl *drv_data = pci_get_drvdata(dev);
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ dev_dbg(&drv_data->pdev->dev, "RESUME SSP ID %d\n", drv_data->pdev->device);
+
+ err = pci_enable_device(dev);
+ if (err)
+ dev_err(&drv_data->pdev->dev, "Unable to re-enable device, trying to continue.\n");
+ dev_dbg(&drv_data->pdev->dev, "resuming\n");
+ pci_set_power_state(dev, PCI_D0);
+ pci_restore_state(dev);
+ ret = pci_enable_device(dev);
+ if (ret)
+ dev_err(&drv_data->pdev->dev, "I2S: device can't be enabled");
+ dev_dbg(&drv_data->pdev->dev, "resumed in D3\n");
+ return ret;
+}
+
+/**
+ * intel_mid_i2s_runtime_suspend - runtime power management suspend activity
+ * @device_ptr : pointer of the device to resume
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+static int intel_mid_i2s_runtime_suspend(struct device *device_ptr)
+{
+ struct pci_dev *pdev;
+ struct intel_mid_i2s_hdl *drv_data;
+ void __iomem *reg;
+
+ pdev = to_pci_dev(device_ptr);
+ WARN(!pdev, "Pci dev=NULL\n");
+ if (!pdev)
+ return -EFAULT;
+ drv_data = (struct intel_mid_i2s_hdl *) pci_get_drvdata(pdev);
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ if (test_bit(I2S_PORT_OPENED, &drv_data->flags)) {
+ dev_err(device_ptr, "Trying to suspend a device that is opened\n");
+ return -ENODEV;
+ }
+ reg = drv_data->ioaddr;
+ dev_dbg(&drv_data->pdev->dev, "Suspend of SSP requested !!\n");
+ return intel_mid_i2s_driver_suspend(to_pci_dev(device_ptr), PMSG_SUSPEND);
+}
+
+/**
+ * intel_mid_i2s_runtime_resume - runtime power management resume activity
+ * @device_ptr : pointer of the device to resume
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+static int intel_mid_i2s_runtime_resume(struct device *device_ptr)
+{
+ struct pci_dev *pdev;
+ struct intel_mid_i2s_hdl *drv_data;
+ pdev = to_pci_dev(device_ptr);
+ WARN(!pdev, "Pci dev=NULL\n");
+ if (!pdev)
+ return -EFAULT;
+ drv_data = (struct intel_mid_i2s_hdl *) pci_get_drvdata(pdev);
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ dev_dbg(&drv_data->pdev->dev, "RT RESUME SSP ID\n");
+ return intel_mid_i2s_driver_resume(to_pci_dev(device_ptr));
+}
+
+#endif
+/*
+ * INTERFACE FUNCTIONS
+ */
+
+/**
+ * intel_mid_i2s_flush - This is the I2S flush request
+ * @drv_data : pointer on private i2s driver data (by i2s_open function)
+ *
+ * It will flush the TX FIFO
+ * WARNING: this function is used in a Burst Mode context where it is called
+ * between Bursts i.e. when there is no FMSYNC, no transfer ongoing at
+ * that time
+ * If you need a flush while SSP configured in I2S is BUSY and FMSYNC are
+ * generated, you have to write another function
+ * (loop on BUSY bit and do not limit the flush to at most 16 samples)
+ *
+ * Output parameters
+ * int : number of samples flushed
+ */
+int intel_mid_i2s_flush(struct intel_mid_i2s_hdl *drv_data)
+{
+ u32 sssr, data;
+ u32 num = 0;
+ void __iomem *reg;
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return 0;
+ reg = drv_data->ioaddr;
+ sssr = read_SSSR(reg);
+ dev_dbg(&drv_data->pdev->dev, "in flush sssr=0x%08X\n", sssr);
+ /*
+ * Flush "by hand" was generating spurious DMA SERV REQUEST
+ * from SSP to DMA => then buggy retrieval of data for next dma_req
+ * Disable: RX Service Request from RX fifo to DMA
+ * as we will flush by hand
+ */
+ clear_SSCR1_reg(reg, RSRE);
+ /* i2s_flush is called in between 2 bursts
+ * => no FMSYNC at that time (i.e. SSP not busy)
+ * => at most 16 samples in the FIFO */
+ while ((read_SSSR(reg) & (SSSR_RNE_MASK<<SSSR_RNE_SHIFT))
+ && (num < FIFO_SIZE)) {
+ data = read_SSDR(reg);
+ num++;
+ dev_warn(&drv_data->pdev->dev, "flush(%u)=%u\n", num, data);
+ }
+ /* Enable: RX Service Request from RX fifo to DMA
+ * as flush by hand is done
+ */
+ set_SSCR1_reg(reg, RSRE);
+ sssr = read_SSSR(reg);
+ dev_dbg(&drv_data->pdev->dev, "out flush sssr=0x%08X\n", sssr);
+ return num;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_flush);
+
+/**
+ * intel_mid_i2s_get_rx_fifo_level - returns I2S rx fifo level
+ * @drv_data : pointer on private i2s driver data (by i2s_open function)
+ *
+ * Output parameters
+ * int : Number of samples currently in the RX FIFO (negative = error)
+ */
+int intel_mid_i2s_get_rx_fifo_level(struct intel_mid_i2s_hdl *drv_data)
+{
+ u32 sssr;
+ u32 rne, rfl;
+ void __iomem *reg;
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ reg = drv_data->ioaddr;
+ sssr = read_SSSR(reg);
+ rfl = GET_SSSR_val(sssr, RFL);
+ rne = GET_SSSR_val(sssr, RNE);
+ if (!rne)
+ return 0;
+ else
+ return rfl+1;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_get_rx_fifo_level);
+
+/**
+ * intel_mid_i2s_get_tx_fifo_level - returns I2S tx fifo level
+ * @drv_data : pointer on private i2s driver data (by i2s_open function)
+ *
+ * Output parameters
+ * int : number of samples currently in the TX FIFO (negative = error)
+ */
+int intel_mid_i2s_get_tx_fifo_level(struct intel_mid_i2s_hdl *drv_data)
+{
+ u32 sssr;
+ u32 tnf, tfl;
+ void __iomem *reg;
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ reg = drv_data->ioaddr;
+ sssr = read_SSSR(reg);
+ tfl = GET_SSSR_val(sssr, TFL);
+ tnf = GET_SSSR_val(sssr, TFN);
+ if (!tnf)
+ return 16;
+ else
+ return tfl;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_get_tx_fifo_level);
+
+/**
+ * intel_mid_i2s_set_rd_cb - set the callback function after read is done
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ * @read_callback : pointer of callback function
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+int intel_mid_i2s_set_rd_cb(struct intel_mid_i2s_hdl *drv_data, int (*read_callback)(void *param))
+{
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ mutex_lock(&drv_data->mutex);
+ if (!test_bit(I2S_PORT_OPENED, &drv_data->flags)) {
+ dev_WARN(&drv_data->pdev->dev, "set WR CB I2S_PORT NOT_OPENED");
+ mutex_unlock(&drv_data->mutex);
+ return -EPERM;
+ }
+ /* Do not change read parameters in the middle of a READ request */
+ if (test_bit(I2S_PORT_READ_BUSY, &drv_data->flags)) {
+ dev_WARN(&drv_data->pdev->dev, "CB reject I2S_PORT_READ_BUSY");
+ mutex_unlock(&drv_data->mutex);
+ return -EBUSY;
+ }
+ drv_data->read_callback = read_callback;
+ drv_data->read_len = 0;
+ mutex_unlock(&drv_data->mutex);
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_set_rd_cb);
+
+/**
+ * intel_mid_i2s_set_wr_cb - set the callback function after write is done
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ * @write_callback : pointer of callback function
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+int intel_mid_i2s_set_wr_cb(struct intel_mid_i2s_hdl *drv_data, int (*write_callback)(void *param))
+{
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ mutex_lock(&drv_data->mutex);
+ if (!test_bit(I2S_PORT_OPENED, &drv_data->flags)) {
+ dev_warn(&drv_data->pdev->dev, "set WR CB I2S_PORT NOT_OPENED");
+ mutex_unlock(&drv_data->mutex);
+ return -EPERM;
+ }
+ /* Do not change write parameters in the middle of a WRITE request */
+ if (test_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags)) {
+ dev_warn(&drv_data->pdev->dev, "CB reject I2S_PORT_WRITE_BUSY");
+ mutex_unlock(&drv_data->mutex);
+ return -EBUSY;
+ }
+ drv_data->write_callback = write_callback;
+ drv_data->write_len = 0;
+ mutex_unlock(&drv_data->mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_set_wr_cb);
+
+/**
+ * intel_mid_i2s_enable_ssp() : start the ssp right after the read/write req
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ *
+ * This enable the read/write to be started synchronously
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+int intel_mid_i2s_enable_ssp(struct intel_mid_i2s_hdl *drv_data)
+{
+ void __iomem *reg = drv_data->ioaddr;
+ set_SSCR0_reg(reg, SSE);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_enable_ssp);
+
+/**
+ * intel_mid_i2s_rd_req - request a read from i2s peripheral
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ * @dst : destination buffer where the read sample should be put
+ * @len : number of sample to be read (160 samples only right now)
+ * @param : private context parameter to give back to read callback
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+int intel_mid_i2s_rd_req(struct intel_mid_i2s_hdl *drv_data, u32 *destination, size_t len, void *param)
+{
+ struct dma_async_tx_descriptor *rxdesc = NULL;
+ struct dma_chan *rxchan = drv_data->rxchan;
+ enum dma_ctrl_flags flag;
+ dma_addr_t ssdr_addr;
+ dma_addr_t dst;
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ if (!rxchan) {
+ dev_WARN(&(drv_data->pdev->dev), "rd_req FAILED no rxchan\n");
+ return -EINVAL;
+ }
+ if (!len) {
+ dev_WARN(&drv_data->pdev->dev, "rd req invalid len=0");
+ return -EINVAL;
+ }
+
+ dev_dbg(&drv_data->pdev->dev, "I2S_READ() dst=%p, len=%d, drv_data=%p", destination, len, drv_data);
+ dst = dma_map_single(NULL, destination, len, DMA_FROM_DEVICE);
+ if (!dst) {
+ dev_WARN(&drv_data->pdev->dev, "can't map DMA address %p", destination);
+ return -ENOMEM;
+ }
+
+ drv_data->read_dst = dst;
+ drv_data->read_len = len;
+ /* get Data Read/Write address */
+ ssdr_addr = (drv_data->paddr + OFFSET_SSDR);
+ set_SSCR1_reg((drv_data->ioaddr), RSRE);
+ change_SSCR0_reg((drv_data->ioaddr), RIM,
+ ((drv_data->current_settings).rx_fifo_interrupt));
+ flag = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
+ /* Start the RX dma transfer */
+ rxdesc = rxchan->device->device_prep_dma_memcpy(
+ rxchan, /* DMA Channel */
+ dst, /* DAR */
+ ssdr_addr, /* SAR */
+ len, /* Data Length */
+ flag); /* Flag */
+ if (!rxdesc) {
+ dev_WARN(&drv_data->pdev->dev, "can not prep dma memcpy");
+ return -EFAULT;
+ }
+ /* Only 1 READ at a time allowed. do it at end to avoid clear&wakeup*/
+ if (test_and_set_bit(I2S_PORT_READ_BUSY, &drv_data->flags)) {
+ dma_unmap_single(NULL, dst, len, DMA_FROM_DEVICE);
+ dev_WARN(&drv_data->pdev->dev, "RD reject I2S_PORT READ_BUSY");
+ return -EBUSY;
+ }
+ dev_dbg(&(drv_data->pdev->dev), "RD dma tx submit\n");
+ rxdesc->callback = i2s_read_done;
+ drv_data->read_param = param;
+ rxdesc->callback_param = drv_data;
+ rxdesc->tx_submit(rxdesc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_rd_req);
+
+/**
+ * intel_mid_i2s_wr_req - request a write to i2s peripheral
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ * @src : source buffer where the samples to wrote should be get
+ * @len : number of sample to be read (160 samples only right now)
+ * @param : private context parameter to give back to write callback
+ *
+ * Output parameters
+ * error : 0 means no error
+ */
+int intel_mid_i2s_wr_req(struct intel_mid_i2s_hdl *drv_data, u32 *source, size_t len, void *param)
+{
+ dma_addr_t ssdr_addr;
+ struct dma_async_tx_descriptor *txdesc = NULL;
+ struct dma_chan *txchan = drv_data->txchan;
+ enum dma_ctrl_flags flag;
+ dma_addr_t src;
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return -EFAULT;
+ if (!txchan) {
+ dev_WARN(&(drv_data->pdev->dev), "wr_req but no txchan\n");
+ return -EINVAL;
+ }
+ if (!len) {
+ dev_WARN(&drv_data->pdev->dev, "invalid len 0");
+ return -EINVAL;
+ }
+
+ dev_dbg(&drv_data->pdev->dev, "I2S_WRITE() src=%p, len=%d, drv_data=%p", source, len, drv_data);
+
+ src = dma_map_single(NULL, source, len, DMA_TO_DEVICE);
+ if (!src) {
+ dev_WARN(&drv_data->pdev->dev, "can't map DMA address %p", source);
+ return -EFAULT;
+ }
+ drv_data->write_src = src;
+ drv_data->write_len = len;
+ /* get Data Read/Write address */
+ ssdr_addr = (dma_addr_t)(u32)(drv_data->paddr + OFFSET_SSDR);
+ set_SSCR1_reg((drv_data->ioaddr), TSRE);
+ change_SSCR0_reg((drv_data->ioaddr), TIM,
+ ((drv_data->current_settings).tx_fifo_interrupt));
+ flag = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
+ txdesc = txchan->device->device_prep_dma_memcpy(
+ txchan, /* DMA Channel */
+ ssdr_addr, /* DAR */
+ src, /* SAR */
+ len, /* Data Length */
+ flag); /* Flag */
+ if (!txdesc) {
+ dev_WARN(&(drv_data->pdev->dev),
+ "wr_req dma memcpy FAILED(src=%08x,len=%d,txchan=%p)\n",
+ (unsigned int) src, len, txchan);
+ return -1;
+ }
+ dev_dbg(&(drv_data->pdev->dev), "WR dma tx summit\n");
+ /* Only 1 WRITE at a time allowed */
+ if (test_and_set_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags)) {
+ dma_unmap_single(NULL, src, len, DMA_TO_DEVICE);
+ dev_WARN(&drv_data->pdev->dev, "WR reject I2S_PORT WRITE_BUSY");
+ return -EBUSY;
+ }
+ txdesc->callback = i2s_write_done;
+ drv_data->write_param = param;
+ txdesc->callback_param = drv_data;
+ txdesc->tx_submit(txdesc);
+ dev_dbg(&(drv_data->pdev->dev), "wr dma req programmed\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_wr_req);
+
+/**
+ * intel_mid_i2s_open - reserve and start a SSP depending of it's usage
+ * @usage : select which ssp i2s you need by giving usage (BT,MODEM...)
+ * @ps_settings : hardware settings to configure the SSP module
+ *
+ * May sleep (driver_find_device) : no lock permitted when called.
+ *
+ * Output parameters
+ * handle : handle of the selected SSP, or NULL if not found
+ */
+struct intel_mid_i2s_hdl *intel_mid_i2s_open(enum intel_mid_i2s_ssp_usage usage,
+ const struct intel_mid_i2s_settings *ps_settings)
+{
+ struct pci_dev *pdev;
+ struct intel_mid_i2s_hdl *drv_data = NULL;
+ struct device *found_device = NULL;
+ pr_debug("%s : open called,searching for device with usage=%x !\n", DRIVER_NAME, usage);
+ found_device = driver_find_device(&(intel_mid_i2s_driver.driver), NULL, &usage, check_device);
+ if (!found_device) {
+ pr_debug("%s : open can not found with usage=0x%02X\n", DRIVER_NAME, (int)usage);
+ return NULL;
+ }
+ pdev = to_pci_dev(found_device);
+ drv_data = pci_get_drvdata(pdev);
+ drv_data->current_settings = *ps_settings;
+
+ if (!drv_data) {
+ dev_err(found_device, "no drv_data in open pdev=%p\n", pdev);
+ put_device(found_device);
+ return NULL;
+ }
+ mutex_lock(&drv_data->mutex);
+ dev_dbg(found_device, "Open found pdevice=0x%p\n", pdev);
+ /* pm_runtime */
+ pm_runtime_get_sync(&drv_data->pdev->dev);
+ /* dmac1 is already set during probe */
+ if (i2s_dma_start(drv_data) != 0) {
+ dev_err(found_device, "Can not start DMA\n");
+ goto open_error;
+ }
+ /* if we restart after stop without suspend, we start ssp faster */
+ set_ssp_i2s_hw(drv_data, ps_settings);
+
+ drv_data->mask_sr = ((SSSR_BCE_MASK << SSSR_BCE_SHIFT) |
+ (SSSR_EOC_MASK << SSSR_EOC_SHIFT) |
+ (SSSR_ROR_MASK << SSSR_ROR_SHIFT) |
+ (SSSR_TUR_MASK << SSSR_TUR_SHIFT) |
+ (SSSR_TINT_MASK << SSSR_TINT_SHIFT) |
+ (SSSR_PINT_MASK << SSSR_PINT_SHIFT));
+ if (test_bit(I2S_PORT_CLOSING, &drv_data->flags)) {
+ dev_err(&drv_data->pdev->dev, "Opening a closing I2S!");
+ goto open_error;
+ }
+ /* there is no need to "wake up" as we can not close an opening i2s */
+ clear_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags);
+ clear_bit(I2S_PORT_READ_BUSY, &drv_data->flags);
+ mutex_unlock(&drv_data->mutex);
+ return drv_data;
+
+open_error:
+ put_device(found_device);
+ mutex_unlock(&drv_data->mutex);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_open);
+
+/**
+ * intel_mid_i2s_close - release and stop the SSP
+ * @drv_data : handle of corresponding ssp i2s (given by i2s_open function)
+ *
+ * WARNING: This is not -yet- allowed to call close from a read/write callback !
+ *
+ * Output parameters
+ * none
+ */
+void intel_mid_i2s_close(struct intel_mid_i2s_hdl *drv_data)
+{
+ void __iomem *reg;
+ struct intel_mid_i2s_settings *ssp_settings = &(drv_data->current_settings);
+ int s;
+ struct dma_chan *channel;
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return;
+ mutex_lock(&drv_data->mutex);
+ if (!test_bit(I2S_PORT_OPENED, &drv_data->flags)) {
+ dev_err(&drv_data->pdev->dev, "not opened but closing?");
+ mutex_unlock(&drv_data->mutex);
+ return;
+ }
+
+ set_bit(I2S_PORT_CLOSING, &drv_data->flags);
+ dev_info(&drv_data->pdev->dev, "Status bit pending write=%d read=%d\n",
+ test_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags),
+ test_bit(I2S_PORT_READ_BUSY, &drv_data->flags));
+ if (test_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags) ||
+ test_bit(I2S_PORT_READ_BUSY, &drv_data->flags)) {
+ dev_info(&drv_data->pdev->dev, "Pending callback in close...\n");
+ }
+
+ if (ssp_settings->ssp_active_tx_slots_map) {
+ channel = drv_data->txchan;
+ s = channel->device->device_control(channel, DMA_TERMINATE_ALL, 0);
+ dev_info(&drv_data->pdev->dev, "DMA_TERMINATE tx=%d\n", s);
+ }
+ if (ssp_settings->ssp_active_rx_slots_map) {
+ channel = drv_data->rxchan;
+ s = channel->device->device_control(channel, DMA_TERMINATE_ALL, 0);
+ dev_info(&drv_data->pdev->dev, "DMA_TERMINATE rx=%d\n", s);
+ }
+
+ /* release DMA Channel.. */
+ i2s_dma_stop(drv_data);
+ reg = drv_data->ioaddr;
+ dev_dbg(&drv_data->pdev->dev, "Stopping the SSP\n");
+ i2s_ssp_stop(drv_data);
+ put_device(&drv_data->pdev->dev);
+ write_SSCR0(0, reg);
+
+ dev_dbg(&(drv_data->pdev->dev), "SSP Stopped.\n");
+ clear_bit(I2S_PORT_CLOSING, &drv_data->flags);
+ clear_bit(I2S_PORT_OPENED, &drv_data->flags);
+
+ /* pm runtime */
+ pm_runtime_put(&drv_data->pdev->dev);
+
+ mutex_unlock(&drv_data->mutex);
+}
+EXPORT_SYMBOL_GPL(intel_mid_i2s_close);
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+/**
+ * check_device - return if the device is the usage we want (usage =*data)
+ * @device_ptr : pointer on device struct
+ * @data : pointer pointer on usage we are looking for
+ *
+ * this is called for each device by find_device() from intel_mid_i2s_open()
+ * Info : when found, the flag of driver is set to I2S_PORT_OPENED
+ *
+ * Output parameters
+ * integer : return 0 means not the device or already started. go next
+ * return != 0 means stop the search and return this device
+ */
+static int
+check_device(struct device *device_ptr, void *data)
+{
+ struct pci_dev *pdev;
+ struct intel_mid_i2s_hdl *drv_data;
+ enum intel_mid_i2s_ssp_usage usage;
+ enum intel_mid_i2s_ssp_usage usage_to_find;
+
+ pdev = to_pci_dev(device_ptr);
+ WARN(!pdev, "Pci device=NULL\n");
+ if (!pdev)
+ return 0;
+ drv_data = (struct intel_mid_i2s_hdl *) pci_get_drvdata(pdev);
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return 0;
+ dev_dbg(&(pdev->dev), "Check device pci_dev ptr = 0X%p\n", pdev);
+ usage_to_find = *((enum intel_mid_i2s_ssp_usage *) data);
+ usage = drv_data->usage;
+
+ /* Can be done in one "if" but separated in purpose : take care of
+ * test_and_set_bit that need to be done AFTER the check on usage.
+ */
+ if (usage == usage_to_find) {
+ if (!test_and_set_bit(I2S_PORT_OPENED, &drv_data->flags))
+ return 1; /* Already opened, do not use this result */
+ };
+ return 0; /* not usage we look for, or already opened */
+}
+
+/**
+ * i2s_read_done - callback from the _dma tasklet_ after read
+ * @arg : void pointer to that should be driver data (context)
+ *
+ * Output parameters
+ * none
+ */
+static void i2s_read_done(void *arg)
+{
+ int status = 0;
+
+ struct intel_mid_i2s_hdl *drv_data = arg;
+ void *param_complete;
+ void __iomem *reg ;
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return;
+ if (!test_bit(I2S_PORT_READ_BUSY, &drv_data->flags))
+ dev_WARN(&drv_data->pdev->dev, "spurious read dma complete");
+
+ dma_unmap_single(NULL, drv_data->read_dst,
+ drv_data->read_len, DMA_FROM_DEVICE);
+ drv_data->read_len = 0;
+ reg = drv_data->ioaddr;
+ /* Rx fifo overrun Interrupt */
+ change_SSCR0_reg(reg, RIM, SSP_RX_FIFO_OVER_INT_DISABLE);
+ param_complete = drv_data->read_param;
+ /* Do not change order sequence:
+ * READ_BUSY clear, then test PORT_CLOSING
+ * wakeup for close() function
+ */
+ clear_bit(I2S_PORT_READ_BUSY, &drv_data->flags);
+ wake_up(&drv_data->wq_chan_closing);
+ if (test_bit(I2S_PORT_CLOSING, &drv_data->flags))
+ return;
+ if (drv_data->read_callback != NULL)
+ status = drv_data->read_callback(param_complete);
+ else
+ dev_warn(&drv_data->pdev->dev, "RD done but not callback set");
+
+}
+
+/**
+ * i2s_write_done() : callback from the _dma tasklet_ after write
+ * @arg : void pointer to that should be driver data (context)
+ *
+ * Output parameters
+ * none
+ */
+static void i2s_write_done(void *arg)
+{
+ int status = 0;
+ void *param_complete;
+ struct intel_mid_i2s_hdl *drv_data = arg;
+ void __iomem *reg ;
+
+ WARN(!drv_data, "Driver data=NULL\n");
+ if (!drv_data)
+ return;
+ if (!test_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags))
+ dev_warn(&drv_data->pdev->dev, "spurious write dma complete");
+ dma_unmap_single(NULL, drv_data->read_dst,
+ drv_data->read_len, DMA_TO_DEVICE);
+ drv_data->read_len = 0;
+ reg = drv_data->ioaddr;
+ change_SSCR0_reg(reg, TIM, SSP_TX_FIFO_UNDER_INT_DISABLE);
+ dev_dbg(&(drv_data->pdev->dev), "DMA channel disable..\n");
+ param_complete = drv_data->write_param;
+ /* Do not change order sequence:
+ * WRITE_BUSY clear, then test PORT_CLOSING
+ * wakeup for close() function
+ */
+ clear_bit(I2S_PORT_WRITE_BUSY, &drv_data->flags);
+ wake_up(&drv_data->wq_chan_closing);
+ if (test_bit(I2S_PORT_CLOSING, &drv_data->flags))
+ return;
+ if (drv_data->write_callback != NULL)
+ status = drv_data->write_callback(param_complete);
+ else
+ dev_warn(&drv_data->pdev->dev, "WR done but no callback set");
+}
+
+static bool chan_filter(struct dma_chan *chan, void *param)
+{
+ struct intel_mid_i2s_hdl *drv_data = (struct intel_mid_i2s_hdl *)param;
+ bool ret = false;
+
+ if (!drv_data->dmac1)
+ goto out;
+ if (chan->device->dev == &drv_data->dmac1->dev)
+ ret = true;
+out:
+ return ret;
+}
+static int i2s_compute_dma_width(u16 ssp_data_size, enum dma_slave_buswidth *dma_width)
+{
+ if (ssp_data_size <= 8)
+ *dma_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ else if (ssp_data_size <= 16)
+ *dma_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ else if (ssp_data_size <= 32)
+ *dma_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ else
+ return -EINVAL;
+
+
+ return 0;
+}
+static int i2s_compute_dma_msize(u8 ssp_threshold, enum intel_mid_dma_msize *dma_msize)
+{
+
+ switch (ssp_threshold) {
+ case 1:
+ *dma_msize = LNW_DMA_MSIZE_1;
+ break;
+ case 4:
+ *dma_msize = LNW_DMA_MSIZE_4;
+ break;
+ case 8:
+ *dma_msize = LNW_DMA_MSIZE_8;
+ break;
+ case 16:
+ *dma_msize = LNW_DMA_MSIZE_16;
+ break;
+ case 32:
+ *dma_msize = LNW_DMA_MSIZE_32;
+ break;
+ case 64:
+ *dma_msize = LNW_DMA_MSIZE_64;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+/**
+ * i2s_dma_start - prepare and reserve dma channels
+ * @arg : intel_mid_i2s_hdl pointer to that should be driver data (context)
+ *
+ * "ssp open" context and dmac1 should already be filled in drv_data
+ *
+ * Output parameters
+ * int : should be zero, else it means error code
+ */
+static int i2s_dma_start(struct intel_mid_i2s_hdl *drv_data)
+{
+ struct intel_mid_dma_slave *rxs, *txs;
+ struct pci_dev *l_pdev;
+ struct intel_mid_i2s_settings *ssp_settings = &(drv_data->current_settings);
+ dma_cap_mask_t mask;
+ int retval = 0;
+ int temp = 0;
+
+ dev_dbg(&drv_data->pdev->dev, "DMAC1 start\n");
+ drv_data->txchan = NULL;
+ drv_data->rxchan = NULL;
+ l_pdev = drv_data->pdev;
+
+ if (ssp_settings->ssp_active_rx_slots_map) {
+ /* 1. init rx channel */
+ rxs = &drv_data->dmas_rx;
+ rxs->dma_slave.direction = DMA_FROM_DEVICE;
+ rxs->hs_mode = LNW_DMA_HW_HS;
+ rxs->cfg_mode = LNW_DMA_PER_TO_MEM;
+ temp = i2s_compute_dma_width(ssp_settings->data_size, &rxs->dma_slave.src_addr_width);
+
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "RX DMA Channel Bad data_size = %d\n",
+ ssp_settings->data_size);
+ retval = -1;
+ goto err_exit;
+
+ }
+ rxs->dma_slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ temp = i2s_compute_dma_msize(ssp_settings->ssp_rx_fifo_threshold, &rxs->dma_slave.src_maxburst);
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "RX DMA Channel Bad RX FIFO Threshold src= %d\n",
+ ssp_settings->ssp_rx_fifo_threshold);
+ retval = -2;
+ goto err_exit;
+
+ }
+
+ temp = i2s_compute_dma_msize(ssp_settings->ssp_rx_fifo_threshold, &rxs->dma_slave.dst_maxburst);
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "RX DMA Channel Bad RX FIFO Threshold dst= %d\n",
+ ssp_settings->ssp_rx_fifo_threshold);
+ retval = -3;
+ goto err_exit;
+
+ }
+
+ rxs->device_instance = drv_data->device_instance;
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ drv_data->rxchan = dma_request_channel(mask, chan_filter, drv_data);
+ if (!drv_data->rxchan) {
+ dev_err(&(drv_data->pdev->dev),
+ "Could not get Rx channel\n");
+ retval = -4;
+ goto err_exit;
+ }
+
+ temp = drv_data->rxchan->device->device_control(drv_data->rxchan, DMA_SLAVE_CONFIG, (unsigned long) &rxs->dma_slave);
+ if (temp) {
+ dev_err(&(drv_data->pdev->dev),
+ "Rx slave control failed\n");
+ retval = -5;
+ goto err_exit;
+ }
+
+ }
+
+ if (ssp_settings->ssp_active_tx_slots_map) {
+ /* 2. init tx channel */
+ txs = &drv_data->dmas_tx;
+ txs->dma_slave.direction = DMA_TO_DEVICE;
+ txs->hs_mode = LNW_DMA_HW_HS;
+ txs->cfg_mode = LNW_DMA_MEM_TO_PER;
+
+ txs->dma_slave.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ temp = i2s_compute_dma_width(ssp_settings->data_size, &txs->dma_slave.dst_addr_width);
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "TX DMA Channel Bad data_size = %d\n",
+ ssp_settings->data_size);
+ retval = -6;
+ goto err_exit;
+
+ }
+
+ temp = i2s_compute_dma_msize(ssp_settings->ssp_tx_fifo_threshold+1, &txs->dma_slave.src_maxburst);
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "TX DMA Channel Bad TX FIFO Threshold src= %d\n",
+ ssp_settings->ssp_tx_fifo_threshold);
+ retval = -7;
+ goto err_exit;
+
+ }
+
+ temp = i2s_compute_dma_msize(ssp_settings->ssp_tx_fifo_threshold+1, &txs->dma_slave.dst_maxburst);
+ if (temp != 0) {
+ dev_err(&(drv_data->pdev->dev),
+ "TX DMA Channel Bad TX FIFO Threshold dst= %d\n",
+ ssp_settings->ssp_tx_fifo_threshold);
+ retval = -8;
+ goto err_exit;
+
+ }
+
+ txs->device_instance = drv_data->device_instance;
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+ drv_data->txchan = dma_request_channel(mask, chan_filter, drv_data);
+
+ if (!drv_data->txchan) {
+ dev_err(&(drv_data->pdev->dev),
+ "Could not get Tx channel\n");
+ retval = -10;
+ goto err_exit;
+ }
+
+ temp = drv_data->txchan->device->device_control(drv_data->txchan, DMA_SLAVE_CONFIG, (unsigned long) &txs->dma_slave);
+ if (temp) {
+ dev_err(&(drv_data->pdev->dev),
+ "Tx slave control failed\n");
+ retval = -9;
+ goto err_exit;
+ }
+ }
+
+ return retval;
+
+err_exit:
+ if (drv_data->txchan)
+ dma_release_channel(drv_data->txchan);
+ if (drv_data->rxchan)
+ dma_release_channel(drv_data->rxchan);
+ drv_data->rxchan = NULL;
+ drv_data->txchan = NULL;
+ return retval;
+}
+
+/**
+ * i2s_dma_stop - release dma channels
+ * @arg : struct intel_mid_i2s_hdl pointer to that should be driver data (context)
+ *
+ * called by intel_mid_i2s_close() context
+ *
+ * Output parameters
+ * none
+ */
+static void i2s_dma_stop(struct intel_mid_i2s_hdl *drv_data)
+{
+ struct intel_mid_i2s_settings *ssp_settings = &(drv_data->current_settings);
+
+ dev_dbg(&drv_data->pdev->dev, "DMAC1 stop\n");
+ if (ssp_settings->ssp_active_rx_slots_map)
+ dma_release_channel(drv_data->rxchan);
+ if (ssp_settings->ssp_active_tx_slots_map)
+ dma_release_channel(drv_data->txchan);
+
+}
+
+static void i2s_ssp_stop(struct intel_mid_i2s_hdl *drv_data)
+{
+ void __iomem *reg = drv_data->ioaddr;
+ dev_dbg(&drv_data->pdev->dev, "Stop SSP\n");
+ clear_SSCR0_reg(reg, SSE);
+}
+
+static void ssp1_dump_registers(struct intel_mid_i2s_hdl *drv_data)
+{
+ u32 irq_status;
+ void __iomem *reg = drv_data->ioaddr;
+ struct device *ddbg = &(drv_data->pdev->dev);
+ u32 status;
+ irq_status = read_SSSR(reg);
+ dev_dbg(ddbg, "dump SSSR=0x%08X\n", irq_status);
+ status = read_SSCR0(reg);
+ dev_dbg(ddbg, "dump SSCR0=0x%08X\n", status);
+ status = read_SSCR1(reg);
+ dev_dbg(ddbg, "dump SSCR1=0x%08X\n", status);
+ status = read_SSPSP(reg);
+ dev_dbg(ddbg, "dump SSPSP=0x%08X\n", status);
+ status = read_SSTSA(reg);
+ dev_dbg(ddbg, "dump SSTSA=0x%08X\n", status);
+ status = read_SSRSA(reg);
+ dev_dbg(ddbg, "dump SSRSA=0x%08X\n", status);
+ status = read_SSTO(reg);
+ dev_dbg(ddbg, "dump SSTO=0x%08X\n", status);
+ status = read_SSITR(reg);
+ dev_dbg(ddbg, "dump SSITR=0x%08X\n", status);
+ status = read_SSTSS(reg);
+ dev_dbg(ddbg, "dump SSTSS=0x%08X\n", status);
+ status = read_SSACD(reg);
+ dev_dbg(ddbg, "dump SSACD=0x%08X\n", status);
+}
+
+/**
+ * i2s_int(): function that handles the SSP Interrupts (errors)
+ * @irq : IRQ Number
+ * @dev_id : structure that contains driver information
+ *
+ * This interrupts do nothing but warnings in case there is some problems
+ * in I2S connection (underruns, overruns...). This may be reported by adding a
+ * new interface to the driver, but not yet requested by "users" of this driver
+ *
+ * Output parameters
+ * NA
+ */
+static irqreturn_t i2s_int(int irq, void *dev_id)
+{
+ struct intel_mid_i2s_hdl *drv_data = dev_id;
+ void __iomem *reg;
+ u32 irq_status = 0;
+ u32 mask_status = 0;
+ struct device *ddbg = &(drv_data->pdev->dev);
+ reg = drv_data->ioaddr;
+ irq_status = read_SSSR(reg);
+
+
+ if (ddbg->power.status!=DPM_ON)
+ return IRQ_NONE;
+#ifdef CONFIG_PM_RUNTIME
+ if (ddbg->power.runtime_status!=RPM_ACTIVE)
+ return IRQ_NONE;
+#endif
+
+ if (!(irq_status & (drv_data->mask_sr))) {
+ return IRQ_NONE;
+ } else {
+ /* may be improved by using a tasklet to send the error
+ * (underrun,...) to client by using callback
+ */
+ if (irq_status & (SSSR_ROR_MASK << SSSR_ROR_SHIFT)) {
+ dev_warn(ddbg,
+ "ssp_int RX FIFO OVER RUN SSSR=0x%08X\n",
+ irq_status);
+ mask_status |= (SSSR_ROR_MASK << SSSR_ROR_SHIFT);
+
+ }
+ if (irq_status & (SSSR_TUR_MASK << SSSR_TUR_SHIFT)) {
+ dev_warn(ddbg,
+ "ssp_int TX FIFO UNDER RUN SSSR=0x%08X\n",
+ irq_status);
+ mask_status |= (SSSR_TUR_MASK << SSSR_TUR_SHIFT);
+
+ }
+ if (irq_status & (SSSR_TINT_MASK << SSSR_TINT_SHIFT)) {
+ dev_warn(ddbg,
+ "ssp_int RX TIME OUT SSSR=0x%08X\n",
+ irq_status);
+ mask_status |= (SSSR_TINT_MASK << SSSR_TINT_SHIFT);
+
+ }
+ if (irq_status & (SSSR_PINT_MASK << SSSR_PINT_SHIFT)) {
+ dev_warn(ddbg,
+ "ssp_int TRAILING BYTE SSSR=0x%08X\n",
+ irq_status);
+ mask_status |= (SSSR_PINT_MASK << SSSR_PINT_SHIFT);
+ }
+ if (irq_status & (SSSR_EOC_MASK << SSSR_EOC_SHIFT)) {
+ dev_warn(ddbg,
+ "ssp_int END OF CHAIN SSSR=0x%08X\n",
+ irq_status);
+ mask_status |= (SSSR_EOC_MASK << SSSR_EOC_SHIFT);
+ }
+ /* clear sticky bits */
+ write_SSSR((irq_status & mask_status), reg);
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * calculate_sspsp_psp - separate function that calculate sspsp register
+ * @ps_settings : pointer of the settings struct
+ *
+ * this function is to simplify/clarify set_ssp_i2s_hw function
+ *
+ *
+ * Output parameters
+ * u32 : calculated SSPSP register
+ */
+u32 calculate_sspsp_psp(const struct intel_mid_i2s_settings *ps_settings)
+{
+ u32 sspsp;
+ sspsp = SSPSP_reg(FSRT, ps_settings->ssp_frmsync_timing_bit)
+ |SSPSP_reg(ETDS, ps_settings->ssp_end_transfer_state)
+ |SSPSP_reg(SCMODE, ps_settings->ssp_serial_clk_mode)
+ |SSPSP_reg(DMYSTOP, ps_settings->ssp_psp_T4)
+ |SSPSP_reg(SFRMDLY, ps_settings->ssp_psp_T5)
+ |SSPSP_reg(SFRMWDTH, ps_settings->ssp_psp_T6)
+ |SSPSP_reg(SFRMP, ps_settings->ssp_frmsync_pol_bit);
+ return sspsp;
+}
+
+/*
+ * calculate_sscr0_psp: separate function that calculate sscr0 register
+ * @ps_settings : pointer of the settings struct
+ *
+ * this function is to simplify/clarify set_ssp_i2s_hw function
+ *
+ * Output parameters
+ * u32 : calculated SSCR0 register
+ */
+u32 calculate_sscr0_psp(const struct intel_mid_i2s_settings *ps_settings)
+{
+ u16 l_ssp_data_size = ps_settings->data_size;
+ u32 sscr0;
+ if (l_ssp_data_size > 16) {
+ sscr0 = SSCR0_reg(DSS, SSCR0_DataSize(l_ssp_data_size - 16))
+ | SSCR0_reg(EDSS, 1);
+ } else {
+ sscr0 = SSCR0_reg(DSS, SSCR0_DataSize(l_ssp_data_size))
+ | SSCR0_reg(EDSS, 0);
+ }
+/*
+Can be replaced by code below :
+sscr0 = SSCR0_reg(DSS, (l_ssp_data_size - 1) & 0x0F)
+| SSCR0_reg(EDSS, ((l_ssp_data_size - 1) & 0x10) >> 8);
+*/
+ sscr0 |= SSCR0_reg(MOD, ps_settings->mode)
+ |SSCR0_reg(FRF, ps_settings->frame_format)
+ |SSCR0_reg(RIM, SSP_RX_FIFO_OVER_INT_DISABLE)
+ |SSCR0_reg(TIM, SSP_TX_FIFO_UNDER_INT_DISABLE);
+ return sscr0;
+}
+
+/**
+ * calculate_sscr1_psp - separate function that calculate sscr1 register
+ * @ps_settings : pointer of the settings struct
+ *
+ * this function is to simplify/clarify set_ssp_i2s_hw function
+ *
+ * Output parameters
+ * u32 : calculated SSCR1 register
+ */
+u32 calculate_sscr1_psp(const struct intel_mid_i2s_settings *ps_settings)
+{
+ u32 sscr1;
+ sscr1 = SSCR1_reg(SFRMDIR, ps_settings->sspsfrm_direction)
+ |SSCR1_reg(SCLKDIR, ps_settings->sspslclk_direction)
+ |SSCR1_reg(TTELP, ps_settings->tx_tristate_phase)
+ |SSCR1_reg(TTE, ps_settings->tx_tristate_enable)
+ |SSCR1_reg(TRAIL, ps_settings->ssp_trailing_byte_mode)
+ |SSCR1_reg(TINTE, ps_settings->ssp_rx_timeout_interrupt_status)
+ |SSCR1_reg(PINTE, ps_settings->ssp_trailing_byte_interrupt_status)
+ |SSCR1_reg(LBM, ps_settings->ssp_loopback_mode_status)
+ |SSCR1_reg(RWOT, ps_settings->ssp_duplex_mode)
+ |SSCR1_reg(RFT, SSCR1_RxTresh(ps_settings->ssp_rx_fifo_threshold))
+ |SSCR1_reg(TFT, SSCR1_TxTresh(ps_settings->ssp_tx_fifo_threshold));
+ return sscr1;
+}
+
+/**
+ * set_ssp_i2s_hw - configure the SSP driver according to the ps_settings
+ * @drv_data : structure that contains all details about the SSP Driver
+ * @ps_settings : structure that contains SSP Hardware settings
+ *
+ * it also store ps_settings the drv_data
+ *
+ * Output parameters
+ * NA
+ */
+static void set_ssp_i2s_hw(struct intel_mid_i2s_hdl *drv_data,
+ const struct intel_mid_i2s_settings *ps_settings)
+{
+ u32 sscr0 = 0;
+ u32 sscr1 = 0;
+ u32 sstsa = 0;
+ u32 ssrsa = 0;
+ u32 sspsp = 0;
+ u32 sssr = 0;
+ /* Get the SSP Settings */
+ u16 l_ssp_clk_frm_mode = 0xFF;
+ void __iomem *reg = drv_data->ioaddr;
+ struct device *ddbg = &(drv_data->pdev->dev);
+ dev_dbg(ddbg,
+ "setup SSP I2S PCM1 configuration\n");
+ if ((ps_settings->sspsfrm_direction == SSPSFRM_MASTER_MODE)
+ && (ps_settings->sspslclk_direction == SSPSCLK_MASTER_MODE)) {
+ l_ssp_clk_frm_mode = SSP_IN_MASTER_MODE;
+ } else if ((ps_settings->sspsfrm_direction == SSPSFRM_SLAVE_MODE)
+ && (ps_settings->sspslclk_direction == SSPSCLK_SLAVE_MODE)) {
+ l_ssp_clk_frm_mode = SSP_IN_SLAVE_MODE;
+ } else {
+ dev_err(ddbg, "Unsupported I2S PCM1 configuration\n");
+ goto leave;
+ }
+ dev_dbg(ddbg, "SSPSFRM_DIRECTION:%d:\n",
+ ps_settings->sspsfrm_direction);
+ dev_dbg(ddbg, "SSPSCLK_DIRECTION:%d:\n",
+ ps_settings->sspslclk_direction);
+ if (ps_settings->frame_format != PSP_FORMAT) {
+ dev_err(ddbg, "UNSUPPORTED FRAME FORMAT:%d:\n", ps_settings->frame_format);
+ goto leave;
+ }
+ if ((ps_settings->ssp_tx_dma != SSP_TX_DMA_ENABLE)
+ || (ps_settings->ssp_rx_dma != SSP_RX_DMA_ENABLE)) {
+ dev_err(ddbg, "ONLY DMA MODE IS SUPPORTED");
+ goto leave;
+ }
+ /*********** DMA Transfer Mode ***********/
+ dev_dbg(ddbg, "FORMAT :%d:\n", ps_settings->frame_format);
+ sscr0 = calculate_sscr0_psp(ps_settings);
+ dev_dbg(ddbg, " sscr0 :0x%08X\n", sscr0);
+ sscr1 = calculate_sscr1_psp(ps_settings);
+ dev_dbg(ddbg, " sscr1 :0x%08X\n", sscr1);
+ if (ps_settings->mode == SSP_IN_NETWORK_MODE) {
+ dev_dbg(ddbg, "MODE :%d:\n", ps_settings->mode);
+ sscr0 |= SSCR0_reg(FRDC, SSCR0_SlotsPerFrm(ps_settings->frame_rate_divider_control));
+ dev_dbg(ddbg, "sscr0 :0x%08X\n", sscr0);
+ sspsp = calculate_sspsp_psp(ps_settings);
+ dev_dbg(ddbg, "sspsp :0x%08X\n", sspsp);
+ /* set the active TX time slot (bitmap) */
+ sstsa = SSTSA_reg(TTSA, ps_settings->ssp_active_tx_slots_map);
+ /* set the active RX time slot (bitmap) */
+ ssrsa = SSRSA_reg(RTSA, ps_settings->ssp_active_rx_slots_map);
+ if (l_ssp_clk_frm_mode == SSP_IN_MASTER_MODE) {
+ switch (ps_settings->master_mode_clk_selection) {
+ case SSP_ONCHIP_CLOCK:
+ break;
+ case SSP_NETWORK_CLOCK:
+ sscr0 |= SSCR0_reg(NCS, 1);
+ break;
+ case SSP_EXTERNAL_CLOCK:
+ sscr0 |= SSCR0_reg(ECS, 1);
+ break;
+ case SSP_ONCHIP_AUDIO_CLOCK:
+ sscr0 |= SSCR0_reg(ACS, 1);
+ break;
+ default:
+ dev_err(ddbg, "Master Mode clk selection UNKNOWN");
+ break;
+ }
+ sspsp |= SSPSP_reg(STRTDLY, ps_settings->ssp_psp_T1)
+ |SSPSP_reg(DMYSTRT, ps_settings->ssp_psp_T2);
+ } else { /* Set the Slave Clock Free Running Status */
+ sscr1 |= SSCR1_reg(SCFR, ps_settings->slave_clk_free_running_status);
+ }
+ } else { /* SSP_IN_NORMAL_MODE */
+ dev_err(ddbg, "UNSUPPORTED MODE");
+ goto leave;
+ }
+
+ /* Clear status */
+ sssr = (SSSR_BCE_MASK << SSSR_BCE_SHIFT)
+ | (SSSR_TUR_MASK << SSSR_TUR_SHIFT)
+ | (SSSR_TINT_MASK << SSSR_TINT_SHIFT)
+ | (SSSR_PINT_MASK << SSSR_PINT_SHIFT)
+ | (SSSR_ROR_MASK << SSSR_ROR_SHIFT);
+ /* disable SSP */
+ clear_SSCR0_reg(reg, SSE);
+ dev_dbg(ddbg, "WRITE SSCR0 DISABLE\n");
+ /* Clear status */
+ write_SSSR(sssr, reg);
+ dev_dbg(ddbg, "WRITE SSSR: 0x%08X\n", sssr);
+ write_SSCR0(sscr0, reg);
+ dev_dbg(ddbg, "WRITE SSCR0\n");
+ /* first set CR1 without interrupt and service enables */
+ write_SSCR1(sscr1, reg);
+ write_SSPSP(sspsp, reg);
+ write_SSTSA(sstsa, reg);
+ write_SSRSA(ssrsa, reg);
+ /* set the time out for the reception */
+ write_SSTO(0, reg);
+ ssp1_dump_registers(drv_data);
+leave:
+ return;
+}
+
+static int
+intel_mid_i2s_find_usage(struct pci_dev *pdev,
+ struct intel_mid_i2s_hdl *drv_data,
+ enum intel_mid_i2s_ssp_usage *usage)
+{
+ int pos;
+ u8 adid;
+ int status = 0;
+
+ *usage = SSP_USAGE_UNASSIGNED;
+ pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR);
+ dev_info((&pdev->dev),
+ "Probe/find capability (VNDR %d pos=0x%x)\n",
+ PCI_CAP_ID_VNDR, pos);
+ if (pos > 0) {
+ pos += PCI_CAP_OFFSET_ADID;
+ pci_read_config_byte(pdev, pos, &adid);
+ dev_info(&(pdev->dev), "Vendor capability adid = 0x%x\n", adid);
+ if (adid == PCI_CAP_ADID_I2S_BT_FM)
+ *usage = SSP_USAGE_BLUETOOTH_FM;
+ else if (adid == PCI_CAP_ADID_I2S_MODEM)
+ *usage = SSP_USAGE_MODEM;
+ else
+ *usage = SSP_USAGE_UNASSIGNED;
+ }
+ /* If there is no capability, check with old PCI_ID */
+#ifdef BYPASS_ADID
+ if (*usage == SSP_USAGE_UNASSIGNED) {
+ dev_warn(&(pdev->dev), "Vendor capability not present/invalid\n");
+ switch (pdev->device) {
+ case MFLD_SSP1_DEVICE_ID:
+ *usage = SSP_USAGE_BLUETOOTH_FM;
+ break;
+ case MFLD_SSP0_DEVICE_ID:
+ *usage = SSP_USAGE_MODEM;
+ break;
+ }
+ }
+#endif
+ if (*usage == SSP_USAGE_UNASSIGNED) {
+ dev_info((&pdev->dev),
+ "No probe for I2S PCI-ID: %04x:%04x, ADID(0x%x)=0x%x\n",
+ pdev->vendor, pdev->device, pos, adid);
+ status = -ENODEV;
+ goto err_find_usage;
+ }
+ dev_dbg(&(pdev->dev),
+ "Detected PCI SSP (ID: %04x:%04x) usage =%x\n",
+ pdev->vendor, pdev->device, *usage);
+ dev_dbg(&(pdev->dev),
+ " found PCI SSP controller(ID: %04x:%04x)\n",
+ pdev->vendor, pdev->device);
+ /* Init the driver data structure fields*/
+ switch (pdev->device) {
+ case MFLD_SSP1_DEVICE_ID:
+ drv_data->device_instance = DMA1C_DEVICE_INSTANCE_SSP1;
+ break;
+ case MFLD_SSP0_DEVICE_ID:
+ drv_data->device_instance = DMA1C_DEVICE_INSTANCE_SSP0;
+ break;
+ default:
+ dev_err(&(pdev->dev),
+ "Can not determine dma device instance (PCI ID:%04x)\n",
+ pdev->device);
+ status = -ENODEV;
+ goto err_find_usage;
+ }
+ status = pci_enable_device(pdev);
+ if (status)
+ dev_err((&pdev->dev), "Can not enable device.Err=%d\n", status);
+err_find_usage:
+ return status;
+}
+
+/**
+ * intel_mid_i2s_probe - probing function for the pci selected
+ * @pdev : pci_dev pointer that is probed
+ * @ent : pci_device_id
+ *
+ * Output parameters
+ * NA
+ */
+static int intel_mid_i2s_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct intel_mid_i2s_hdl *drv_data;
+ int status = 0;
+ enum intel_mid_i2s_ssp_usage usage;
+
+ drv_data = kzalloc(sizeof(struct intel_mid_i2s_hdl), GFP_KERNEL);
+ dev_dbg(&(pdev->dev), "%s Probe, drv_data =%p\n", DRIVER_NAME, drv_data);
+ if (!drv_data) {
+ dev_err((&pdev->dev), "Can't alloc driver data in probe\n");
+ status = -ENOMEM;
+ goto leave;
+ }
+ dev_info((&pdev->dev), "Detected PCI SSP (ID: %04x:%04x)\n", pdev->vendor, pdev->device);
+ status = intel_mid_i2s_find_usage(pdev, drv_data, &usage);
+ if (status)
+ goto err_i2s_probe0;
+ mutex_init(&drv_data->mutex);
+ drv_data->pdev = pdev;
+ drv_data->usage = usage;
+ /*
+ * Get basic io resource and map it for SSP1 [BAR=0]
+ */
+ if ((pdev->device == MFLD_SSP1_DEVICE_ID) ||
+ (pdev->device == MFLD_SSP0_DEVICE_ID)) {
+ drv_data->paddr = pci_resource_start(pdev, MRST_SSP_BAR);
+ drv_data->iolen = pci_resource_len(pdev, MRST_SSP_BAR);
+ status = pci_request_region(pdev, MRST_SSP_BAR, dev_name(&pdev->dev));
+ /* map bus memory into CPU space */
+ drv_data->ioaddr = pci_ioremap_bar(pdev, MRST_SSP_BAR);
+ } else {
+ dev_err(&pdev->dev,
+ "Don't know which BAR to usefor this SSP PCDID=%x\n",
+ pdev->device);
+ status = -ENODEV;
+ goto err_i2s_probe1;
+ }
+ dev_dbg(&(pdev->dev), "paddr = : %x\n", (unsigned int) drv_data->paddr);
+ dev_dbg(&(pdev->dev), "iolen = : %d\n", drv_data->iolen);
+ if (status) {
+ dev_err((&pdev->dev), "Can't request region. err=%d\n", status);
+ goto err_i2s_probe1;
+ }
+ if (!drv_data->ioaddr) {
+ dev_err((&pdev->dev), "ioremap_nocache error\n");
+ status = -ENOMEM;
+ goto err_i2s_probe2;
+ }
+ dev_dbg(&(pdev->dev), "ioaddr = : %p\n", drv_data->ioaddr);
+ /* prepare for DMA channel allocation */
+ /* get the pci_dev structure pointer */
+ /* Check the SSP, if SSP3, then another DMA is used (GPDMA..) */
+ if ((pdev->device == MFLD_SSP1_DEVICE_ID) ||
+ (pdev->device == MFLD_SSP0_DEVICE_ID)) {
+ drv_data->dmac1 = pci_get_device(PCI_VENDOR_ID_INTEL,
+ MFLD_LPE_DMA_DEVICE_ID,
+ NULL);
+ } else {
+ dev_err(&pdev->dev,
+ "Don't know dma device ID for this SSP PCDID=%x\n",
+ pdev->device);
+ goto err_i2s_probe3;
+ }
+ /* in case the stop dma have to wait for end of callbacks */
+ /* This will be removed when TERMINATE_ALL available in DMA */
+ init_waitqueue_head(&drv_data->wq_chan_closing);
+ if (!drv_data->dmac1) {
+ dev_err(&(drv_data->pdev->dev), "Can't find DMAC1, dma init failed\n");
+ status = -ENODEV;
+ goto err_i2s_probe3;
+ }
+ /* increment ref count of pci device structure already done by */
+ /* pci_get_device. will do a pci_dev_put when exiting the module */
+ pci_set_drvdata(pdev, drv_data);
+ /* set SSP FrameSync and CLK direction in INPUT mode in order
+ * to avoid disturbing peripherals
+ */
+ write_SSCR1((SSCR1_SFRMDIR_MASK<<SSCR1_SFRMDIR_SHIFT)
+ | (SSCR1_SCLKDIR_MASK<<SSCR1_SCLKDIR_SHIFT),
+ drv_data->ioaddr);
+ /* Attach to IRQ */
+ drv_data->irq = pdev->irq;
+ dev_dbg(&(pdev->dev), "attaching to IRQ: %04x\n", pdev->irq);
+ status = request_irq(drv_data->irq, i2s_int, IRQF_SHARED, "i2s ssp", drv_data);
+ if (status < 0) {
+ dev_err(&pdev->dev, "can not get IRQ. status err=%d\n", status);
+ goto err_i2s_probe3;
+ }
+ pm_runtime_enable(&(drv_data->pdev->dev));
+ pm_runtime_allow(&(drv_data->pdev->dev));
+ pm_runtime_set_active(&(drv_data->pdev->dev));
+ pm_request_idle(&(drv_data->pdev->dev));
+
+ goto leave;
+err_i2s_probe3:
+ iounmap(drv_data->ioaddr);
+err_i2s_probe2:
+ pci_release_region(pdev, MRST_SSP_BAR);
+err_i2s_probe1:
+ pci_disable_device(pdev);
+err_i2s_probe0:
+ kfree(drv_data);
+leave:
+ return status;
+}
+
+static void __devexit intel_mid_i2s_remove(struct pci_dev *pdev)
+{
+ struct intel_mid_i2s_hdl *drv_data;
+
+ drv_data = pci_get_drvdata(pdev);
+ if (!drv_data) {
+ dev_err(&pdev->dev, "no drv_data in pci device to remove!\n");
+ goto leave;
+ }
+ if (test_bit(I2S_PORT_OPENED, &drv_data->flags)) {
+ dev_warn(&pdev->dev, "Not closed before removing pci_dev!\n");
+ intel_mid_i2s_close(drv_data);
+ }
+ pci_set_drvdata(pdev, NULL);
+ /* Stop DMA is already done during close() */
+ pci_dev_put(drv_data->dmac1);
+ /* Disable the SSP at the peripheral and SOC level */
+ write_SSCR0(0, drv_data->ioaddr);
+ free_irq(drv_data->irq, drv_data);
+ iounmap(drv_data->ioaddr);
+ pci_release_region(pdev, MRST_SSP_BAR);
+ pci_release_region(pdev, MRST_LPE_BAR);
+ pci_disable_device(pdev);
+ kfree(drv_data);
+leave:
+ return;
+}
+
+/**
+ * intel_mid_i2s_init - register pci driver
+ *
+ */
+static int __init intel_mid_i2s_init(void)
+{
+ return pci_register_driver(&intel_mid_i2s_driver);
+}
+
+static void __exit intel_mid_i2s_exit(void)
+{
+ pci_unregister_driver(&intel_mid_i2s_driver);
+}
+
+
+module_init(intel_mid_i2s_init);
+module_exit(intel_mid_i2s_exit);
+
+
+
diff --git a/sound/pci/intel_mid_i2s/intel_mid_i2s.h b/sound/pci/intel_mid_i2s/intel_mid_i2s.h
new file mode 100644
index 00000000000..819164d9d93
--- /dev/null
+++ b/sound/pci/intel_mid_i2s/intel_mid_i2s.h
@@ -0,0 +1,502 @@
+/*
+ * <Driver for I2S protocol on SSP (Moorestown and Medfield hardware)>
+ * Copyright (c) 2010, Intel Corporation.
+ * Louis LE GALL <louis.le.gall intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define DRIVER_NAME "I2S SSP Driver"
+/*
+ * Defines
+ */
+#define MFLD_SSP1_DEVICE_ID 0x0825 /* FOR MFLD */
+#define MRST_SSP0_DEVICE_ID 0x0815 /* FOR MRST */
+#define MFLD_SSP0_DEVICE_ID 0x0832 /* FOR MFLD */
+
+#define MRST_LPE_DMA_DEVICE_ID 0x0814
+#define MFLD_LPE_DMA_DEVICE_ID 0x0830
+
+/* SSP1 PCI device Base Address Register */
+#define MRST_SSP_BAR 0
+#define MRST_LPE_BAR 1
+#define DMA1C_DEVICE_INSTANCE_SSP0 0
+#define DMA1C_DEVICE_INSTANCE_SSP1 1
+#define OFFSET_SSCR0 0x00
+#define OFFSET_SSCR1 0x04
+#define OFFSET_SSSR 0x08
+#define OFFSET_SSITR 0x0c
+#define OFFSET_SSDR 0x10
+#define OFFSET_SSTO 0x28
+#define OFFSET_SSPSP 0x2c
+#define OFFSET_SSTSA 0x30 /* SSP Tx Timeslot Active */
+#define OFFSET_SSRSA 0x34 /* SSP Rx Timeslot Active */
+/* SST register map */
+#define OFFSET_LPE_CSR 0x00
+#define OFFSET_LPE_PISR 0x08
+#define OFFSET_LPE_PIMR 0x10
+#define OFFSET_LPE_ISRX 0x18
+#define OFFSET_LPE_IMRX 0x28
+#define OFFSET_LPE_IPCX 0x38 /* IPC IA-SST */
+#define OFFSET_LPE_IPCD 0x40 /* IPC SST-IA */
+#define OFFSET_LPE_ISRD 0x20 /* dummy register for*/
+ /* shim workaround */
+#define OFFSET_LPE_SHIM_SIZE 0X44
+
+#define SSP_IN_MASTER_MODE 0x0
+#define SSP_IN_SLAVE_MODE 0x1
+
+/*
+ * Macros
+ */
+#define DEFINE_SSP_REG(reg, off) \
+static inline u32 read_##reg(void *p) { return __raw_readl(p + (off)); } \
+static inline void write_##reg(u32 v, void *p) { __raw_writel(v, p + (off)); }
+DEFINE_SSP_REG(SSCR0, 0x00)
+DEFINE_SSP_REG(SSCR1, 0x04)
+DEFINE_SSP_REG(SSSR, 0x08)
+DEFINE_SSP_REG(SSITR, 0x0c)
+DEFINE_SSP_REG(SSDR, 0x10)
+DEFINE_SSP_REG(SSTO, 0x28)
+DEFINE_SSP_REG(SSPSP, 0x2c)
+DEFINE_SSP_REG(SSTSA, 0x30)
+DEFINE_SSP_REG(SSRSA, 0x34)
+DEFINE_SSP_REG(SSTSS, 0x38)
+DEFINE_SSP_REG(SSACD, 0x3C)
+DEFINE_SSP_REG(I2CCTRL, 0x00);
+DEFINE_SSP_REG(I2CDATA, 0x04);
+/*
+ * Langwell SSP serial port register definitions
+ */
+#define SSCR0_DSS_MASK 0x0F /* Data Size Select [4..16] */
+#define SSCR0_DSS_SHIFT 0
+#define SSCR0_FRF_MASK 0x03 /* FRame Format */
+#define SSCR0_FRF_SHIFT 4
+#define SSCR0_ECS_MASK 0x01 /* External clock select */
+#define SSCR0_ECS_SHIFT 6
+#define SSCR0_SSE_MASK 0x01 /* Synchronous Serial Port Enable */
+#define SSCR0_SSE_SHIFT 7
+#define SSCR0_SCR_MASK 0xFFF /* Not implemented */
+#define SSCR0_SCR_SHIFT 8
+#define SSCR0_EDSS_MASK 0x1 /* Extended data size select */
+#define SSCR0_EDSS_SHIFT 20
+#define SSCR0_NCS_MASK 0x1 /* Network clock select */
+#define SSCR0_NCS_SHIFT 21
+#define SSCR0_RIM_MASK 0x1 /* Receive FIFO overrrun int mask */
+#define SSCR0_RIM_SHIFT 22
+#define SSCR0_TIM_MASK 0x1 /* Transmit FIFO underrun int mask */
+#define SSCR0_TIM_SHIFT 23
+#define SSCR0_FRDC_MASK 0x7 /* Frame Rate Divider Control */
+#define SSCR0_FRDC_SHIFT 24
+#define SSCR0_ACS_MASK 0x1 /* Audio clock select */
+#define SSCR0_ACS_SHIFT 30
+#define SSCR0_MOD_MASK 0x1 /* Mode (normal or network) */
+#define SSCR0_MOD_SHIFT 31
+
+#define SSCR0_DataSize(x) ((x) - 1) /* Data Size Select [4..16] */
+#define SSCR0_SlotsPerFrm(x) ((x) - 1) /* Time slots per frame */
+#define SSCR0_SerClkDiv(x) ((x) - 1) /* Divisor [1..4096],... */
+ /*...not implemented on Langwell */
+#define SSCR1_TTELP_MASK 0x1 /* TXD Tristate Enable on Last Phase */
+#define SSCR1_TTELP_SHIFT 31
+#define SSCR1_TTE_MASK 0x1 /* TXD Tristate Enable */
+#define SSCR1_TTE_SHIFT 30
+#define SSCR1_EBCEI_MASK 0x1 /* Enable Bit Count Error Interrupt */
+#define SSCR1_EBCEI_SHIFT 29
+#define SSCR1_SCFR_MASK 0x1 /* Slave Clock Running */
+#define SSCR1_SCFR_SHIFT 28
+#define SSCR1_ECRA_MASK 0x1 /* Enable Clock Request A */
+#define SSCR1_ECRA_SHIFT 27
+#define SSCR1_ECRB_MASK 0x1 /* Enable Clock Request B */
+#define SSCR1_ECRB_SHIFT 26
+#define SSCR1_SCLKDIR_MASK 0x1 /* SSPCLK Direction */
+#define SSCR1_SCLKDIR_SHIFT 25
+#define SSCR1_SFRMDIR_MASK 0x1 /* SSPFRM Direction */
+#define SSCR1_SFRMDIR_SHIFT 24
+#define SSCR1_RWOT_MASK 0x1 /* Receive without Transmit */
+#define SSCR1_RWOT_SHIFT 23
+#define SSCR1_TRAIL_MASK 0x1 /* Trailing Byte */
+#define SSCR1_TRAIL_SHIFT 22
+#define SSCR1_TSRE_MASK 0x1 /* DMA Transmit Service Request Enable*/
+#define SSCR1_TSRE_SHIFT 21
+#define SSCR1_RSRE_MASK 0x1 /* DMA Receive Service Request Enable */
+#define SSCR1_RSRE_SHIFT 20
+#define SSCR1_TINTE_MASK 0x1 /* Receiver Time-out Interrupt Enable */
+#define SSCR1_TINTE_SHIFT 19
+#define SSCR1_PINTE_MASK 0x1 /* Periph. Trailing Byte Int. Enable */
+#define SSCR1_PINTE_SHIFT 18
+#define SSCR1_IFS_MASK 0x1 /* Invert Frame Signal */
+#define SSCR1_IFS_SHIFT 16
+#define SSCR1_STFR_MASK 0x1 /* Select FIFO for EFWR: test mode */
+#define SSCR1_STFR_SHIFT 15
+#define SSCR1_EFWR_MASK 0x1 /* Enable FIFO Write/Read: test mode */
+#define SSCR1_EFWR_SHIFT 14
+#define SSCR1_RFT_MASK 0xF /* Receive FIFO Trigger Threshold */
+#define SSCR1_RFT_SHIFT 10
+#define SSCR1_TFT_MASK 0xF /* Transmit FIFO Trigger Threshold */
+#define SSCR1_TFT_SHIFT 6
+#define SSCR1_MWDS_MASK 0x1 /* Microwire Transmit Data Size */
+#define SSCR1_MWDS_SHIFT 5
+#define SSCR1_SPH_MASK 0x1 /* Motorola SPI SSPSCLK phase setting */
+#define SSCR1_SPH_SHIFT 4
+#define SSCR1_SPO_MASK 0x1 /* Motorola SPI SSPSCLK polarity */
+#define SSCR1_SPO_SHIFT 3
+#define SSCR1_LBM_MASK 0x1 /* Loopback mode: test mode */
+#define SSCR1_LBM_SHIFT 2
+#define SSCR1_TIE_MASK 0x1 /* Transmit FIFO Interrupt Enable */
+#define SSCR1_TIE_SHIFT 1
+#define SSCR1_RIE_MASK 0x1 /* Receive FIFO Interrupt Enable */
+#define SSCR1_RIE_SHIFT 0
+
+#define SSCR1_RxTresh(x) ((x) - 1) /* level [1..16] */
+#define SSCR1_TxTresh(x) ((x) - 1) /* level [1..16] */
+
+#define SSPSP_FSRT_MASK 0x1 /* Frame Sync Relative Timing Bit */
+#define SSPSP_FSRT_SHIFT 25
+#define SSPSP_DMYSTOP_MASK 0x3 /* Dummy Stop in Number of SSPSCLKs:T4*/
+#define SSPSP_DMYSTOP_SHIFT 23
+#define SSPSP_SFRMWDTH_MASK 0x3F /* Serial Frame width : T6 */
+#define SSPSP_SFRMWDTH_SHIFT 16
+#define SSPSP_SFRMDLY_MASK 0x7F /* Serial Fr. Delay in 1/2SSPSCLKs:T5 */
+#define SSPSP_SFRMDLY_SHIFT 9
+#define SSPSP_DMYSTRT_MASK 0x3 /* Dummy Start in Number of SSPSCLKs..*/
+#define SSPSP_DMYSTRT_SHIFT 7 /*...after STRTDLY, T2 (master mode only) */
+#define SSPSP_STRTDLY_MASK 0x7 /* Start Delay, T1 (master mode only) */
+#define SSPSP_STRTDLY_SHIFT 4
+#define SSPSP_ETDS_MASK 0x1 /* End of Transfer Data State */
+#define SSPSP_ETDS_SHIFT 3
+#define SSPSP_SFRMP_MASK 0x1 /* Serial Frame Polarity */
+#define SSPSP_SFRMP_SHIFT 2
+#define SSPSP_SCMODE_MASK 0x3 /* Serial bit-rate Clock Mode */
+#define SSPSP_SCMODE_SHIFT 0
+
+#define SSTSA_TTSA_MASK 0xFF
+#define SSTSA_TTSA_SHIFT 0
+
+#define SSRSA_RTSA_MASK 0xFF
+#define SSRSA_RTSA_SHIFT 0
+
+#define SSSR_BCE_MASK 0x1 /* Bit Count Error: Read/Write 1 to Clear */
+#define SSSR_BCE_SHIFT 23
+#define SSSR_CSS_MASK 0x1 /* Clock Synchronization Status */
+#define SSSR_CSS_SHIFT 22
+#define SSSR_TUR_MASK 0x1 /* Transmit FIFO UnderRun: Rd/Wr 1 to Clear */
+#define SSSR_TUR_SHIFT 21
+#define SSSR_EOC_MASK 0x1 /* End Of Chain: Read/Write 1 to Clear */
+#define SSSR_EOC_SHIFT 20
+#define SSSR_TINT_MASK 0x1 /* Receiver Time-out Interrupt:... */
+#define SSSR_TINT_SHIFT 19 /* ...Read/Write 1 to Clear */
+#define SSSR_PINT_MASK 0x1 /* Peripheral Trailing Byte Interrupt:... */
+#define SSSR_PINT_SHIFT 18 /* ...Read/Write 1 to Clear */
+#define SSSR_RFL_MASK 0xF /* Receive FIFO Level */
+#define SSSR_RFL_SHIFT 12
+#define SSSR_TFL_MASK 0xF /* Transmit FIFO Level */
+#define SSSR_TFL_SHIFT 8
+#define SSSR_ROR_MASK 0x1 /* Receive FIFO Overrun: Read/Write 1 to Clear*/
+#define SSSR_ROR_SHIFT 7
+#define SSSR_RFS_MASK 0x1 /* Receive FIFO Service Request */
+#define SSSR_RFS_SHIFT 6
+#define SSSR_TFS_MASK 0x1 /* Transmit FIFO Service Request */
+#define SSSR_TFS_SHIFT 5
+#define SSSR_BSY_MASK 0x1 /* SSP Busy */
+#define SSSR_BSY_SHIFT 4
+#define SSSR_RNE_MASK 0x1 /* Receive FIFO not empty */
+#define SSSR_RNE_SHIFT 3
+#define SSSR_TFN_MASK 0x1 /* Transmit FIFO not Full */
+#define SSSR_TFN_SHIFT 2
+
+
+#define SSP_OFF 0
+#define SSP_ON 1
+
+/* bit I2S_PORT_OPENED lock for open/close
+ * bit I2S_PORT_READ_BUSY lock for read requests (serialized)
+ * bit I2S_PORT_WRITE_BUSY lock for write requests (serialized)
+ * bit I2S_PORT_CLOSING means close on going, waiting for pending callbacks.
+ */
+
+enum i2s_flags {
+ I2S_PORT_OPENED,
+ I2S_PORT_WRITE_BUSY,
+ I2S_PORT_READ_BUSY,
+ I2S_PORT_CLOSING
+};
+
+#define FIFO_SIZE 16
+/*
+ * Structures Definition
+ */
+
+/**
+ * struct intel_mid_i2s_data - context struct to keep SSP I2S data
+ * @pdev: pci dev pointer corresponding to context
+ * @paddr:
+ * @ioaddr:
+ * @iolen:
+ * @irq:
+ * @clear_sr:
+ * @mask_sr:
+ * @dmac1:
+ * @dmas_tx: dma slave structure for transmit
+ * @dmas_rx: dma slave structure for receive
+ * @txchan: Dma channel for transmit
+ * @rxchan: Dma channel for receive
+ *
+ * @read_done:
+ * @read_dst:
+ * @read_len:
+ *
+ * @write_done:
+ * @write_src:
+ * @write_len:
+ *
+ * @mutex: a mutex to make sure we have once-at-time critical functions.
+ *
+ * Longer description
+ */
+
+/* Locking rules:
+ *
+ * All the fields, not listed below, are set during probe, and then read only
+ * So they do not require locking
+ *
+ * The fields that require locking are related to the I2S read and write
+ * requests.
+ *
+ * We allow only 1 read at a time, and 1 write at a time.
+ * We allow read in parallel of write but use separate variables.
+ * We allow only 1 user per SSP/I2S port.
+ * Typically this user will be a dedicated PulseAudio RT thread communicating
+ * with cmt-speech driver which in turns communicates with intel_mid_ssp
+ * driver.
+ * PCM mixing is done before access to kernel drivers;typically within
+ * PulseAudio or after; typically within the modem.
+ * So no concurrent users, per I2S channel, to this driver are allowed
+ * The read & write are triggered from a USER context
+ * The read & write callbacks are called from a BH context
+ * You should have not callback pending before calling close, close will wait
+ * for remaining callback calls.
+ * It is not allowed to call close function from read/write callback threads.
+ *
+ * Locking is handled via drv_data->flags & atomic bitwise operations
+ *
+ * I2S0 is dedicated for PCM transfer to/from the modem module
+ * I2S1 is dedicated for PCM transfer to/from the Bluetooth or FM module
+ *
+ * read_done:
+ * read_len:
+ * read_dst:
+ *
+ * write_done:
+ * write_src:
+ * write_len:
+ *
+ * mutex: a mutex to make sure we have once-at-time critical functions.
+ * once-at-a-time actions functions are:
+ * -intel_mid_i2s_open
+ * -intel_mid_i2s_close
+ * -intel_mid_i2s_rd_req
+ * -intel_mid_i2s_wr_req
+ * -intel_mid_i2s_set_rd_cb
+ * -intel_mid_i2s_set_wr_cb
+ * These functions should not be called during a lock() neither in interrupt.
+ */
+
+struct intel_mid_i2s_hdl {
+ /* Driver model hookup */
+ struct pci_dev *pdev;
+ /* register addresses */
+ dma_addr_t paddr;
+ void __iomem *ioaddr;
+ u32 iolen;
+ int irq;
+
+ /* SSP masks */
+ u32 clear_sr;
+ u32 mask_sr;
+
+ /* SSP Configuration */
+ /* DMA info */
+ struct pci_dev *dmac1;
+ wait_queue_head_t wq_chan_closing;
+
+ struct intel_mid_dma_slave dmas_tx;
+ struct intel_mid_dma_slave dmas_rx;
+ struct dma_chan *txchan;
+ struct dma_chan *rxchan;
+
+ unsigned int device_instance;
+ /* Call back functions */
+ int (*read_callback)(void *param);
+ dma_addr_t read_dst;
+ size_t read_len; /* read_len > 0 <=> read_dma_running */
+ void *read_param; /* context param for callback */
+ int (*write_callback)(void *param);
+ dma_addr_t write_src;
+ size_t write_len; /* write_len > 0 <=> read_dma_running */
+ void *write_param; /* context param for callback */
+
+ unsigned long flags;
+ struct mutex mutex;
+ enum intel_mid_i2s_ssp_usage usage;
+
+ struct intel_mid_i2s_settings current_settings;
+
+};
+
+static void i2s_read_done(void *arg);
+static void i2s_write_done(void *arg);
+static bool chan_filter(struct dma_chan *chan, void *param);
+static void i2s_dma_stop(struct intel_mid_i2s_hdl *drv_data);
+static int i2s_dma_start(struct intel_mid_i2s_hdl *drv_data);
+static void ssp1_dump_registers(struct intel_mid_i2s_hdl *);
+static irqreturn_t i2s_int(int irq, void *dev_id);
+static void set_ssp_i2s_hw(struct intel_mid_i2s_hdl *drv_data,
+ const struct intel_mid_i2s_settings *ps_settings);
+static int check_device(struct device *device_ptr, void *data);
+#ifdef CONFIG_PM
+static int intel_mid_i2s_runtime_resume(struct device *device_ptr);
+static int intel_mid_i2s_runtime_suspend(struct device *device_ptr);
+#endif
+static int intel_mid_i2s_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent);
+static void intel_mid_i2s_remove(struct pci_dev *pdev);
+static void i2s_ssp_stop(struct intel_mid_i2s_hdl *drv_data);
+/*static int bt_pcm_dma_init(struct intel_mid_i2s_hdl *drv_data);*/
+
+
+#ifdef CONFIG_PM
+static int intel_mid_i2s_driver_suspend(struct pci_dev *dev,
+ pm_message_t state);
+static int intel_mid_i2s_driver_resume(struct pci_dev *dev);
+#endif
+
+/*
+ * These define will clarify source code when accessing SSCRx registers
+ */
+
+#define SSCR0_reg(regbit, value) \
+ (((value) & SSCR0_##regbit##_MASK) << SSCR0_##regbit##_SHIFT)
+
+#define SSCR1_reg(regbit, value) \
+ (((value) & SSCR1_##regbit##_MASK) << SSCR1_##regbit##_SHIFT)
+
+#define SSPSP_reg(regbit, value) \
+ (((value) & SSPSP_##regbit##_MASK) << SSPSP_##regbit##_SHIFT)
+
+#define SSRSA_reg(regbit, value) \
+ (((value) & SSRSA_##regbit##_MASK) << SSRSA_##regbit##_SHIFT)
+#define SSTSA_reg(regbit, value) \
+ (((value) & SSTSA_##regbit##_MASK) << SSTSA_##regbit##_SHIFT)
+
+
+#define change_SSCR0_reg(reg_pointer, regbit, value) \
+ write_SSCR0((read_SSCR0(reg_pointer) \
+ & (~((SSCR0_##regbit##_MASK << SSCR0_##regbit##_SHIFT)))) \
+ | (((value) & SSCR0_##regbit##_MASK) << SSCR0_##regbit##_SHIFT), \
+ reg_pointer);
+
+#define set_SSCR0_reg(reg_pointer, regbit) \
+ write_SSCR0(read_SSCR0(reg_pointer) \
+ | (SSCR0_##regbit##_MASK << SSCR0_##regbit##_SHIFT), \
+ reg_pointer);
+
+#define clear_SSCR0_reg(reg_pointer, regbit) \
+ write_SSCR0((read_SSCR0(reg_pointer) \
+ & (~((SSCR0_##regbit##_MASK << SSCR0_##regbit##_SHIFT)))), \
+ reg_pointer);
+
+#define change_SSCR1_reg(reg_pointer, regbit, value) \
+ write_SSCR1((read_SSCR1(reg_pointer) \
+ & (~((SSCR1_##regbit##_MASK << SSCR1_##regbit##_SHIFT)))) \
+ | (((value) & SSCR1_##regbit##_MASK) << SSCR1_##regbit##_SHIFT), \
+ reg_pointer);
+
+#define set_SSCR1_reg(reg_pointer, regbit) \
+ write_SSCR1(read_SSCR1(reg_pointer) \
+ | (SSCR1_##regbit##_MASK << SSCR1_##regbit##_SHIFT), \
+ reg_pointer);
+
+#define clear_SSCR1_reg(reg_pointer, regbit) \
+ write_SSCR1((read_SSCR1(reg_pointer) \
+ & (~((SSCR1_##regbit##_MASK << SSCR1_##regbit##_SHIFT)))), \
+ reg_pointer);
+
+/* RX FIFO level */
+#define GET_SSSR_val(x, regb) \
+ ((x & (SSSR_##regb##_MASK<<SSSR_##regb##_SHIFT))>>SSSR_##regb##_SHIFT)
+
+
+/*
+ * SSP hardware can be configured as I2S, PCM, SPI...
+ * In order to allow flexibility without modifying the software driver, the
+ * PCI header uses the configuration register 'adid':
+ *
+ * The PCI header associated to SSP devices includes a configuration register.
+ * It provides information to a driver which is probed for the SSP, specifying
+ * in which way the SSP is supposed to be used.
+ * Here is the format of this configuration register (8 bits):
+ *
+ * bits 2..0: Mode
+ * 000: Invalid, the register should be ignored
+ * 001: SSP to be used as SPI controller
+ * 010: SSP to be used in I2S/ISS mode
+ * other: Reserved
+ *
+ * bits 5..3: Configuration
+ * In I2S/ISS mode:
+ * 000: Invalid
+ * 001: Bluetooth
+ * 010: Modem
+ * other: Reserved
+ * In SPI mode:
+ * Value is the SPI bus number connected to the SSP.
+ * To be used for registration to the Linux SPI
+ * framework.
+ *
+ * bit 6: SPI slave
+ * Relevant in SPI mode only. If set, indicates the SPI clock
+ * is not provided by the SSP: SPI slave mode.
+ *
+ * bit 7: Reserved (0)
+ *
+ * This configuration register is implemented in the adid field of the
+ * Vendor Specific PCI capability associated to the SSP. The format of
+ * this capability is:
+ *
+ * uint8_t capId; < Capability ID (vendor-specific)
+ * uint8_t nextCap; < Next Item Ptr
+ * uint8_t length; < Size of this capability (7)
+ * uint8_t version; < Version of this capability (1)
+ * uint8_t lss; < Logical subsystem info
+ * Bit 7 = PMU (0 = NC, 1 = SC)
+ * Bits 6:0 = LSS ID
+ * uint8_t apmc; < Additional PM capabilities
+ * Bit 7 = Rsvd
+ * Bit 6 = Wake capable
+ * Bit 5 = D3 support
+ * Bit 4 = D2 support
+ * Bit 3 = D1 support
+ * Bit 2 = D0i3 support
+ * Bit 1 = D0i2 support
+ * Bit 0 = D0i1 support
+ * uint8_t adid; < Additional device ID (dev-specific)
+ * uint8_t rsvd; < Reserved for future use
+ *
+ * The capability data are in the PCI configuration space and the
+ * adid field can be modified using BMP tool.
+ */
+/* ADDID = Additional Device ID */
+#define PCI_CAP_OFFSET_ADID 6
+
+