aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/modem_if/modem_link_device_pld.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/modem_if/modem_link_device_pld.c')
-rw-r--r--drivers/misc/modem_if/modem_link_device_pld.c1981
1 files changed, 1981 insertions, 0 deletions
diff --git a/drivers/misc/modem_if/modem_link_device_pld.c b/drivers/misc/modem_if/modem_link_device_pld.c
new file mode 100644
index 0000000..b68040e
--- /dev/null
+++ b/drivers/misc/modem_if/modem_link_device_pld.c
@@ -0,0 +1,1981 @@
+/*
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/wakelock.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/if_arp.h>
+#include <linux/platform_device.h>
+#include <linux/kallsyms.h>
+#include <linux/platform_data/modem.h>
+
+#include "modem_prj.h"
+#include "modem_link_device_pld.h"
+#include "modem_utils.h"
+
+
+/*
+** Function prototypes for basic DPRAM operations
+*/
+static inline void clear_intr(struct dpram_link_device *dpld);
+static inline u16 recv_intr(struct dpram_link_device *dpld);
+static inline void send_intr(struct dpram_link_device *dpld, u16 mask);
+
+static inline u16 get_magic(struct dpram_link_device *dpld);
+static inline void set_magic(struct dpram_link_device *dpld, u16 val);
+static inline u16 get_access(struct dpram_link_device *dpld);
+static inline void set_access(struct dpram_link_device *dpld, u16 val);
+
+static inline u32 get_tx_head(struct dpram_link_device *dpld, int id);
+static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id);
+static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in);
+static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out);
+static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id);
+static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id);
+
+static inline u32 get_rx_head(struct dpram_link_device *dpld, int id);
+static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id);
+static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in);
+static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out);
+static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id);
+static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id);
+
+static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id);
+static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id);
+static inline u16 get_mask_send(struct dpram_link_device *dpld, int id);
+
+static inline bool dpram_circ_valid(u32 size, u32 in, u32 out);
+
+static void handle_cp_crash(struct dpram_link_device *dpld);
+static int trigger_force_cp_crash(struct dpram_link_device *dpld);
+
+/*
+** Functions for debugging
+*/
+static inline void log_dpram_status(struct dpram_link_device *dpld)
+{
+ pr_info("mif: %s: {M:0x%X A:%d} {FMT TI:%u TO:%u RI:%u RO:%u} "
+ "{RAW TI:%u TO:%u RI:%u RO:%u} {INT:0x%X}\n",
+ dpld->ld.mc->name,
+ get_magic(dpld), get_access(dpld),
+ get_tx_head(dpld, IPC_FMT), get_tx_tail(dpld, IPC_FMT),
+ get_rx_head(dpld, IPC_FMT), get_rx_tail(dpld, IPC_FMT),
+ get_tx_head(dpld, IPC_RAW), get_tx_tail(dpld, IPC_RAW),
+ get_rx_head(dpld, IPC_RAW), get_rx_tail(dpld, IPC_RAW),
+ recv_intr(dpld));
+}
+
+static void set_dpram_map(struct dpram_link_device *dpld,
+ struct mif_irq_map *map)
+{
+ map->magic = get_magic(dpld);
+ map->access = get_access(dpld);
+
+ map->fmt_tx_in = get_tx_head(dpld, IPC_FMT);
+ map->fmt_tx_out = get_tx_tail(dpld, IPC_FMT);
+ map->fmt_rx_in = get_rx_head(dpld, IPC_FMT);
+ map->fmt_rx_out = get_rx_tail(dpld, IPC_FMT);
+ map->raw_tx_in = get_tx_head(dpld, IPC_RAW);
+ map->raw_tx_out = get_tx_tail(dpld, IPC_RAW);
+ map->raw_rx_in = get_rx_head(dpld, IPC_RAW);
+ map->raw_rx_out = get_rx_tail(dpld, IPC_RAW);
+
+ map->cp2ap = recv_intr(dpld);
+}
+
+/*
+** RXB (DPRAM RX buffer) functions
+*/
+static struct dpram_rxb *rxbq_create_pool(unsigned size, int count)
+{
+ struct dpram_rxb *rxb;
+ u8 *buff;
+ int i;
+
+ rxb = kzalloc(sizeof(struct dpram_rxb) * count, GFP_KERNEL);
+ if (!rxb) {
+ mif_info("ERR! kzalloc rxb fail\n");
+ return NULL;
+ }
+
+ buff = kzalloc((size * count), GFP_KERNEL|GFP_DMA);
+ if (!buff) {
+ mif_info("ERR! kzalloc buff fail\n");
+ kfree(rxb);
+ return NULL;
+ }
+
+ for (i = 0; i < count; i++) {
+ rxb[i].buff = buff;
+ rxb[i].size = size;
+ buff += size;
+ }
+
+ return rxb;
+}
+
+static inline unsigned rxbq_get_page_size(unsigned len)
+{
+ return ((len + PAGE_SIZE - 1) >> PAGE_SHIFT) << PAGE_SHIFT;
+}
+
+static inline bool rxbq_empty(struct dpram_rxb_queue *rxbq)
+{
+ return (rxbq->in == rxbq->out) ? true : false;
+}
+
+static inline int rxbq_free_size(struct dpram_rxb_queue *rxbq)
+{
+ int in = rxbq->in;
+ int out = rxbq->out;
+ int qsize = rxbq->size;
+ return (in < out) ? (out - in - 1) : (qsize + out - in - 1);
+}
+
+static inline struct dpram_rxb *rxbq_get_free_rxb(struct dpram_rxb_queue *rxbq)
+{
+ struct dpram_rxb *rxb = NULL;
+
+ if (likely(rxbq_free_size(rxbq) > 0)) {
+ rxb = &rxbq->rxb[rxbq->in];
+ rxbq->in++;
+ if (rxbq->in >= rxbq->size)
+ rxbq->in -= rxbq->size;
+ rxb->data = rxb->buff;
+ }
+
+ return rxb;
+}
+
+static inline int rxbq_size(struct dpram_rxb_queue *rxbq)
+{
+ int in = rxbq->in;
+ int out = rxbq->out;
+ int qsize = rxbq->size;
+ return (in >= out) ? (in - out) : (qsize - out + in);
+}
+
+static inline struct dpram_rxb *rxbq_get_data_rxb(struct dpram_rxb_queue *rxbq)
+{
+ struct dpram_rxb *rxb = NULL;
+
+ if (likely(!rxbq_empty(rxbq))) {
+ rxb = &rxbq->rxb[rxbq->out];
+ rxbq->out++;
+ if (rxbq->out >= rxbq->size)
+ rxbq->out -= rxbq->size;
+ }
+
+ return rxb;
+}
+
+static inline u8 *rxb_put(struct dpram_rxb *rxb, unsigned len)
+{
+ rxb->len = len;
+ return rxb->data;
+}
+
+static inline void rxb_clear(struct dpram_rxb *rxb)
+{
+ rxb->data = NULL;
+ rxb->len = 0;
+}
+
+/*
+** DPRAM operations
+*/
+static int dpram_register_isr(unsigned irq, irqreturn_t (*isr)(int, void*),
+ unsigned long flag, const char *name,
+ struct dpram_link_device *dpld)
+{
+ int ret = 0;
+
+ ret = request_irq(irq, isr, flag, name, dpld);
+ if (ret) {
+ mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret);
+ return ret;
+ }
+
+ ret = enable_irq_wake(irq);
+ if (ret)
+ mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret);
+
+ mif_info("%s (#%d) handler registered\n", name, irq);
+
+ return 0;
+}
+
+static inline void clear_intr(struct dpram_link_device *dpld)
+{
+ if (dpld->dpctl->clear_intr)
+ dpld->dpctl->clear_intr();
+}
+
+static inline u16 recv_intr(struct dpram_link_device *dpld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->mbx2ap[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&dpld->mbx2ap[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: intr1(%d) != intr1(%d)\n", val1, val2);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+
+ return val1;
+}
+
+static inline void send_intr(struct dpram_link_device *dpld, u16 mask)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&dpld->mbx2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)mask, dpld->dp_base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->mbx2cp[0]),
+ dpld->address_buffer);
+ val = ioread16(dpld->dp_base);
+
+ if (likely(val == mask)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: intr1(%d) != intr2(%d)\n", val, mask);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&dpld->mbx2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)mask, dpld->dp_base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+
+ return;
+}
+
+static inline u16 get_magic(struct dpram_link_device *dpld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->magic_ap2cp[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&dpld->magic_ap2cp[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: txq.head(%d) != in(%d)\n", val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+
+}
+
+static inline void set_magic(struct dpram_link_device *dpld, u16 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&dpld->magic_ap2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->magic_ap2cp[0]),
+ dpld->address_buffer);
+ val = ioread16(dpld->dp_base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: magic1(%d) != magic2(%d)\n", val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&dpld->magic_ap2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+}
+
+static inline u16 get_access(struct dpram_link_device *dpld)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->access_ap2cp[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&dpld->access_ap2cp[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: access1(%d) != access2(%d)\n", val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+
+}
+
+static inline void set_access(struct dpram_link_device *dpld, u16 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ iowrite16(PLD_ADDR_MASK(&dpld->access_ap2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&dpld->access_ap2cp[0]),
+ dpld->address_buffer);
+ val = ioread16(dpld->dp_base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: access(%d) != access(%d)\n", val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&dpld->access_ap2cp[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+}
+
+static inline u32 get_tx_head(struct dpram_link_device *dpld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.head)[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.head)[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s txq.head(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+}
+
+static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.tail)[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.tail)[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s txq.tail(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+}
+
+static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.head)[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.head)[0]),
+ dpld->address_buffer);
+ val = ioread16(dpld->dp_base);
+
+ if (likely(val == in)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: %s txq.head(%d) != in(%d)\n",
+ get_dev_name(id), val, in);
+ udelay(100);
+
+ /* Write head value again */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->txq.head)[0]),
+ dpld->address_buffer);
+ iowrite16((u16)in, dpld->dp_base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+}
+
+static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out)
+{
+ return;
+}
+
+static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->txq.buff;
+}
+
+static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->txq.size;
+}
+
+static inline u32 get_rx_head(struct dpram_link_device *dpld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.head)[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.head)[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s rxq.head(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+}
+
+static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id)
+{
+ u16 val1 = 0, val2 = 0, cnt = 3;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ do {
+ /* Check head value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.tail)[0]),
+ dpld->address_buffer);
+ val1 = ioread16(dpld->dp_base);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.tail)[0]),
+ dpld->address_buffer);
+ val2 = ioread16(dpld->dp_base);
+
+ if (likely(val1 == val2)) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+ }
+
+ mif_err("ERR: %s rxq.tail(%d) != in(%d)\n",
+ get_dev_name(id), val1, val2);
+ udelay(100);
+
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return val1;
+}
+
+static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in)
+{
+ return;
+}
+
+static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out)
+{
+ int cnt = 3;
+ u32 val = 0;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->pld_lock, flags);
+
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.tail)[0]),
+ dpld->address_buffer);
+ iowrite16((u16)out, dpld->dp_base);
+
+ do {
+ /* Check tail value written */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.tail)[0]),
+ dpld->address_buffer);
+ val = ioread16(dpld->dp_base);
+
+ if (val == out) {
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+ }
+
+ mif_err("ERR: %s rxq.tail(%d) != out(%d)\n",
+ get_dev_name(id), val, out);
+ udelay(100);
+
+ /* Write tail value again */
+ iowrite16(PLD_ADDR_MASK(&(dpld->dev[id]->rxq.tail)[0]),
+ dpld->address_buffer);
+ iowrite16((u16)out, dpld->dp_base);
+ } while (cnt--);
+
+ spin_unlock_irqrestore(&dpld->pld_lock, flags);
+ return;
+}
+
+static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->rxq.buff;
+}
+
+static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->rxq.size;
+}
+
+static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_req_ack;
+}
+
+static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_res_ack;
+}
+
+static inline u16 get_mask_send(struct dpram_link_device *dpld, int id)
+{
+ return dpld->dev[id]->mask_send;
+}
+
+static inline bool dpram_circ_valid(u32 size, u32 in, u32 out)
+{
+ if (in >= size)
+ return false;
+
+ if (out >= size)
+ return false;
+
+ return true;
+}
+
+/* Get free space in the TXQ as well as in & out pointers */
+static inline int dpram_get_txq_space(struct dpram_link_device *dpld, int dev,
+ u32 qsize, u32 *in, u32 *out)
+{
+ struct link_device *ld = &dpld->ld;
+ int cnt = 3;
+ u32 head;
+ u32 tail;
+ int space;
+
+ do {
+ head = get_tx_head(dpld, dev);
+ tail = get_tx_tail(dpld, dev);
+
+ space = (head < tail) ? (tail - head - 1) :
+ (qsize + tail - head - 1);
+ mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, space);
+
+ if (dpram_circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return space;
+ }
+
+ mif_info("%s: CAUTION! <%pf> "
+ "%s_TXQ invalid (size:%d in:%d out:%d)\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail);
+
+ udelay(100);
+ } while (cnt--);
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+static void dpram_reset_tx_circ(struct dpram_link_device *dpld, int dev)
+{
+ set_tx_head(dpld, dev, 0);
+ set_tx_tail(dpld, dev, 0);
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(dpld);
+}
+
+/* Get data size in the RXQ as well as in & out pointers */
+static inline int dpram_get_rxq_rcvd(struct dpram_link_device *dpld, int dev,
+ u32 qsize, u32 *in, u32 *out)
+{
+ struct link_device *ld = &dpld->ld;
+ int cnt = 3;
+ u32 head;
+ u32 tail;
+ u32 rcvd;
+
+ do {
+ head = get_rx_head(dpld, dev);
+ tail = get_rx_tail(dpld, dev);
+ if (head == tail) {
+ *in = head;
+ *out = tail;
+ return 0;
+ }
+
+ rcvd = (head > tail) ? (head - tail) : (qsize - tail + head);
+ mif_info("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n",
+ ld->name, get_dev_name(dev), qsize, head, tail, rcvd);
+
+ if (dpram_circ_valid(qsize, head, tail)) {
+ *in = head;
+ *out = tail;
+ return rcvd;
+ }
+
+ mif_info("%s: CAUTION! <%pf> "
+ "%s_RXQ invalid (size:%d in:%d out:%d)\n",
+ ld->name, __builtin_return_address(0),
+ get_dev_name(dev), qsize, head, tail);
+
+ udelay(100);
+ } while (cnt--);
+
+ *in = 0;
+ *out = 0;
+ return -EINVAL;
+}
+
+static void dpram_reset_rx_circ(struct dpram_link_device *dpld, int dev)
+{
+ set_rx_head(dpld, dev, 0);
+ set_rx_tail(dpld, dev, 0);
+ if (dev == IPC_FMT)
+ trigger_force_cp_crash(dpld);
+}
+
+/*
+** CAUTION : dpram_allow_sleep() MUST be invoked after dpram_wake_up() success
+*/
+static int dpram_wake_up(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (!dpld->dpctl->wakeup)
+ return 0;
+
+ if (dpld->dpctl->wakeup() < 0) {
+ mif_err("%s: ERR! <%pf> DPRAM wakeup fail\n",
+ ld->name, __builtin_return_address(0));
+ return -EACCES;
+ }
+
+ atomic_inc(&dpld->accessing);
+ return 0;
+}
+
+static void dpram_allow_sleep(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (!dpld->dpctl->sleep)
+ return;
+
+ if (atomic_dec_return(&dpld->accessing) <= 0) {
+ dpld->dpctl->sleep();
+ atomic_set(&dpld->accessing, 0);
+ mif_debug("%s: DPRAM sleep possible\n", ld->name);
+ }
+}
+
+static int dpram_check_access(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ int i;
+ u16 magic = get_magic(dpld);
+ u16 access = get_access(dpld);
+
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+
+ for (i = 1; i <= 100; i++) {
+ mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n",
+ ld->name, magic, access, i);
+ udelay(100);
+
+ magic = get_magic(dpld);
+ access = get_access(dpld);
+ if (likely(magic == DPRAM_MAGIC_CODE && access == 1))
+ return 0;
+ }
+
+ mif_info("%s: !CRISIS! magic:%X access:%X\n", ld->name, magic, access);
+ return -EACCES;
+}
+
+static bool dpram_ipc_active(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ /* Check DPRAM mode */
+ if (ld->mode != LINK_MODE_IPC) {
+ mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ if (dpram_check_access(dpld) < 0) {
+ mif_info("%s: ERR! <%pf> dpram_check_access fail\n",
+ ld->name, __builtin_return_address(0));
+ return false;
+ }
+
+ return true;
+}
+
+static void dpram_ipc_write(struct dpram_link_device *dpld, int dev,
+ u32 qsize, u32 in, u32 out, struct sk_buff *skb)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 __iomem *buff = get_tx_buff(dpld, dev);
+ u8 *src = skb->data;
+ u32 len = skb->len;
+ u32 inp;
+ struct mif_irq_map map;
+
+ if (in < out) {
+ /* +++++++++ in ---------- out ++++++++++ */
+ iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), dpld->address_buffer);
+ memcpy(dpld->dp_base, src, len);
+ } else {
+ /* ------ out +++++++++++ in ------------ */
+ u32 space = qsize - in;
+
+ /* 1) in -> buffer end */
+ iowrite16(PLD_ADDR_MASK(&(buff+in)[0]), dpld->address_buffer);
+ memcpy(dpld->dp_base, src, ((len > space) ? space : len));
+
+ if (len > space) {
+ iowrite16(PLD_ADDR_MASK(&buff[0]),
+ dpld->address_buffer);
+ memcpy(dpld->dp_base, (src+space), (len-space));
+ }
+ }
+
+ /* update new in pointer */
+ inp = in + len;
+ if (inp >= qsize)
+ inp -= qsize;
+ set_tx_head(dpld, dev, inp);
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write"));
+ mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len);
+ }
+}
+
+static int dpram_try_ipc_tx(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ struct sk_buff *skb;
+ unsigned long int flags;
+ u32 qsize = get_tx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ int space;
+ int copied = 0;
+
+ spin_lock_irqsave(&dpld->tx_rx_lock, flags);
+
+ while (1) {
+ space = dpram_get_txq_space(dpld, dev, qsize, &in, &out);
+ if (unlikely(space < 0)) {
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+ dpram_reset_tx_circ(dpld, dev);
+ return space;
+ }
+
+ skb = skb_dequeue(txq);
+ if (unlikely(!skb))
+ break;
+
+ if (unlikely(space < skb->len)) {
+ atomic_set(&dpld->res_required[dev], 1);
+ skb_queue_head(txq, skb);
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+ mif_info("%s: %s "
+ "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n",
+ ld->name, get_dev_name(dev),
+ qsize, in, out, space, skb->len);
+ return -ENOSPC;
+ }
+
+ /* TX if there is enough room in the queue */
+ dpram_ipc_write(dpld, dev, qsize, in, out, skb);
+ copied += skb->len;
+ dev_kfree_skb_any(skb);
+ }
+
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+
+ return copied;
+}
+
+static void dpram_ipc_rx_task(unsigned long data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod;
+ struct dpram_rxb *rxb;
+ unsigned qlen;
+ int i;
+
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ iod = dpld->iod[i];
+ qlen = rxbq_size(&dpld->rxbq[i]);
+ while (qlen > 0) {
+ rxb = rxbq_get_data_rxb(&dpld->rxbq[i]);
+ iod->recv(iod, ld, rxb->data, rxb->len);
+ rxb_clear(rxb);
+ qlen--;
+ }
+ }
+}
+
+static void dpram_ipc_read(struct dpram_link_device *dpld, int dev, u8 *dst,
+ u8 __iomem *src, u32 out, u32 len, u32 qsize)
+{
+ u8 *ori_det = dst;
+ unsigned long flags;
+
+ if ((out + len) <= qsize) {
+ /* ----- (out) (in) ----- */
+ /* ----- 7f 00 00 7e ----- */
+ iowrite16(PLD_ADDR_MASK(&(src+out)[0]), dpld->address_buffer);
+ memcpy(dst, dpld->dp_base, len);
+ } else {
+ /* (in) ----------- (out) */
+ /* 00 7e ----------- 7f 00 */
+ unsigned len1 = qsize - out;
+
+ /* 1) out -> buffer end */
+ iowrite16(PLD_ADDR_MASK(&(src+out)[0]), dpld->address_buffer);
+ memcpy(dst, dpld->dp_base, len1);
+
+ /* 2) buffer start -> in */
+ dst += len1;
+ iowrite16(PLD_ADDR_MASK(&src[0]), dpld->address_buffer);
+ memcpy(dst, dpld->dp_base, (len - len1));
+ }
+
+ if (dpld->ld.mode == LINK_MODE_IPC && ori_det[0] != 0x7F) {
+ mif_info("ipc read error!! in[%d], out[%d]\n",
+ get_rx_head(dpld, dev),
+ get_rx_tail(dpld, dev));
+ }
+
+}
+
+/*
+ ret < 0 : error
+ ret == 0 : no data
+ ret > 0 : valid data
+*/
+static int dpram_ipc_recv_data_with_rxb(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct dpram_rxb *rxb;
+ u8 __iomem *src = get_rx_buff(dpld, dev);
+ u32 qsize = get_rx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ u32 rcvd;
+ struct mif_irq_map map;
+ unsigned long int flags;
+
+ spin_lock_irqsave(&dpld->tx_rx_lock, flags);
+
+ rcvd = dpram_get_rxq_rcvd(dpld, dev, qsize, &in, &out);
+ if (rcvd <= 0) {
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+ return rcvd;
+ }
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv"));
+ }
+
+ /* Allocate an rxb */
+ rxb = rxbq_get_free_rxb(&dpld->rxbq[dev]);
+ if (!rxb) {
+ mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n",
+ ld->name, get_dev_name(dev));
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+ return -ENOMEM;
+ }
+
+ /* Read data from each DPRAM buffer */
+ dpram_ipc_read(dpld, dev, rxb_put(rxb, rcvd), src, out, rcvd, qsize);
+
+ /* Calculate and set new out */
+ out += rcvd;
+ if (out >= qsize)
+ out -= qsize;
+ set_rx_tail(dpld, dev, out);
+
+ spin_unlock_irqrestore(&dpld->tx_rx_lock, flags);
+ return rcvd;
+}
+
+/*
+ ret < 0 : error
+ ret == 0 : no data
+ ret > 0 : valid data
+*/
+static int dpram_ipc_recv_data_with_skb(struct dpram_link_device *dpld, int dev)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = dpld->iod[dev];
+ struct sk_buff *skb;
+ u8 __iomem *src = get_rx_buff(dpld, dev);
+ u32 qsize = get_rx_buff_size(dpld, dev);
+ u32 in;
+ u32 out;
+ u32 rcvd;
+ int rest;
+ u8 *frm;
+ u8 *dst;
+ unsigned int len;
+ unsigned int pad;
+ unsigned int tot;
+ struct mif_irq_map map;
+
+ rcvd = dpram_get_rxq_rcvd(dpld, dev, qsize, &in, &out);
+ if (rcvd <= 0)
+ return rcvd;
+
+ if (dev == IPC_FMT) {
+ set_dpram_map(dpld, &map);
+ mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv"));
+ }
+
+ rest = rcvd;
+ while (rest > 0) {
+ frm = src + out;
+ if (unlikely(!sipc5_start_valid(frm[0]))) {
+ mif_err("%s: ERR! %s invalid start 0x%02X\n",
+ ld->name, get_dev_name(dev), frm[0]);
+ skb_queue_purge(&dpld->skb_rxq[dev]);
+ return -EBADMSG;
+ }
+
+ len = sipc5_get_frame_sz16(frm);
+ if (unlikely(len > rest)) {
+ mif_err("%s: ERR! %s len %d > rest %d\n",
+ ld->name, get_dev_name(dev), len, rest);
+ skb_queue_purge(&dpld->skb_rxq[dev]);
+ return -EBADMSG;
+ }
+
+ pad = sipc5_calc_padding_size(len);
+ tot = len + pad;
+
+ /* Allocate an skb */
+ skb = dev_alloc_skb(tot);
+ if (!skb) {
+ mif_err("%s: ERR! %s dev_alloc_skb fail\n",
+ ld->name, get_dev_name(dev));
+ return -ENOMEM;
+ }
+
+ /* Read data from each DPRAM buffer */
+ dst = skb_put(skb, tot);
+ dpram_ipc_read(dpld, dev, dst, src, out, tot, qsize);
+ skb_trim(skb, len);
+ iod->recv_skb(iod, ld, skb);
+
+ /* Calculate and set new out */
+ rest -= tot;
+ out += tot;
+ if (out >= qsize)
+ out -= qsize;
+ }
+
+ set_rx_tail(dpld, dev, out);
+
+ return rcvd;
+}
+
+static void non_command_handler(struct dpram_link_device *dpld, u16 non_cmd)
+{
+ struct link_device *ld = &dpld->ld;
+ int i = 0;
+ int ret = 0;
+ u16 tx_mask = 0;
+
+ if (!dpram_ipc_active(dpld))
+ return;
+
+ /* Read data from DPRAM */
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ if (dpld->use_skb)
+ ret = dpram_ipc_recv_data_with_skb(dpld, i);
+ else
+ ret = dpram_ipc_recv_data_with_rxb(dpld, i);
+ if (ret < 0)
+ dpram_reset_rx_circ(dpld, i);
+
+ /* Check and process REQ_ACK (at this time, in == out) */
+ if (non_cmd & get_mask_req_ack(dpld, i)) {
+ mif_debug("%s: send %s_RES_ACK\n",
+ ld->name, get_dev_name(i));
+ tx_mask |= get_mask_res_ack(dpld, i);
+ }
+ }
+
+ if (!dpld->use_skb) {
+ /* Schedule soft IRQ for RX */
+ tasklet_hi_schedule(&dpld->rx_tsk);
+ }
+
+ /* Try TX via DPRAM */
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ if (atomic_read(&dpld->res_required[i]) > 0) {
+ ret = dpram_try_ipc_tx(dpld, i);
+ if (ret > 0) {
+ atomic_set(&dpld->res_required[i], 0);
+ tx_mask |= get_mask_send(dpld, i);
+ } else if (ret == -ENOSPC) {
+ tx_mask |= get_mask_req_ack(dpld, i);
+ }
+ }
+ }
+
+ if (tx_mask) {
+ send_intr(dpld, INT_NON_CMD(tx_mask));
+ mif_debug("%s: send intr 0x%04X\n", ld->name, tx_mask);
+ }
+}
+
+static void handle_cp_crash(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod;
+ int i;
+
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ mif_info("%s: purging %s_skb_txq\b", ld->name, get_dev_name(i));
+ skb_queue_purge(ld->skb_txq[i]);
+ }
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_EXIT);
+
+ iod = link_get_iod_with_channel(ld, PS_DATA_CH_0);
+ if (iod)
+ iodevs_for_each(iod->msd, iodev_netif_stop, 0);
+}
+
+static void handle_no_crash_ack(unsigned long arg)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)arg;
+ struct link_device *ld = &dpld->ld;
+
+ mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->mc->name);
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ handle_cp_crash(dpld);
+}
+
+static int trigger_force_cp_crash(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (ld->mode == LINK_MODE_ULOAD) {
+ mif_err("%s: CP crash is already in progress\n", ld->mc->name);
+ return 0;
+ }
+
+ ld->mode = LINK_MODE_ULOAD;
+ mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0));
+
+ dpram_wake_up(dpld);
+
+ send_intr(dpld, INT_CMD(INT_CMD_CRASH_EXIT));
+
+ mif_add_timer(&dpld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT,
+ handle_no_crash_ack, (unsigned long)dpld);
+
+ return 0;
+}
+
+static int dpram_init_ipc(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ int i;
+
+ if (ld->mode == LINK_MODE_IPC &&
+ get_magic(dpld) == DPRAM_MAGIC_CODE &&
+ get_access(dpld) == 1)
+ mif_info("%s: IPC already initialized\n", ld->name);
+
+ /* Clear pointers in every circular queue */
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ set_tx_head(dpld, i, 0);
+ set_tx_tail(dpld, i, 0);
+ set_rx_head(dpld, i, 0);
+ set_rx_tail(dpld, i, 0);
+ }
+
+ /* Initialize variables for efficient TX/RX processing */
+ for (i = 0; i < dpld->max_ipc_dev; i++)
+ dpld->iod[i] = link_get_iod_with_format(ld, i);
+ dpld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW);
+
+ if (dpld->iod[IPC_RAW]->recv_skb)
+ dpld->use_skb = true;
+
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ atomic_set(&dpld->res_required[i], 0);
+ skb_queue_purge(&dpld->skb_rxq[i]);
+ }
+
+ spin_lock_init(&dpld->tx_rx_lock);
+
+ /* Enable IPC */
+ atomic_set(&dpld->accessing, 0);
+
+ set_magic(dpld, DPRAM_MAGIC_CODE);
+ set_access(dpld, 1);
+ if (get_magic(dpld) != DPRAM_MAGIC_CODE || get_access(dpld) != 1)
+ return -EACCES;
+
+ ld->mode = LINK_MODE_IPC;
+
+ if (wake_lock_active(&dpld->wlock))
+ wake_unlock(&dpld->wlock);
+
+ return 0;
+}
+
+static void cmd_req_active_handler(struct dpram_link_device *dpld)
+{
+ send_intr(dpld, INT_CMD(INT_CMD_RES_ACTIVE));
+}
+
+static void cmd_crash_reset_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = NULL;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+
+ iod = link_get_iod_with_format(ld, IPC_BOOT);
+ iod->modem_state_changed(iod, STATE_CRASH_RESET);
+}
+
+static void cmd_crash_exit_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+
+ ld->mode = LINK_MODE_ULOAD;
+
+ if (!wake_lock_active(&dpld->wlock))
+ wake_lock(&dpld->wlock);
+
+ mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name);
+
+ dpram_wake_up(dpld);
+
+ del_timer(&dpld->crash_ack_timer);
+
+ if (dpld->ext_op && dpld->ext_op->crash_log)
+ dpld->ext_op->crash_log(dpld);
+
+ handle_cp_crash(dpld);
+}
+
+static void cmd_phone_start_handler(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ struct io_device *iod = NULL;
+
+ mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name);
+
+ dpram_init_ipc(dpld);
+
+ iod = link_get_iod_with_format(ld, IPC_FMT);
+ if (!iod) {
+ mif_info("%s: ERR! no iod\n", ld->name);
+ return;
+ }
+
+ if (dpld->ext_op && dpld->ext_op->cp_start_handler)
+ dpld->ext_op->cp_start_handler(dpld);
+
+ if (ld->mc->phone_state != STATE_ONLINE) {
+ mif_info("%s: phone_state: %d -> ONLINE\n",
+ ld->name, ld->mc->phone_state);
+ iod->modem_state_changed(iod, STATE_ONLINE);
+ }
+
+ mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name);
+ send_intr(dpld, INT_CMD(INT_CMD_INIT_END));
+}
+
+static void command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+
+ switch (INT_CMD_MASK(cmd)) {
+ case INT_CMD_REQ_ACTIVE:
+ cmd_req_active_handler(dpld);
+ break;
+
+ case INT_CMD_CRASH_RESET:
+ dpld->dpram_init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_reset_handler(dpld);
+ break;
+
+ case INT_CMD_CRASH_EXIT:
+ dpld->dpram_init_status = DPRAM_INIT_STATE_NONE;
+ cmd_crash_exit_handler(dpld);
+ break;
+
+ case INT_CMD_PHONE_START:
+ dpld->dpram_init_status = DPRAM_INIT_STATE_READY;
+ cmd_phone_start_handler(dpld);
+ complete_all(&dpld->dpram_init_cmd);
+ break;
+
+ case INT_CMD_NV_REBUILDING:
+ mif_info("%s: NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_PIF_INIT_DONE:
+ complete_all(&dpld->modem_pif_init_done);
+ break;
+
+ case INT_CMD_SILENT_NV_REBUILDING:
+ mif_info("%s: SILENT_NV_REBUILDING\n", ld->name);
+ break;
+
+ case INT_CMD_NORMAL_PWR_OFF:
+ /*ToDo:*/
+ /*kernel_sec_set_cp_ack()*/;
+ break;
+
+ case INT_CMD_REQ_TIME_SYNC:
+ case INT_CMD_CP_DEEP_SLEEP:
+ case INT_CMD_EMER_DOWN:
+ break;
+
+ default:
+ mif_info("%s: unknown command 0x%04X\n", ld->name, cmd);
+ }
+}
+
+static void ext_command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+ u16 resp;
+
+ switch (EXT_CMD_MASK(cmd)) {
+ case EXT_CMD_SET_SPEED_LOW:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_LOW);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_LOW);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ case EXT_CMD_SET_SPEED_MID:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_MID);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_MID);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ case EXT_CMD_SET_SPEED_HIGH:
+ if (dpld->dpctl->setup_speed) {
+ dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH);
+ resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_HIGH);
+ send_intr(dpld, resp);
+ }
+ break;
+
+ default:
+ mif_info("%s: unknown command 0x%04X\n", ld->name, cmd);
+ break;
+ }
+}
+
+static void udl_command_handler(struct dpram_link_device *dpld, u16 cmd)
+{
+ struct link_device *ld = &dpld->ld;
+
+ if (cmd & UDL_RESULT_FAIL) {
+ mif_info("%s: ERR! Command failed: %04x\n", ld->name, cmd);
+ return;
+ }
+
+ switch (UDL_CMD_MASK(cmd)) {
+ case UDL_CMD_RECV_READY:
+ mif_debug("%s: Send CP-->AP RECEIVE_READY\n", ld->name);
+ send_intr(dpld, CMD_IMG_START_REQ);
+ break;
+ default:
+ complete_all(&dpld->udl_cmd_complete);
+ }
+}
+
+static irqreturn_t dpram_irq_handler(int irq, void *data)
+{
+ struct dpram_link_device *dpld = (struct dpram_link_device *)data;
+ struct link_device *ld = (struct link_device *)&dpld->ld;
+ u16 int2ap = 0;
+
+ if (unlikely(ld->mode == LINK_MODE_OFFLINE))
+ return IRQ_HANDLED;
+
+ if (dpram_wake_up(dpld) < 0) {
+ log_dpram_status(dpld);
+ trigger_force_cp_crash(dpld);
+ return IRQ_HANDLED;
+ }
+
+ int2ap = recv_intr(dpld);
+
+ if (unlikely(int2ap == INT_POWERSAFE_FAIL)) {
+ mif_info("%s: int2ap == INT_POWERSAFE_FAIL\n", ld->name);
+ goto exit;
+ } else if (int2ap == 0x1234 || int2ap == 0xDBAB || int2ap == 0xABCD) {
+ if (dpld->ext_op && dpld->ext_op->dload_cmd_handler) {
+ dpld->ext_op->dload_cmd_handler(dpld, int2ap);
+ goto exit;
+ }
+ }
+
+ if (unlikely(EXT_UDL_CMD(int2ap))) {
+ if (likely(EXT_INT_VALID(int2ap))) {
+ if (UDL_CMD_VALID(int2ap))
+ udl_command_handler(dpld, int2ap);
+ else if (EXT_CMD_VALID(int2ap))
+ ext_command_handler(dpld, int2ap);
+ else
+ mif_info("%s: ERR! invalid intr 0x%04X\n",
+ ld->name, int2ap);
+ } else {
+ mif_info("%s: ERR! invalid intr 0x%04X\n",
+ ld->name, int2ap);
+ }
+ } else {
+ if (likely(INT_VALID(int2ap))) {
+ if (unlikely(INT_CMD_VALID(int2ap)))
+ command_handler(dpld, int2ap);
+ else
+ non_command_handler(dpld, int2ap);
+ } else {
+ mif_info("%s: ERR! invalid intr 0x%04X\n",
+ ld->name, int2ap);
+ }
+ }
+
+exit:
+ clear_intr(dpld);
+ dpram_allow_sleep(dpld);
+ return IRQ_HANDLED;
+}
+
+static void dpram_send_ipc(struct link_device *ld, int dev,
+ struct io_device *iod, struct sk_buff *skb)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ struct sk_buff_head *txq = ld->skb_txq[dev];
+ int ret;
+ u16 mask;
+
+ skb_queue_tail(txq, skb);
+ if (txq->qlen > 1024) {
+ mif_debug("%s: %s txq->qlen %d > 1024\n",
+ ld->name, get_dev_name(dev), txq->qlen);
+ }
+
+ if (dpram_wake_up(dpld) < 0) {
+ trigger_force_cp_crash(dpld);
+ return;
+ }
+
+ if (!dpram_ipc_active(dpld))
+ goto exit;
+
+ if (atomic_read(&dpld->res_required[dev]) > 0) {
+ mif_debug("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev));
+ goto exit;
+ }
+
+ ret = dpram_try_ipc_tx(dpld, dev);
+ if (ret > 0) {
+ mask = get_mask_send(dpld, dev);
+ send_intr(dpld, INT_NON_CMD(mask));
+ } else if (ret == -ENOSPC) {
+ mask = get_mask_req_ack(dpld, dev);
+ send_intr(dpld, INT_NON_CMD(mask));
+ mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask);
+ } else {
+ mif_info("%s: dpram_try_ipc_tx fail (err %d)\n", ld->name, ret);
+ }
+
+exit:
+ dpram_allow_sleep(dpld);
+}
+
+static int dpram_download_bin(struct link_device *ld, struct sk_buff *skb)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ if (dpld->ext_op && dpld->ext_op->dload_bin)
+ return dpld->ext_op->dload_bin(dpld, skb);
+ else
+ return -ENODEV;
+}
+
+static int dpram_send(struct link_device *ld, struct io_device *iod,
+ struct sk_buff *skb)
+{
+ enum dev_format dev = iod->format;
+ int len = skb->len;
+
+ switch (dev) {
+ case IPC_FMT:
+ case IPC_RAW:
+ case IPC_RFS:
+ if (likely(ld->mode == LINK_MODE_IPC)) {
+ dpram_send_ipc(ld, dev, iod, skb);
+ } else {
+ mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name);
+ dev_kfree_skb_any(skb);
+ }
+ return len;
+
+ case IPC_BOOT:
+ return dpram_download_bin(ld, skb);
+
+ default:
+ mif_info("%s: ERR! no TXQ for %s\n", ld->name, iod->name);
+ dev_kfree_skb_any(skb);
+ return -ENODEV;
+ }
+}
+
+static int dpram_force_dump(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ trigger_force_cp_crash(dpld);
+ return 0;
+}
+
+static void dpram_dump_memory(struct link_device *ld, char *buff)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ u8 __iomem *base = dpld->dpctl->dp_base;
+ u32 size = dpld->dpctl->dp_size;
+
+ dpram_wake_up(dpld);
+ memcpy(buff, base, size);
+}
+
+static int dpram_dump_start(struct link_device *ld, struct io_device *iod)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ if (dpld->ext_op && dpld->ext_op->dump_start)
+ return dpld->ext_op->dump_start(dpld);
+ else
+ return -ENODEV;
+}
+
+static int dpram_dump_update(struct link_device *ld, struct io_device *iod,
+ unsigned long arg)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+
+ if (dpld->ext_op && dpld->ext_op->dump_update)
+ return dpld->ext_op->dump_update(dpld, (void *)arg);
+ else
+ return -ENODEV;
+}
+
+static int dpram_ioctl(struct link_device *ld, struct io_device *iod,
+ unsigned int cmd, unsigned long arg)
+{
+ struct dpram_link_device *dpld = to_dpram_link_device(ld);
+ int err = 0;
+
+/*
+ mif_info("%s: cmd 0x%08X\n", ld->name, cmd);
+*/
+
+ switch (cmd) {
+ case IOCTL_DPRAM_INIT_STATUS:
+ mif_debug("%s: get dpram init status\n", ld->name);
+ return dpld->dpram_init_status;
+
+ default:
+ if (dpld->ext_ioctl) {
+ err = dpld->ext_ioctl(dpld, iod, cmd, arg);
+ } else {
+ mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
+ err = -EINVAL;
+ }
+
+ break;
+ }
+
+ return err;
+}
+
+static int dpram_table_init(struct dpram_link_device *dpld)
+{
+ struct link_device *ld = &dpld->ld;
+ u8 __iomem *dp_base;
+ int i;
+
+ if (!dpld->dp_base) {
+ mif_info("%s: ERR! dpld->dp_base == NULL\n", ld->name);
+ return -EINVAL;
+ }
+ dp_base = dpld->dp_base;
+
+ /* Map for IPC */
+ if (dpld->dpctl->ipc_map) {
+ memcpy(&dpld->ipc_map, dpld->dpctl->ipc_map,
+ sizeof(struct dpram_ipc_map));
+ }
+
+ dpld->magic_ap2cp = dpld->ipc_map.magic_ap2cp;
+ dpld->access_ap2cp = dpld->ipc_map.access_ap2cp;
+
+ dpld->magic_cp2ap = dpld->ipc_map.magic_cp2ap;
+ dpld->access_cp2ap = dpld->ipc_map.access_cp2ap;
+
+ dpld->address_buffer = dpld->ipc_map.address_buffer;
+
+ for (i = 0; i < dpld->max_ipc_dev; i++)
+ dpld->dev[i] = &dpld->ipc_map.dev[i];
+ dpld->mbx2ap = dpld->ipc_map.mbx_cp2ap;
+ dpld->mbx2cp = dpld->ipc_map.mbx_ap2cp;
+
+ /* Map for booting */
+ if (dpld->ext_op && dpld->ext_op->init_boot_map) {
+ dpld->ext_op->init_boot_map(dpld);
+ } else {
+ dpld->bt_map.magic = (u32 *)(dp_base);
+ dpld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET);
+ dpld->bt_map.size = dpld->dp_size - 8;
+ }
+
+ /* Map for download (FOTA, UDL, etc.) */
+ if (dpld->ext_op && dpld->ext_op->init_dl_map) {
+ dpld->ext_op->init_dl_map(dpld);
+ } else {
+ dpld->dl_map.magic = (u32 *)(dp_base);
+ dpld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET);
+ }
+
+ /* Map for upload mode */
+ if (dpld->ext_op && dpld->ext_op->init_ul_map) {
+ dpld->ext_op->init_ul_map(dpld);
+ } else {
+ dpld->ul_map.magic = (u32 *)(dp_base);
+ dpld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET);
+ }
+
+ return 0;
+}
+
+static void dpram_setup_common_op(struct dpram_link_device *dpld)
+{
+ dpld->clear_intr = clear_intr;
+ dpld->recv_intr = recv_intr;
+ dpld->send_intr = send_intr;
+ dpld->get_magic = get_magic;
+ dpld->set_magic = set_magic;
+ dpld->get_access = get_access;
+ dpld->set_access = set_access;
+ dpld->get_tx_head = get_tx_head;
+ dpld->get_tx_tail = get_tx_tail;
+ dpld->set_tx_head = set_tx_head;
+ dpld->set_tx_tail = set_tx_tail;
+ dpld->get_tx_buff = get_tx_buff;
+ dpld->get_tx_buff_size = get_tx_buff_size;
+ dpld->get_rx_head = get_rx_head;
+ dpld->get_rx_tail = get_rx_tail;
+ dpld->set_rx_head = set_rx_head;
+ dpld->set_rx_tail = set_rx_tail;
+ dpld->get_rx_buff = get_rx_buff;
+ dpld->get_rx_buff_size = get_rx_buff_size;
+ dpld->get_mask_req_ack = get_mask_req_ack;
+ dpld->get_mask_res_ack = get_mask_res_ack;
+ dpld->get_mask_send = get_mask_send;
+}
+
+static int dpram_link_init(struct link_device *ld, struct io_device *iod)
+{
+ return 0;
+}
+
+static void dpram_link_terminate(struct link_device *ld, struct io_device *iod)
+{
+ return;
+}
+
+struct link_device *pld_create_link_device(struct platform_device *pdev)
+{
+ struct modem_data *mdm_data = NULL;
+ struct dpram_link_device *dpld = NULL;
+ struct link_device *ld = NULL;
+ struct resource *res = NULL;
+ resource_size_t res_size;
+ struct modemlink_dpram_control *dpctl = NULL;
+ unsigned long task_data;
+ int ret = 0;
+ int i = 0;
+ int bsize;
+ int qsize;
+
+ /* Get the platform data */
+ mdm_data = (struct modem_data *)pdev->dev.platform_data;
+ if (!mdm_data) {
+ mif_info("ERR! mdm_data == NULL\n");
+ goto err;
+ }
+ mif_info("modem = %s\n", mdm_data->name);
+ mif_info("link device = %s\n", mdm_data->link_name);
+
+ if (!mdm_data->dpram_ctl) {
+ mif_info("ERR! mdm_data->dpram_ctl == NULL\n");
+ goto err;
+ }
+ dpctl = mdm_data->dpram_ctl;
+
+ /* Alloc DPRAM link device structure */
+ dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL);
+ if (!dpld) {
+ mif_info("ERR! kzalloc dpld fail\n");
+ goto err;
+ }
+ ld = &dpld->ld;
+
+ /* Retrieve modem data and DPRAM control data from the modem data */
+ ld->mdm_data = mdm_data;
+ ld->name = mdm_data->link_name;
+ ld->ipc_version = mdm_data->ipc_version;
+
+ /* Retrieve the most basic data for IPC from the modem data */
+ dpld->dpctl = dpctl;
+ dpld->dp_type = dpctl->dp_type;
+
+ if (mdm_data->ipc_version < SIPC_VER_50) {
+ if (!dpctl->max_ipc_dev) {
+ mif_info("ERR! no max_ipc_dev\n");
+ goto err;
+ }
+
+ ld->aligned = dpctl->aligned;
+ dpld->max_ipc_dev = dpctl->max_ipc_dev;
+ } else {
+ ld->aligned = 1;
+ dpld->max_ipc_dev = MAX_SIPC5_DEV;
+ }
+
+ /* Set attributes as a link device */
+ ld->init_comm = dpram_link_init;
+ ld->terminate_comm = dpram_link_terminate;
+ ld->send = dpram_send;
+ ld->force_dump = dpram_force_dump;
+ ld->dump_start = dpram_dump_start;
+ ld->dump_update = dpram_dump_update;
+ ld->ioctl = dpram_ioctl;
+
+ INIT_LIST_HEAD(&ld->list);
+
+ skb_queue_head_init(&ld->sk_fmt_tx_q);
+ skb_queue_head_init(&ld->sk_raw_tx_q);
+ skb_queue_head_init(&ld->sk_rfs_tx_q);
+ ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q;
+ ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q;
+ ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q;
+
+ /* Set up function pointers */
+ dpram_setup_common_op(dpld);
+ dpld->dpram_dump = dpram_dump_memory;
+ dpld->ext_op = dpram_get_ext_op(mdm_data->modem_type);
+ if (dpld->ext_op && dpld->ext_op->ioctl)
+ dpld->ext_ioctl = dpld->ext_op->ioctl;
+
+ /* Retrieve DPRAM resource */
+ if (!dpctl->dp_base) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ mif_info("%s: ERR! platform_get_resource fail\n",
+ ld->name);
+ goto err;
+ }
+ res_size = resource_size(res);
+
+ dpctl->dp_base = ioremap_nocache(res->start, res_size);
+ dpctl->dp_size = res_size;
+ }
+ dpld->dp_base = dpctl->dp_base;
+ dpld->dp_size = dpctl->dp_size;
+
+ mif_info("%s: dp_type %d, aligned %d, dp_base 0x%08X, dp_size %d\n",
+ ld->name, dpld->dp_type, ld->aligned, (int)dpld->dp_base,
+ dpld->dp_size);
+
+ /* Initialize DPRAM map (physical map -> logical map) */
+ ret = dpram_table_init(dpld);
+ if (ret < 0) {
+ mif_info("%s: ERR! dpram_table_init fail (err %d)\n",
+ ld->name, ret);
+ goto err;
+ }
+
+ spin_lock_init(&dpld->pld_lock);
+
+ /* Disable IPC */
+ set_magic(dpld, 0);
+ set_access(dpld, 0);
+
+ dpld->dpram_init_status = DPRAM_INIT_STATE_NONE;
+
+ /* Initialize locks, completions, and bottom halves */
+ snprintf(dpld->wlock_name, DP_MAX_NAME_LEN, "%s_wlock", ld->name);
+ wake_lock_init(&dpld->wlock, WAKE_LOCK_SUSPEND, dpld->wlock_name);
+
+ init_completion(&dpld->dpram_init_cmd);
+ init_completion(&dpld->modem_pif_init_done);
+ init_completion(&dpld->udl_start_complete);
+ init_completion(&dpld->udl_cmd_complete);
+ init_completion(&dpld->dump_start_complete);
+ init_completion(&dpld->dump_recv_done);
+
+ task_data = (unsigned long)dpld;
+ tasklet_init(&dpld->rx_tsk, dpram_ipc_rx_task, task_data);
+
+ /* Prepare SKB queue head for RX processing */
+ for (i = 0; i < dpld->max_ipc_dev; i++)
+ skb_queue_head_init(&dpld->skb_rxq[i]);
+
+ /* Prepare RXB queue */
+ qsize = DPRAM_MAX_RXBQ_SIZE;
+ for (i = 0; i < dpld->max_ipc_dev; i++) {
+ bsize = rxbq_get_page_size(get_rx_buff_size(dpld, i));
+ dpld->rxbq[i].size = qsize;
+ dpld->rxbq[i].in = 0;
+ dpld->rxbq[i].out = 0;
+ dpld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize);
+ if (!dpld->rxbq[i].rxb) {
+ mif_info("%s: ERR! %s rxbq_create_pool fail\n",
+ ld->name, get_dev_name(i));
+ goto err;
+ }
+ mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n",
+ ld->name, get_dev_name(i), bsize, qsize);
+ }
+
+ /* Prepare a multi-purpose miscellaneous buffer */
+ dpld->buff = kzalloc(dpld->dp_size, GFP_KERNEL);
+ if (!dpld->buff) {
+ mif_info("%s: ERR! kzalloc dpld->buff fail\n", ld->name);
+ goto err;
+ }
+
+ /* Retrieve DPRAM IRQ GPIO# */
+ dpld->gpio_dpram_int = mdm_data->gpio_dpram_int;
+
+ /* Retrieve DPRAM IRQ# */
+ if (!dpctl->dpram_irq) {
+ dpctl->dpram_irq = platform_get_irq_byname(pdev, "dpram_irq");
+ if (dpctl->dpram_irq < 0) {
+ mif_info("%s: ERR! platform_get_irq_byname fail\n",
+ ld->name);
+ goto err;
+ }
+ }
+ dpld->irq = dpctl->dpram_irq;
+
+ /* Retrieve DPRAM IRQ flags */
+ if (!dpctl->dpram_irq_flags)
+ dpctl->dpram_irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW);
+ dpld->irq_flags = dpctl->dpram_irq_flags;
+
+ /* Register DPRAM interrupt handler */
+ snprintf(dpld->irq_name, DP_MAX_NAME_LEN, "%s_irq", ld->name);
+ ret = dpram_register_isr(dpld->irq, dpram_irq_handler, dpld->irq_flags,
+ dpld->irq_name, dpld);
+ if (ret)
+ goto err;
+
+ return ld;
+
+err:
+ if (dpld) {
+ kfree(dpld->buff);
+ kfree(dpld);
+ }
+
+ return NULL;
+}
+