diff options
Diffstat (limited to 'drivers/misc/modem_if/modem_link_device_dpram.c')
-rw-r--r-- | drivers/misc/modem_if/modem_link_device_dpram.c | 2111 |
1 files changed, 2111 insertions, 0 deletions
diff --git a/drivers/misc/modem_if/modem_link_device_dpram.c b/drivers/misc/modem_if/modem_link_device_dpram.c new file mode 100644 index 00000000000..ea0b21e33bd --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_dpram.c @@ -0,0 +1,2111 @@ +/* + * 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_dpram.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 void reset_tx_circ(struct dpram_link_device *dpld, int dev); +static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev); + +static int trigger_force_cp_crash(struct dpram_link_device *dpld); + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +static inline void log_dpram_status(struct dpram_link_device *dpld, char *str) +{ + struct utc_time utc; + + get_utc_time(&utc); + + pr_info("%s%s: %s: [%02d:%02d:%02d.%03d] " + "ACC{%X %d} FMT{TI:%u TO:%u RI:%u RO:%u} " + "RAW{TI:%u TO:%u RI:%u RO:%u} INTR{0x%X}\n", + LOG_TAG, dpld->ld.mc->name, str, + utc.hour, utc.min, utc.sec, utc.msec, + 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 pr_trace(struct dpram_link_device *dpld, int dev, + struct timespec *ts, u8 *buff, u32 rcvd) +{ + struct link_device *ld = &dpld->ld; + struct utc_time utc; + + ts2utc(ts, &utc); + + pr_info("%s[%d-%02d-%02d %02d:%02d:%02d.%03d] %s trace (%s)\n", + LOG_TAG, utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec, + utc.msec, get_dev_name(dev), ld->name); + + mif_print_dump(buff, rcvd, 4); + + return; +} + +static void save_dpram_dump_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct trace_queue *trq; + struct trace_data *trd; + struct file *fp; + struct timespec *ts; + u8 *dump; + int rcvd; + char *path; + struct utc_time utc; + + dpld = container_of(work, struct dpram_link_device, dump_dwork.work); + ld = &dpld->ld; + trq = &dpld->dump_list; + path = dpld->dump_path; + + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dump = trd->data; + rcvd = trd->size; + + ts2utc(ts, &utc); + snprintf(path, MIF_MAX_PATH_LEN, + "%s/%s_dump_%d%02d%02d-%02d%02d%02d", + MIF_LOG_DIR, ld->name, utc.year, utc.mon, utc.day, + utc.hour, utc.min, utc.sec); + + fp = mif_open_file(path); + if (fp) { + mif_save_file(fp, dump, rcvd); + mif_close_file(fp); + } else { + mif_err("%s: ERR! %s open fail\n", ld->name, path); + mif_print_dump(dump, rcvd, 16); + } + + kfree(dump); + } +} + +static void save_dpram_dump(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct trace_data *trd; + u8 *buff; + struct timespec ts; + + buff = kzalloc(dpld->size, GFP_ATOMIC); + if (!buff) { + mif_err("%s: ERR! kzalloc fail\n", ld->name); + return; + } + + getnstimeofday(&ts); + + memcpy(buff, dpld->base, dpld->size); + + trd = trq_get_free_slot(&dpld->dump_list); + if (!trd) { + mif_err("%s: ERR! trq_get_free_slot fail\n", ld->name); + mif_print_dump(buff, dpld->size, 16); + kfree(buff); + return; + } + + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->data = buff; + trd->size = dpld->size; + + queue_delayed_work(system_nrt_wq, &dpld->dump_dwork, 0); +} + +static void save_ipc_trace_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct trace_queue *trq; + struct trace_data *trd; + struct file *fp; + struct timespec *ts; + int dev; + u8 *dump; + int rcvd; + u8 *buff; + char *path; + struct utc_time utc; + + dpld = container_of(work, struct dpram_link_device, trace_dwork.work); + ld = &dpld->ld; + trq = &dpld->trace_list; + path = dpld->trace_path; + + buff = kzalloc(dpld->size << 3, GFP_KERNEL); + if (!buff) { + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dev = trd->dev; + dump = trd->data; + rcvd = trd->size; + pr_trace(dpld, dev, ts, dump, rcvd); + + kfree(dump); + } + return; + } + + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dev = trd->dev; + dump = trd->data; + rcvd = trd->size; + + ts2utc(ts, &utc); + snprintf(path, MIF_MAX_PATH_LEN, + "%s/%s_%s_%d%02d%02d-%02d%02d%02d", + MIF_LOG_DIR, ld->name, get_dev_name(dev), + utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec); + + fp = mif_open_file(path); + if (fp) { + int len; + + snprintf(buff, MIF_MAX_PATH_LEN, + "[%d-%02d-%02d %02d:%02d:%02d.%03d]\n", + utc.year, utc.mon, utc.day, utc.hour, utc.min, + utc.sec, utc.msec); + len = strlen(buff); + mif_dump2format4(dump, rcvd, (buff + len), NULL); + strcat(buff, "\n"); + len = strlen(buff); + + mif_save_file(fp, buff, len); + + memset(buff, 0, len); + mif_close_file(fp); + } else { + mif_err("%s: %s open fail\n", ld->name, path); + pr_trace(dpld, dev, ts, dump, rcvd); + } + + kfree(dump); + } + + kfree(buff); +} + +static void print_ipc_trace(struct dpram_link_device *dpld, int dev, + u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd) +{ + u8 *buff = dpld->buff; + struct timespec ts; + + getnstimeofday(&ts); + + memset(buff, 0, dpld->size); + circ_read(buff, src, qsize, out, rcvd); + + pr_trace(dpld, dev, &ts, buff, rcvd); +} + +static void save_ipc_trace(struct dpram_link_device *dpld, int dev, + u8 __iomem *src, u32 qsize, u32 in, u32 out, u32 rcvd) +{ + struct link_device *ld = &dpld->ld; + struct trace_data *trd; + u8 *buff; + struct timespec ts; + + buff = kzalloc(rcvd, GFP_ATOMIC); + if (!buff) { + mif_err("%s: %s: ERR! kzalloc fail\n", + ld->name, get_dev_name(dev)); + print_ipc_trace(dpld, dev, src, qsize, in, out, rcvd); + return; + } + + getnstimeofday(&ts); + + circ_read(buff, src, qsize, out, rcvd); + + trd = trq_get_free_slot(&dpld->trace_list); + if (!trd) { + mif_err("%s: %s: ERR! trq_get_free_slot fail\n", + ld->name, get_dev_name(dev)); + pr_trace(dpld, dev, &ts, buff, rcvd); + kfree(buff); + return; + } + + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->dev = dev; + trd->data = buff; + trd->size = rcvd; + + queue_delayed_work(system_nrt_wq, &dpld->trace_dwork, 0); +} +#endif + +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); +} + +/* +** DPRAM operations +*/ +static int register_isr(unsigned irq, irqreturn_t (*isr)(int, void*), + unsigned long flag, const char *name, + struct dpram_link_device *dpld) +{ + int ret; + + 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 (likely(dpld->need_intr_clear)) + dpld->ext_op->clear_intr(dpld); +} + +static inline u16 recv_intr(struct dpram_link_device *dpld) +{ + return ioread16(dpld->mbx2ap); +} + +static inline void send_intr(struct dpram_link_device *dpld, u16 mask) +{ + iowrite16(mask, dpld->mbx2cp); +} + +static inline u16 get_magic(struct dpram_link_device *dpld) +{ + return ioread16(dpld->magic); +} + +static inline void set_magic(struct dpram_link_device *dpld, u16 val) +{ + iowrite16(val, dpld->magic); +} + +static inline u16 get_access(struct dpram_link_device *dpld) +{ + return ioread16(dpld->access); +} + +static inline void set_access(struct dpram_link_device *dpld, u16 val) +{ + iowrite16(val, dpld->access); +} + +static inline u32 get_tx_head(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->txq.head); +} + +static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->txq.tail); +} + +static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)in, dpld->dev[id]->txq.head); + + while (1) { + /* Check head value written */ + val = ioread16(dpld->dev[id]->txq.head); + if (likely(val == in)) + return; + + cnt++; + mif_err("ERR: %s txq.head(%d) != in(%d), count %d\n", + get_dev_name(id), val, in, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write head value again */ + udelay(100); + iowrite16((u16)in, dpld->dev[id]->txq.head); + } + + trigger_force_cp_crash(dpld); +} + +static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)out, dpld->dev[id]->txq.tail); + + while (1) { + /* Check tail value written */ + val = ioread16(dpld->dev[id]->txq.tail); + if (likely(val == out)) + return; + + cnt++; + mif_err("ERR: %s txq.tail(%d) != out(%d), count %d\n", + get_dev_name(id), val, out, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write tail value again */ + udelay(100); + iowrite16((u16)out, dpld->dev[id]->txq.tail); + } + + trigger_force_cp_crash(dpld); +} + +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) +{ + return ioread16(dpld->dev[id]->rxq.head); +} + +static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id) +{ + return ioread16(dpld->dev[id]->rxq.tail); +} + +static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)in, dpld->dev[id]->rxq.head); + + while (1) { + /* Check head value written */ + val = ioread16(dpld->dev[id]->rxq.head); + if (val == in) + return; + + cnt++; + mif_err("ERR: %s rxq.head(%d) != in(%d), count %d\n", + get_dev_name(id), val, in, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write head value again */ + udelay(100); + iowrite16((u16)in, dpld->dev[id]->rxq.head); + } + + trigger_force_cp_crash(dpld); +} + +static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out) +{ + int cnt = 0; + u32 val = 0; + + iowrite16((u16)out, dpld->dev[id]->rxq.tail); + + while (1) { + /* Check tail value written */ + val = ioread16(dpld->dev[id]->rxq.tail); + if (val == out) + return; + + cnt++; + mif_err("ERR: %s rxq.tail(%d) != out(%d), count %d\n", + get_dev_name(id), val, out, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + /* Write tail value again */ + udelay(100); + iowrite16((u16)out, dpld->dev[id]->rxq.tail); + } + + trigger_force_cp_crash(dpld); +} + +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; +} + +/* Get free space in the TXQ as well as in & out pointers */ +static int get_txq_space(struct dpram_link_device *dpld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &dpld->ld; + int cnt = 0; + u32 head; + u32 tail; + int space; + + while (1) { + head = get_tx_head(dpld, dev); + tail = get_tx_tail(dpld, dev); + + space = circ_get_space(qsize, head, tail); + 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 (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return space; + } + + cnt++; + mif_err("%s: ERR! <%pf> " + "%s_TXQ invalid (size:%d in:%d out:%d), count %d\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + udelay(100); + } + + *in = 0; + *out = 0; + return -EINVAL; +} + +/* Get data size in the RXQ as well as in & out pointers */ +static int get_rxq_rcvd(struct dpram_link_device *dpld, int dev, u32 qsize, + u32 *in, u32 *out) +{ + struct link_device *ld = &dpld->ld; + int cnt = 0; + u32 head; + u32 tail; + u32 rcvd; + + while (1) { + head = get_rx_head(dpld, dev); + tail = get_rx_tail(dpld, dev); + if (head == tail) { + *in = head; + *out = tail; + return 0; + } + + rcvd = circ_get_usage(qsize, head, tail); + mif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", + ld->name, get_dev_name(dev), qsize, head, tail, rcvd); + + if (circ_valid(qsize, head, tail)) { + *in = head; + *out = tail; + return rcvd; + } + + cnt++; + mif_err("%s: ERR! <%pf> " + "%s_RXQ invalid (size:%d in:%d out:%d), count %d\n", + ld->name, __builtin_return_address(0), + get_dev_name(dev), qsize, head, tail, cnt); + if (cnt >= MAX_RETRY_CNT) + break; + + udelay(100); + } + + *in = 0; + *out = 0; + return -EINVAL; +} + +static inline void 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); +} + +static inline void reset_rx_circ(struct dpram_link_device *dpld, int dev) +{ +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + trigger_force_cp_crash(dpld); +#else + set_rx_tail(dpld, dev, get_rx_head(dpld, dev)); + if (dev == IPC_FMT) + trigger_force_cp_crash(dpld); +#endif +} + +/* +** CAUTION : dpram_allow_sleep() MUST be invoked after dpram_wake_up() success +*/ +static inline bool dpram_can_sleep(struct dpram_link_device *dpld) +{ + return dpld->need_wake_up; +} + +static int dpram_wake_up(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + + if (unlikely(!dpram_can_sleep(dpld))) + return 0; + + if (dpld->ext_op->wakeup(dpld) < 0) { + mif_err("%s: ERR! <%pf> DPRAM wakeup fail once\n", + ld->name, __builtin_return_address(0)); + + dpld->ext_op->sleep(dpld); + + udelay(10); + + if (dpld->ext_op->wakeup(dpld) < 0) { + mif_err("%s: ERR! <%pf> DPRAM wakeup fail twice\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 (unlikely(!dpram_can_sleep(dpld))) + return; + + if (atomic_dec_return(&dpld->accessing) <= 0) { + dpld->ext_op->sleep(dpld); + atomic_set(&dpld->accessing, 0); + mif_debug("%s: DPRAM sleep possible\n", ld->name); + } +} + +static int 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 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 (check_access(dpld) < 0) { + mif_info("%s: ERR! <%pf> 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; + + circ_write(buff, src, qsize, in, len); + + /* update new in pointer */ + inp = in + len; + if (inp >= qsize) + inp -= qsize; + set_tx_head(dpld, dev, inp); + + if (dev == IPC_FMT) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2CP", ld->mc->name); + pr_ipc(tag, src, (len > 20 ? 20 : len)); + log_dpram_status(dpld, "MIF2CP"); +#endif + 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); + } + +#if 1 + if (ld->aligned && memcmp16_to_io((buff + in), src, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + trigger_force_cp_crash(dpld); + } +#endif +} + +static int dpram_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_lock[dev], flags); + + while (1) { + space = get_txq_space(dpld, dev, qsize, &in, &out); + if (unlikely(space < 0)) { + spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); + 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_lock[dev], 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_lock[dev], flags); + + return copied; +} + +static int dpram_wait_for_res_ack(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct completion *cmpl = &dpld->res_ack_cmpl[dev]; + unsigned long timeout = RES_ACK_WAIT_TIMEOUT; + int ret; + u16 mask; + + mask = get_mask_req_ack(dpld, dev); + mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask); + send_intr(dpld, INT_NON_CMD(mask)); + + ret = wait_for_completion_interruptible_timeout(cmpl, timeout); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret == 0) { + mif_err("%s: %s: TIMEOUT\n", ld->name, get_dev_name(dev)); + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else if (ret < 0) { + mif_err("%s: %s: interrupted (ret %d)\n", + ld->name, get_dev_name(dev), ret); + } + + return ret; +} + +static int dpram_process_res_ack(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + int ret; + u16 mask; + + ret = dpram_ipc_tx(dpld, dev); + if (ret < 0) { + if (ret == -ENOSPC) { + /* dpld->res_required[dev] is set in dpram_ipc_tx() */ + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else { + mif_err("%s: ERR! ipc_tx fail (%d)\n", ld->name, ret); + } + } else { + if (ret > 0) { + mask = get_mask_send(dpld, dev); + send_intr(dpld, INT_NON_CMD(mask)); + mif_debug("%s: send intr 0x%04X\n", ld->name, mask); + } + atomic_set(&dpld->res_required[dev], 0); + } + + return ret; +} + +static void dpram_fmt_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, fmt_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_FMT); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_FMT); +} + +static void dpram_raw_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, raw_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_RAW); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_RAW); + if (ret > 0) + mif_netif_wake(ld); +} + +static void dpram_rfs_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + int ret; + + ld = container_of(work, struct link_device, rfs_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = dpram_wait_for_res_ack(dpld, IPC_RFS); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret <= 0) + return; + + ret = dpram_process_res_ack(dpld, IPC_RFS); +} + +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 mif_rxb *rxb; + unsigned qlen; + int i; + + for (i = 0; i < ld->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--; + } + } +} + +/* + ret < 0 : error + ret == 0 : no data + ret > 0 : valid data +*/ +static int dpram_recv_ipc_with_rxb(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct mif_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; + + rcvd = 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")); + } + + /* 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)); + return -ENOMEM; + } + + /* Read data from each DPRAM buffer */ + circ_read(rxb_put(rxb, rcvd), src, qsize, out, rcvd); + + /* Update tail (out) pointer */ + set_rx_tail(dpld, dev, in); + + return rcvd; +} + +/* + ret < 0 : error + ret == 0 : no data + ret > 0 : valid data +*/ +static int dpram_recv_ipc_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 = get_rxq_rcvd(dpld, dev, qsize, &in, &out); + if (rcvd <= 0) + return rcvd; + + if (dev == IPC_FMT) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + log_dpram_status(dpld, "CP2MIF"); +#endif + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); + } + + rest = rcvd; + while (rest > 0) { + /* Calculate the start of an SIPC5 frame */ + frm = src + out; + + /* Check the SIPC5 frame */ + len = sipc5_check_frame_in_dev(ld, dev, frm, rest); + if (len <= 0) { +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + u32 tail = get_rx_tail(dpld, dev); + log_dpram_status(dpld, "CP2MIF"); + save_ipc_trace(dpld, dev, src, qsize, in, tail, rcvd); + save_dpram_dump(dpld); +#endif + return -EBADMSG; + } + + /* Calculate total length with padding in DPRAM */ + 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); + circ_read(dst, src, qsize, out, tot); + skb_trim(skb, len); +#if 1 + if (ld->aligned && memcmp16_to_io((src + out), dst, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + trigger_force_cp_crash(dpld); + } +#endif +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + if (unlikely(dev == IPC_FMT)) { + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: CP2MIF", + ld->mc->name); + pr_ipc(str, skb->data, (skb->len > 20 ? 20 : skb->len)); + } +#endif + + /* Pass the IPC frame to IOD */ + iod->recv_skb(iod, ld, skb); + + /* Calculate new 'out' pointer */ + rest -= tot; + out += tot; + if (out >= qsize) + out -= qsize; + } + + /* Update tail (out) pointer */ + set_rx_tail(dpld, dev, in); + + return rcvd; +} + +static void non_command_handler(struct dpram_link_device *dpld, u16 intr) +{ + struct link_device *ld = &dpld->ld; + int i = 0; + int ret = 0; + u16 mask = 0; + + if (!ipc_active(dpld)) + return; + + /* Read data from DPRAM */ + for (i = 0; i < ld->max_ipc_dev; i++) { + if (dpld->use_skb) + ret = dpram_recv_ipc_with_skb(dpld, i); + else + ret = dpram_recv_ipc_with_rxb(dpld, i); + if (ret < 0) + reset_rx_circ(dpld, i); + + /* Check and process REQ_ACK (at this time, in == out) */ + if (intr & get_mask_req_ack(dpld, i)) { + mif_debug("%s: send %s_RES_ACK\n", + ld->name, get_dev_name(i)); + mask |= get_mask_res_ack(dpld, i); + } + } + + if (!dpld->use_skb) { + /* Schedule soft IRQ for RX */ + tasklet_hi_schedule(&dpld->rx_tsk); + } + + if (mask) { + send_intr(dpld, INT_NON_CMD(mask)); + mif_debug("%s: send intr 0x%04X\n", ld->name, mask); + } + + if (intr && INT_MASK_RES_ACK_SET) { + if (intr && INT_MASK_RES_ACK_R) + complete_all(&dpld->res_ack_cmpl[IPC_RAW]); + else if (intr && INT_MASK_RES_ACK_F) + complete_all(&dpld->res_ack_cmpl[IPC_FMT]); + else + complete_all(&dpld->res_ack_cmpl[IPC_RFS]); + } +} + +static void handle_cp_crash(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod; + int i; + + mif_netif_stop(ld); + + for (i = 0; i < ld->max_ipc_dev; i++) { + mif_info("%s: purging %s_skb_txq\n", 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); +} + +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; + } + + disable_irq_nosync(dpld->irq); + + ld->mode = LINK_MODE_ULOAD; + mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0)); + + dpram_wake_up(dpld); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + save_dpram_dump(dpld); +#endif + + enable_irq(dpld->irq); + + 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 < ld->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 < ld->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 < ld->max_ipc_dev; i++) { + spin_lock_init(&dpld->tx_lock[i]); + atomic_set(&dpld->res_required[i], 0); + } + + /* 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); + + del_timer(&dpld->crash_ack_timer); + + dpram_wake_up(dpld); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + save_dpram_dump(dpld); +#endif + + if (dpld->ext_op && dpld->ext_op->crash_log) + dpld->ext_op->crash_log(dpld); + + mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name); + + 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->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_reset_handler(dpld); + break; + + case INT_CMD_CRASH_EXIT: + dpld->init_status = DPRAM_INIT_STATE_NONE; + cmd_crash_exit_handler(dpld); + break; + + case INT_CMD_PHONE_START: + dpld->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 inline void dpram_ipc_rx(struct dpram_link_device *dpld, u16 intr) +{ + if (unlikely(INT_CMD_VALID(intr))) + command_handler(dpld, intr); + else + non_command_handler(dpld, intr); +} + +static inline void dpram_intr_handler(struct dpram_link_device *dpld, u16 intr) +{ + char *name = dpld->ld.name; + + if (unlikely(intr == INT_POWERSAFE_FAIL)) { + mif_info("%s: intr == INT_POWERSAFE_FAIL\n", name); + return; + } + + if (unlikely(EXT_UDL_CMD(intr))) { + if (likely(EXT_INT_VALID(intr))) { + if (UDL_CMD_VALID(intr)) + udl_command_handler(dpld, intr); + else if (EXT_CMD_VALID(intr)) + ext_command_handler(dpld, intr); + else + mif_info("%s: ERR! invalid intr 0x%04X\n", + name, intr); + } else { + mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); + } + return; + } + + if (likely(INT_VALID(intr))) + dpram_ipc_rx(dpld, intr); + else + mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); +} + +static irqreturn_t ap_idpram_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 = recv_intr(dpld); + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + dpram_intr_handler(dpld, int2ap); + + return IRQ_HANDLED; +} + +static irqreturn_t cp_idpram_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; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + if (dpram_wake_up(dpld) < 0) { + trigger_force_cp_crash(dpld); + return IRQ_HANDLED; + } + + int2ap = recv_intr(dpld); + + dpram_intr_handler(dpld, int2ap); + + clear_intr(dpld); + + dpram_allow_sleep(dpld); + + return IRQ_HANDLED; +} + +static irqreturn_t ext_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 = recv_intr(dpld); + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + return IRQ_HANDLED; + + dpram_intr_handler(dpld, int2ap); + + 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; + + if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) { + mif_err("%s: %s txq->qlen %d >= %d\n", ld->name, + get_dev_name(dev), txq->qlen, MAX_SKB_TXQ_DEPTH); + if (iod->io_typ == IODEV_NET || iod->format == IPC_MULTI_RAW) { + dev_kfree_skb_any(skb); + return; + } + } + + skb_queue_tail(txq, skb); + + if (dpram_wake_up(dpld) < 0) { + trigger_force_cp_crash(dpld); + return; + } + + if (!ipc_active(dpld)) { + mif_info("%s: IPC is NOT active\n", ld->name); + goto exit; + } + + if (atomic_read(&dpld->res_required[dev]) > 0) { + mif_info("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + goto exit; + } + + ret = dpram_ipc_tx(dpld, dev); + if (ret > 0) { + mask = get_mask_send(dpld, dev); + send_intr(dpld, INT_NON_CMD(mask)); + } else if (ret == -ENOSPC) { + /* + ** dpld->res_required[dev] is set in dpram_ipc_tx() + */ + if (dev == IPC_RAW) + mif_netif_stop(ld); + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); + } else { + mif_info("%s: dpram_ipc_tx fail (err %d)\n", ld->name, ret); + } + +exit: + dpram_allow_sleep(dpld); +} + +static int dpram_send_cp_binary(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->download_binary) + return dpld->ext_op->download_binary(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_send_cp_binary(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 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->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 void dpram_dump_memory(struct link_device *ld, char *buff) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + dpram_wake_up(dpld); + memcpy(buff, dpld->base, dpld->size); + dpram_allow_sleep(dpld); +} + +static void dpram_remap_std_16k_region(struct dpram_link_device *dpld) +{ + struct dpram_ipc_16k_map *dpram_map; + struct dpram_ipc_device *dev; + + dpram_map = (struct dpram_ipc_16k_map *)dpld->base; + + /* magic code and access enable fields */ + dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic; + dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access; + + /* FMT */ + dev = &dpld->ipc_map.dev[IPC_FMT]; + + strcpy(dev->name, "FMT"); + dev->id = IPC_FMT; + + dev->txq.head = (u16 __iomem *)&dpram_map->fmt_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->fmt_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->fmt_tx_buff[0]; + dev->txq.size = DP_16K_FMT_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->fmt_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->fmt_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->fmt_rx_buff[0]; + dev->rxq.size = DP_16K_FMT_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_F; + dev->mask_res_ack = INT_MASK_RES_ACK_F; + dev->mask_send = INT_MASK_SEND_F; + + /* RAW */ + dev = &dpld->ipc_map.dev[IPC_RAW]; + + strcpy(dev->name, "RAW"); + dev->id = IPC_RAW; + + dev->txq.head = (u16 __iomem *)&dpram_map->raw_tx_head; + dev->txq.tail = (u16 __iomem *)&dpram_map->raw_tx_tail; + dev->txq.buff = (u8 __iomem *)&dpram_map->raw_tx_buff[0]; + dev->txq.size = DP_16K_RAW_TX_BUFF_SZ; + + dev->rxq.head = (u16 __iomem *)&dpram_map->raw_rx_head; + dev->rxq.tail = (u16 __iomem *)&dpram_map->raw_rx_tail; + dev->rxq.buff = (u8 __iomem *)&dpram_map->raw_rx_buff[0]; + dev->rxq.size = DP_16K_RAW_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_R; + dev->mask_res_ack = INT_MASK_RES_ACK_R; + dev->mask_send = INT_MASK_SEND_R; + + /* interrupt ports */ + dpld->ipc_map.mbx_cp2ap = (u16 __iomem *)&dpram_map->mbx_cp2ap; + dpld->ipc_map.mbx_ap2cp = (u16 __iomem *)&dpram_map->mbx_ap2cp; +} + +static int dpram_table_init(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + u8 __iomem *dp_base; + int i; + + if (!dpld->base) { + mif_info("%s: ERR! dpld->base == NULL\n", ld->name); + return -EINVAL; + } + dp_base = dpld->base; + + /* 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->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); + } + + /* Map for IPC */ + if (dpld->ext_op && dpld->ext_op->init_ipc_map) { + dpld->ext_op->init_ipc_map(dpld); + } else if (dpld->dpctl->ipc_map) { + memcpy(&dpld->ipc_map, dpld->dpctl->ipc_map, + sizeof(struct dpram_ipc_map)); + } else { + if (dpld->size == DPRAM_SIZE_16KB) + dpram_remap_std_16k_region(dpld); + else + return -EINVAL; + } + + dpld->magic = dpld->ipc_map.magic; + dpld->access = dpld->ipc_map.access; + for (i = 0; i < ld->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; + + return 0; +} + +static void dpram_setup_common_op(struct dpram_link_device *dpld) +{ + 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; + dpld->ipc_rx_handler = dpram_intr_handler; +} + +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) +{ + if (iod->format == IPC_FMT && ld->mode == LINK_MODE_IPC) { + if (!atomic_read(&iod->opened)) { + ld->mode = LINK_MODE_OFFLINE; + mif_err("%s: %s: link mode is changed: IPC->OFFLINE\n", + iod->name, ld->name); + } + } + + return; +} + +struct link_device *dpram_create_link_device(struct platform_device *pdev) +{ + struct dpram_link_device *dpld = NULL; + struct link_device *ld = NULL; + struct modem_data *modem = NULL; + struct modemlink_dpram_control *dpctl = NULL; + struct resource *res = NULL; + resource_size_t res_size; + unsigned long task_data; + int ret = 0; + int i = 0; + int bsize; + int qsize; + + /* + ** Alloc an instance of the DPRAM link device structure + */ + dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL); + if (!dpld) { + mif_err("ERR! kzalloc dpld fail\n"); + goto err; + } + ld = &dpld->ld; + + /* + ** Get the modem (platform) data + */ + modem = (struct modem_data *)pdev->dev.platform_data; + if (!modem) { + mif_err("ERR! modem == NULL\n"); + goto err; + } + mif_info("modem = %s\n", modem->name); + mif_info("link device = %s\n", modem->link_name); + + /* + ** Retrieve modem data and DPRAM control data from the modem data + */ + ld->mdm_data = modem; + ld->name = modem->link_name; + ld->ipc_version = modem->ipc_version; + + if (!modem->dpram_ctl) { + mif_err("ERR! modem->dpram_ctl == NULL\n"); + goto err; + } + dpctl = modem->dpram_ctl; + + dpld->dpctl = dpctl; + dpld->type = dpctl->dp_type; + + if (ld->ipc_version < SIPC_VER_50) { + if (!dpctl->max_ipc_dev) { + mif_err("%s: ERR! no max_ipc_dev\n", ld->name); + goto err; + } + + ld->aligned = dpctl->aligned; + ld->max_ipc_dev = dpctl->max_ipc_dev; + } else { + ld->aligned = 1; + ld->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(modem->modem_type); + if (dpld->ext_op && dpld->ext_op->ioctl) + dpld->ext_ioctl = dpld->ext_op->ioctl; + if (dpld->ext_op && dpld->ext_op->wakeup && dpld->ext_op->sleep) + dpld->need_wake_up = true; + if (dpld->ext_op && dpld->ext_op->clear_intr) + dpld->need_intr_clear = true; + + /* + ** Retrieve DPRAM resource + */ + if (!dpctl->dp_base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_BASE); + if (!res) { + mif_err("%s: ERR! no DPRAM resource\n", ld->name); + goto err; + } + res_size = resource_size(res); + + dpctl->dp_base = ioremap_nocache(res->start, res_size); + if (!dpctl->dp_base) { + mif_err("%s: ERR! ioremap_nocache for BASE fail\n", + ld->name); + goto err; + } + dpctl->dp_size = res_size; + } + dpld->base = dpctl->dp_base; + dpld->size = dpctl->dp_size; + + mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n", + ld->name, dpld->type, ld->aligned, (int)dpld->base, dpld->size); + + /* + ** Retrieve DPRAM SFR resource if exists + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_SFR_BASE); + if (res) { + res_size = resource_size(res); + dpld->sfr_base = ioremap_nocache(res->start, res_size); + if (!dpld->sfr_base) { + mif_err("%s: ERR! ioremap_nocache for SFR fail\n", + ld->name); + goto err; + } + } + + /* Initialize DPRAM map (physical map -> logical map) */ + ret = dpram_table_init(dpld); + if (ret < 0) { + mif_err("%s: ERR! dpram_table_init fail (err %d)\n", + ld->name, ret); + goto err; + } + + /* Disable IPC */ + set_magic(dpld, 0); + set_access(dpld, 0); + dpld->init_status = DPRAM_INIT_STATE_NONE; + + /* Initialize locks, completions, and bottom halves */ + snprintf(dpld->wlock_name, MIF_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->crash_start_complete); + init_completion(&dpld->crash_recv_done); + for (i = 0; i < ld->max_ipc_dev; i++) + init_completion(&dpld->res_ack_cmpl[i]); + + task_data = (unsigned long)dpld; + tasklet_init(&dpld->rx_tsk, dpram_ipc_rx_task, task_data); + + ld->tx_wq = create_singlethread_workqueue("dpram_tx_wq"); + if (!ld->tx_wq) { + mif_err("%s: ERR! fail to create tx_wq\n", ld->name); + goto err; + } + INIT_DELAYED_WORK(&ld->fmt_tx_dwork, dpram_fmt_tx_work); + INIT_DELAYED_WORK(&ld->raw_tx_dwork, dpram_raw_tx_work); + INIT_DELAYED_WORK(&ld->rfs_tx_dwork, dpram_rfs_tx_work); + ld->tx_dwork[IPC_FMT] = &ld->fmt_tx_dwork; + ld->tx_dwork[IPC_RAW] = &ld->raw_tx_dwork; + ld->tx_dwork[IPC_RFS] = &ld->rfs_tx_dwork; + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + INIT_DELAYED_WORK(&dpld->dump_dwork, save_dpram_dump_work); + INIT_DELAYED_WORK(&dpld->trace_dwork, save_ipc_trace_work); + spin_lock_init(&dpld->dump_list.lock); + spin_lock_init(&dpld->trace_list.lock); +#endif + + /* Prepare RXB queue */ + qsize = DPRAM_MAX_RXBQ_SIZE; + for (i = 0; i < ld->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_err("%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->size, GFP_KERNEL); + if (!dpld->buff) { + mif_err("%s: ERR! kzalloc dpld->buff fail\n", ld->name); + goto err; + } + + /* + ** Retrieve DPRAM IRQ GPIO#, IRQ#, and IRQ flags + */ + dpld->gpio_dpram_int = modem->gpio_dpram_int; + + if (dpctl->dpram_irq) { + dpld->irq = dpctl->dpram_irq; + } else { + dpld->irq = platform_get_irq_byname(pdev, STR_DPRAM_IRQ); + if (dpld->irq < 0) { + mif_err("%s: ERR! no DPRAM IRQ resource\n", ld->name); + goto err; + } + } + + if (dpctl->dpram_irq_flags) + dpld->irq_flags = dpctl->dpram_irq_flags; + else + dpld->irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW); + + /* + ** Register DPRAM interrupt handler + */ + snprintf(dpld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name); + if (dpld->ext_op && dpld->ext_op->irq_handler) + dpld->irq_handler = dpld->ext_op->irq_handler; + else if (dpld->type == CP_IDPRAM) + dpld->irq_handler = cp_idpram_irq_handler; + else if (dpld->type == AP_IDPRAM) + dpld->irq_handler = ap_idpram_irq_handler; + else + dpld->irq_handler = ext_dpram_irq_handler; + + ret = register_isr(dpld->irq, dpld->irq_handler, dpld->irq_flags, + dpld->irq_name, dpld); + if (ret) + goto err; + + return ld; + +err: + if (dpld) { + if (dpld->buff) + kfree(dpld->buff); + kfree(dpld); + } + + return NULL; +} + |