Maemo patchset 20101501+0m5
[h-e-n] / drivers / misc / ssi / ssi_driver_int.c
diff --git a/drivers/misc/ssi/ssi_driver_int.c b/drivers/misc/ssi/ssi_driver_int.c
new file mode 100644 (file)
index 0000000..79dda0c
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * ssi_driver_int.c
+ *
+ * Implements SSI interrupt functionality.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Carlos Chinea <carlos.chinea@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#include "ssi_driver.h"
+
+void ssi_reset_ch_read(struct ssi_channel *ch)
+{
+       struct ssi_port *p = ch->ssi_port;
+       struct ssi_dev *ssi_ctrl = p->ssi_controller;
+       unsigned int channel = ch->channel_number;
+       void __iomem *base = ssi_ctrl->base;
+       unsigned int port = p->port_number;
+       unsigned int irq = p->n_irq;
+
+       ch->read_data.addr = NULL;
+       ch->read_data.size = 0;
+       ch->read_data.lch = -1;
+
+       ssi_outl(SSI_SSR_DATAAVAILABLE(channel), base,
+                       SSI_SYS_MPU_STATUS_REG(port, irq));
+}
+
+void ssi_reset_ch_write(struct ssi_channel *ch)
+{
+       ch->write_data.addr = NULL;
+       ch->write_data.size = 0;
+       ch->write_data.lch = -1;
+}
+
+int ssi_driver_write_interrupt(struct ssi_channel *ch, u32 *data)
+{
+       struct ssi_port *p = ch->ssi_port;
+       unsigned int port = p->port_number;
+       unsigned int channel = ch->channel_number;
+
+       clk_enable(p->ssi_controller->ssi_clk);
+       ssi_outl_or(SSI_SST_DATAACCEPT(channel), p->ssi_controller->base,
+                                       SSI_SYS_MPU_ENABLE_REG(port, p->n_irq));
+
+       return 0;
+}
+
+int ssi_driver_read_interrupt(struct ssi_channel *ch, u32 *data)
+{
+       struct ssi_port *p = ch->ssi_port;
+       unsigned int port = p->port_number;
+       unsigned int channel = ch->channel_number;
+
+       clk_enable(p->ssi_controller->ssi_clk);
+
+       ssi_outl_or(SSI_SSR_DATAAVAILABLE(channel), p->ssi_controller->base,
+                                       SSI_SYS_MPU_ENABLE_REG(port, p->n_irq));
+
+       clk_disable(p->ssi_controller->ssi_clk);
+
+       return 0;
+}
+
+void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch)
+{
+       struct ssi_port *p = ch->ssi_port;
+       unsigned int port = p->port_number;
+       unsigned int channel = ch->channel_number;
+       void __iomem *base = p->ssi_controller->base;
+       u32 enable;
+
+       clk_enable(p->ssi_controller->ssi_clk);
+
+       enable = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, p->n_irq));
+       if (!(enable & SSI_SST_DATAACCEPT(channel))) {
+               dev_dbg(&ch->dev->device, LOG_NAME "Write cancel on not "
+               "enabled channel %d ENABLE REG 0x%08X", channel, enable);
+               clk_disable(p->ssi_controller->ssi_clk);
+               return;
+       }
+       ssi_outl_and(~SSI_SST_DATAACCEPT(channel), base,
+                               SSI_SYS_MPU_ENABLE_REG(port, p->n_irq));
+       ssi_outl_and(~NOTFULL(channel), base, SSI_SST_BUFSTATE_REG(port));
+       ssi_reset_ch_write(ch);
+
+       clk_disable(p->ssi_controller->ssi_clk);
+       clk_disable(p->ssi_controller->ssi_clk);
+}
+
+void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch)
+{
+       struct ssi_port *p = ch->ssi_port;
+       unsigned int port = p->port_number;
+       unsigned int channel = ch->channel_number;
+       void __iomem *base = p->ssi_controller->base;
+
+       clk_enable(p->ssi_controller->ssi_clk);
+
+       ssi_outl_and(~SSI_SSR_DATAAVAILABLE(channel), base,
+                                       SSI_SYS_MPU_ENABLE_REG(port, p->n_irq));
+       ssi_outl_and(~NOTEMPTY(channel), base, SSI_SSR_BUFSTATE_REG(port));
+       ssi_reset_ch_read(ch);
+
+       clk_disable(p->ssi_controller->ssi_clk);
+}
+
+static void do_channel_tx(struct ssi_channel *ch)
+{
+       struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller;
+       void __iomem *base = ssi_ctrl->base;
+       unsigned int n_ch;
+       unsigned int n_p;
+       unsigned int irq;
+
+       n_ch = ch->channel_number;
+       n_p = ch->ssi_port->port_number;
+       irq = ch->ssi_port->n_irq;
+
+       spin_lock(&ssi_ctrl->lock);
+
+       if (ch->write_data.addr == NULL) {
+               ssi_outl_and(~SSI_SST_DATAACCEPT(n_ch), base,
+                                       SSI_SYS_MPU_ENABLE_REG(n_p, irq));
+               ssi_reset_ch_write(ch);
+               spin_unlock(&ssi_ctrl->lock);
+               clk_disable(ssi_ctrl->ssi_clk);
+               (*ch->write_done)(ch->dev);
+       } else {
+               ssi_outl(*(ch->write_data.addr), base,
+                                       SSI_SST_BUFFER_CH_REG(n_p, n_ch));
+               ch->write_data.addr = NULL;
+               spin_unlock(&ssi_ctrl->lock);
+       }
+}
+
+static void do_channel_rx(struct ssi_channel *ch)
+{
+       struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller;
+       void __iomem *base = ch->ssi_port->ssi_controller->base;
+       unsigned int n_ch;
+       unsigned int n_p;
+       unsigned int irq;
+       int rx_poll = 0;
+       int data_read = 0;
+
+       n_ch = ch->channel_number;
+       n_p = ch->ssi_port->port_number;
+       irq = ch->ssi_port->n_irq;
+
+       spin_lock(&ssi_ctrl->lock);
+
+       if (ch->flags & SSI_CH_RX_POLL)
+               rx_poll = 1;
+
+       if (ch->read_data.addr) {
+               data_read = 1;
+               *(ch->read_data.addr) = ssi_inl(base,
+                                               SSI_SSR_BUFFER_CH_REG(n_p, n_ch));
+       }
+
+       ssi_outl_and(~SSI_SSR_DATAAVAILABLE(n_ch), base,
+                                       SSI_SYS_MPU_ENABLE_REG(n_p, irq));
+       ssi_reset_ch_read(ch);
+
+       spin_unlock(&ssi_ctrl->lock);
+
+       if (rx_poll)
+               ssi_port_event_handler(ch->ssi_port,
+                                       SSI_EVENT_SSR_DATAAVAILABLE,
+                                       (void *)n_ch);
+
+       if (data_read)
+               (*ch->read_done)(ch->dev);
+}
+
+static void do_ssi_tasklet(unsigned long ssi_port)
+{
+       struct ssi_port *pport = (struct ssi_port *)ssi_port;
+       struct ssi_dev *ssi_ctrl = pport->ssi_controller;
+       void __iomem *base = ssi_ctrl->base;
+       unsigned int port = pport->port_number;
+       unsigned int channel;
+       unsigned int irq = pport->n_irq;
+       u32 channels_served = 0;
+       u32 status_reg;
+       u32 ssr_err_reg;
+
+       clk_enable(ssi_ctrl->ssi_clk);
+
+       status_reg = ssi_inl(base, SSI_SYS_MPU_STATUS_REG(port, irq));
+       status_reg &= ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, irq));
+
+       for (channel = 0; channel < pport->max_ch; channel++) {
+               if (status_reg & SSI_SST_DATAACCEPT(channel)) {
+                       do_channel_tx(&pport->ssi_channel[channel]);
+                       channels_served |= SSI_SST_DATAACCEPT(channel);
+               }
+
+               if (status_reg & SSI_SSR_DATAAVAILABLE(channel)) {
+                       do_channel_rx(&pport->ssi_channel[channel]);
+                       channels_served |= SSI_SSR_DATAAVAILABLE(channel);
+               }
+       }
+
+       if (status_reg & SSI_BREAKDETECTED) {
+               dev_info(ssi_ctrl->dev, "Hardware BREAK on port %d\n", port);
+               ssi_outl(0, base, SSI_SSR_BREAK_REG(port));
+               ssi_port_event_handler(pport, SSI_EVENT_BREAK_DETECTED, NULL);
+               channels_served |= SSI_BREAKDETECTED;
+       }
+
+       if (status_reg & SSI_ERROROCCURED) {
+               ssr_err_reg = ssi_inl(base, SSI_SSR_ERROR_REG(port));
+               dev_err(ssi_ctrl->dev, "SSI ERROR Port %d: 0x%02x\n",
+                                                       port, ssr_err_reg);
+               ssi_outl(ssr_err_reg, base, SSI_SSR_ERRORACK_REG(port));
+               if (ssr_err_reg) /* Ignore spurios errors */
+                       ssi_port_event_handler(pport, SSI_EVENT_ERROR, NULL);
+               else
+                       dev_dbg(ssi_ctrl->dev, "spurious SSI error!\n");
+
+               channels_served |= SSI_ERROROCCURED;
+       }
+
+       ssi_outl(channels_served, base, SSI_SYS_MPU_STATUS_REG(port, irq));
+
+       status_reg = ssi_inl(base, SSI_SYS_MPU_STATUS_REG(port, irq));
+       status_reg &= ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, irq));
+
+       clk_disable(ssi_ctrl->ssi_clk);
+
+       if (status_reg)
+               tasklet_hi_schedule(&pport->ssi_tasklet);
+       else
+               enable_irq(pport->irq);
+}
+
+static irqreturn_t ssi_mpu_handler(int irq, void *ssi_port)
+{
+       struct ssi_port *p = ssi_port;
+
+       tasklet_hi_schedule(&p->ssi_tasklet);
+       disable_irq_nosync(p->irq);
+
+       return IRQ_HANDLED;
+}
+
+int __init ssi_mpu_init(struct ssi_port *ssi_p, const char *irq_name)
+{
+       int err;
+
+       tasklet_init(&ssi_p->ssi_tasklet, do_ssi_tasklet,
+                                                       (unsigned long)ssi_p);
+       err = request_irq(ssi_p->irq, ssi_mpu_handler, IRQF_DISABLED,
+                                                       irq_name, ssi_p);
+       if (err < 0) {
+               dev_err(ssi_p->ssi_controller->dev, "FAILED to MPU request"
+                       " IRQ (%d) on port %d", ssi_p->irq, ssi_p->port_number);
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
+void ssi_mpu_exit(struct ssi_port *ssi_p)
+{
+       tasklet_disable(&ssi_p->ssi_tasklet);
+       free_irq(ssi_p->irq, ssi_p);
+}