/* arch/arm/mach-msm/smd.c
*
* Copyright (C) 2007 Google, Inc.
* Author: Brian Swetland <swetland@google.com>
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <mach/msm_smd.h>
#include <mach/system.h>
#include "smd_private.h"
#include "proc_comm.h"
#if defined(CONFIG_ARCH_QSD8X50)
#define CONFIG_QDSP6 1
#endif
void (*msm_hw_reset_hook)(void);
#define MODULE_NAME "msm_smd"
enum {
MSM_SMD_DEBUG = 1U << 0,
MSM_SMSM_DEBUG = 1U << 0,
};
static int msm_smd_debug_mask;
struct shared_info {
int ready;
unsigned state;
};
static unsigned dummy_state[SMSM_STATE_COUNT];
static struct shared_info smd_info = {
.state = (unsigned) &dummy_state,
};
module_param_named(debug_mask, msm_smd_debug_mask,
int, S_IRUGO | S_IWUSR | S_IWGRP);
static unsigned last_heap_free = 0xffffffff;
static inline void notify_other_smsm(void)
{
msm_a2m_int(5);
#ifdef CONFIG_QDSP6
msm_a2m_int(8);
#endif
}
static inline void notify_modem_smd(void)
{
msm_a2m_int(0);
}
static inline void notify_dsp_smd(void)
{
msm_a2m_int(8);
}
static void smd_diag(void)
{
char *x;
x = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG);
if (x != 0) {
x[SZ_DIAG_ERR_MSG - 1] = 0;
pr_debug("DIAG '%s'\n", x);
}
}
/* call when SMSM_RESET flag is set in the A9's smsm_state */
static void handle_modem_crash(void)
{
pr_err("ARM9 has CRASHED\n");
smd_diag();
/* hard reboot if possible */
if (msm_hw_reset_hook)
msm_hw_reset_hook();
/* in this case the modem or watchdog should reboot us */
for (;;)
;
}
uint32_t raw_smsm_get_state(enum smsm_state_item item)
{
return readl(smd_info.state + item * 4);
}
static int check_for_modem_crash(void)
{
if (raw_smsm_get_state(SMSM_STATE_MODEM) & SMSM_RESET) {
handle_modem_crash();
return -1;
}
return 0;
}
/* the spinlock is used to synchronize between the
* irq handler and code that mutates the channel
* list or fiddles with channel state
*/
DEFINE_SPINLOCK(smd_lock);
DEFINE_SPINLOCK(smem_lock);
/* the mutex is used during open() and close()
* operations to avoid races while creating or
* destroying smd_channel structures
*/
static DEFINE_MUTEX(smd_creation_mutex);
static int smd_initialized;
LIST_HEAD(smd_ch_closed_list);
LIST_HEAD(smd_ch_list_modem);
LIST_HEAD(smd_ch_list_dsp);
static unsigned char smd_ch_allocated[64];
static struct work_struct probe_work;
/* how many bytes are available for reading */
static int smd_stream_read_avail(struct smd_channel *ch)
{
return (ch->recv->head - ch->recv->tail) & ch->fifo_mask;
}
/* how many bytes we are free to write */
static int smd_stream_write_avail(struct smd_channel *ch)
{
return ch->fifo_mask -
((ch->send->head - ch->send->tail) & ch->fifo_mask);
}
static int smd_packet_read_avail(struct smd_channel *ch)
{
if (ch->current_packet) {
int n = smd_stream_read_avail(ch);
if (n > ch->current_packet)
n = ch->current_packet;
return n;
} else {
return 0;
}
}
static int smd_packet_write_avail(struct smd_channel *ch)
{
int n = smd_stream_write_avail(ch);
return n > SMD_HEADER_SIZE ? n - SMD_HEADER_SIZE : 0;
}
static int ch_is_open(struct smd_channel *ch)
{
return (ch->recv->state == SMD_SS_OPENED) &&
(ch->send->state == SMD_SS_OPENED);
}
/* provide a pointer and length to readable data in the fifo */
static unsigned ch_read_buffer(struct smd_channel *