985 lines
28 KiB
C
Executable file
985 lines
28 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/* linux/drivers/modem/modem.c
|
|
*
|
|
* Copyright (C) 2010 Google, Inc.
|
|
* Copyright (C) 2010 Samsung Electronics.
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/io.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <uapi/linux/in.h>
|
|
#include <linux/inet.h>
|
|
#include <net/ipv6.h>
|
|
|
|
#if IS_ENABLED(CONFIG_LINK_DEVICE_SHMEM)
|
|
#include <soc/samsung/shm_ipc.h>
|
|
#include <soc/samsung/mcu_ipc.h>
|
|
#endif
|
|
|
|
#include <soc/samsung/exynos-modem-ctrl.h>
|
|
#include "modem_prj.h"
|
|
#include "modem_variation.h"
|
|
#include "modem_utils.h"
|
|
#include "modem_toe_device.h"
|
|
|
|
#if IS_ENABLED(CONFIG_MODEM_IF_LEGACY_QOS)
|
|
#include "cpif_qos_info.h"
|
|
#endif
|
|
|
|
#define FMT_WAKE_TIME (msecs_to_jiffies(300))
|
|
#define RAW_WAKE_TIME (HZ*6)
|
|
#define NET_WAKE_TIME (HZ/2)
|
|
|
|
static struct modem_shared *create_modem_shared_data(
|
|
struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct modem_shared *msd;
|
|
int size = MAX_MIF_BUFF_SIZE;
|
|
|
|
msd = devm_kzalloc(dev, sizeof(struct modem_shared), GFP_KERNEL);
|
|
if (!msd)
|
|
return NULL;
|
|
|
|
/* initialize link device list */
|
|
INIT_LIST_HEAD(&msd->link_dev_list);
|
|
INIT_LIST_HEAD(&msd->activated_ndev_list);
|
|
|
|
/* initialize tree of io devices */
|
|
msd->iodevs_tree_fmt = RB_ROOT;
|
|
|
|
msd->storage.cnt = 0;
|
|
msd->storage.addr = devm_kzalloc(dev, MAX_MIF_BUFF_SIZE +
|
|
(MAX_MIF_SEPA_SIZE * 2), GFP_KERNEL);
|
|
if (!msd->storage.addr) {
|
|
mif_err("IPC logger buff alloc failed!!\n");
|
|
kfree(msd);
|
|
return NULL;
|
|
}
|
|
memset(msd->storage.addr, 0, size + (MAX_MIF_SEPA_SIZE * 2));
|
|
memcpy(msd->storage.addr, MIF_SEPARATOR, strlen(MIF_SEPARATOR));
|
|
msd->storage.addr += MAX_MIF_SEPA_SIZE;
|
|
memcpy(msd->storage.addr, &size, sizeof(int));
|
|
msd->storage.addr += MAX_MIF_SEPA_SIZE;
|
|
spin_lock_init(&msd->lock);
|
|
spin_lock_init(&msd->active_list_lock);
|
|
|
|
return msd;
|
|
}
|
|
|
|
static struct modem_ctl *create_modemctl_device(struct platform_device *pdev,
|
|
struct modem_shared *msd)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct modem_data *pdata = pdev->dev.platform_data;
|
|
struct modem_ctl *modemctl;
|
|
int ret;
|
|
|
|
/* create modem control device */
|
|
modemctl = devm_kzalloc(dev, sizeof(struct modem_ctl), GFP_KERNEL);
|
|
if (!modemctl) {
|
|
mif_err("%s: modemctl devm_kzalloc fail\n", pdata->name);
|
|
mif_err("%s: xxx\n", pdata->name);
|
|
return NULL;
|
|
}
|
|
|
|
modemctl->dev = dev;
|
|
modemctl->name = pdata->name;
|
|
modemctl->mdm_data = pdata;
|
|
|
|
modemctl->msd = msd;
|
|
|
|
modemctl->phone_state = STATE_INIT;
|
|
|
|
INIT_LIST_HEAD(&modemctl->modem_state_notify_list);
|
|
spin_lock_init(&modemctl->lock);
|
|
spin_lock_init(&modemctl->tx_timer_lock);
|
|
init_completion(&modemctl->init_cmpl);
|
|
init_completion(&modemctl->off_cmpl);
|
|
|
|
/* init modemctl device for getting modemctl operations */
|
|
ret = call_modem_init_func(modemctl, pdata);
|
|
if (ret) {
|
|
mif_err("%s: call_modem_init_func fail (err %d)\n",
|
|
pdata->name, ret);
|
|
mif_err("%s: xxx\n", pdata->name);
|
|
devm_kfree(dev, modemctl);
|
|
return NULL;
|
|
}
|
|
|
|
mif_info("%s is created!!!\n", pdata->name);
|
|
|
|
return modemctl;
|
|
}
|
|
|
|
static struct io_device *create_io_device(struct platform_device *pdev,
|
|
struct modem_io_t *io_t, struct modem_shared *msd,
|
|
struct modem_ctl *modemctl, struct modem_data *pdata)
|
|
{
|
|
int ret;
|
|
struct device *dev = &pdev->dev;
|
|
struct io_device *iod;
|
|
|
|
iod = devm_kzalloc(dev, sizeof(struct io_device), GFP_KERNEL);
|
|
if (!iod) {
|
|
mif_err("iod == NULL\n");
|
|
return NULL;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&iod->list);
|
|
RB_CLEAR_NODE(&iod->node_fmt);
|
|
|
|
iod->name = io_t->name;
|
|
iod->ch = io_t->ch;
|
|
iod->format = io_t->format;
|
|
iod->io_typ = io_t->io_type;
|
|
iod->link_type = io_t->link_type;
|
|
iod->attrs = io_t->attrs;
|
|
iod->max_tx_size = io_t->ul_buffer_size;
|
|
iod->ipc_version = pdata->ipc_version;
|
|
#if IS_ENABLED(CONFIG_CPIF_USERSPACE_NETWORK)
|
|
iod->p_type = -1;
|
|
#endif
|
|
atomic_set(&iod->opened, 0);
|
|
spin_lock_init(&iod->info_id_lock);
|
|
spin_lock_init(&iod->clat_lock);
|
|
|
|
/* link between io device and modem control */
|
|
iod->mc = modemctl;
|
|
|
|
switch (pdata->protocol) {
|
|
case PROTOCOL_SIPC:
|
|
if (iod->format == IPC_FMT && iod->ch == SIPC5_CH_ID_FMT_0)
|
|
modemctl->iod = iod;
|
|
break;
|
|
case PROTOCOL_SIT:
|
|
if (iod->format == IPC_FMT && iod->ch == EXYNOS_CH_ID_FMT_0)
|
|
modemctl->iod = iod;
|
|
break;
|
|
default:
|
|
mif_err("protocol error\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (iod->format == IPC_BOOT) {
|
|
modemctl->bootd = iod;
|
|
mif_info("BOOT device = %s\n", iod->name);
|
|
}
|
|
|
|
/* link between io device and modem shared */
|
|
iod->msd = msd;
|
|
|
|
/* add iod to rb_tree */
|
|
if (iod->format != IPC_RAW)
|
|
insert_iod_with_format(msd, iod->format, iod);
|
|
|
|
switch (pdata->protocol) {
|
|
case PROTOCOL_SIPC:
|
|
if (sipc5_is_not_reserved_channel(iod->ch))
|
|
insert_iod_with_channel(msd, iod->ch, iod);
|
|
break;
|
|
case PROTOCOL_SIT:
|
|
insert_iod_with_channel(msd, iod->ch, iod);
|
|
break;
|
|
default:
|
|
mif_err("protocol error\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* register misc device or net device */
|
|
ret = sipc5_init_io_device(iod);
|
|
if (ret) {
|
|
devm_kfree(dev, iod);
|
|
mif_err("sipc5_init_io_device fail (%d)\n", ret);
|
|
return NULL;
|
|
}
|
|
|
|
mif_info("%s created. attrs:0x%08x\n", iod->name, iod->attrs);
|
|
return iod;
|
|
}
|
|
|
|
static int attach_devices(struct io_device *iod, struct device *dev)
|
|
{
|
|
struct modem_shared *msd = iod->msd;
|
|
struct link_device *ld;
|
|
|
|
/* find link type for this io device */
|
|
list_for_each_entry(ld, &msd->link_dev_list, list) {
|
|
if (IS_CONNECTED(iod, ld)) {
|
|
mif_debug("set %s->%s\n", iod->name, ld->name);
|
|
set_current_link(iod, ld);
|
|
|
|
if (iod->io_typ == IODEV_NET && iod->ndev) {
|
|
struct vnet *vnet;
|
|
|
|
vnet = netdev_priv(iod->ndev);
|
|
vnet->hiprio_ack_only = ld->hiprio_ack_only;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!get_current_link(iod)) {
|
|
mif_err("%s->link == NULL\n", iod->name);
|
|
BUG();
|
|
}
|
|
|
|
iod->ws = cpif_wake_lock_register(dev, iod->name);
|
|
if (iod->ws == NULL) {
|
|
mif_err("%s: wakeup_source_register fail\n", iod->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (iod->ch) {
|
|
case SIPC5_CH_ID_FMT_0 ... SIPC5_CH_ID_FMT_9:
|
|
iod->waketime = FMT_WAKE_TIME;
|
|
break;
|
|
|
|
case SIPC5_CH_ID_BOOT_0 ... SIPC5_CH_ID_DUMP_9:
|
|
iod->waketime = RAW_WAKE_TIME;
|
|
break;
|
|
|
|
#if IS_ENABLED(CONFIG_CH_EXTENSION)
|
|
case SIPC_CH_EX_ID_PDP_0 ... SIPC_CH_EX_ID_PDP_MAX:
|
|
case SIPC_CH_ID_BT_DUN ... SIPC_CH_ID_CIQ_DATA:
|
|
case SIPC_CH_ID_CPLOG1 ... SIPC_CH_ID_LOOPBACK2:
|
|
#else
|
|
case SIPC_CH_ID_PDP_0 ... SIPC_CH_ID_LOOPBACK2:
|
|
#endif
|
|
iod->waketime = NET_WAKE_TIME;
|
|
break;
|
|
|
|
default:
|
|
iod->waketime = 0;
|
|
break;
|
|
}
|
|
|
|
switch (iod->format) {
|
|
case IPC_FMT:
|
|
iod->waketime = FMT_WAKE_TIME;
|
|
break;
|
|
|
|
case IPC_BOOT ... IPC_DUMP:
|
|
iod->waketime = RAW_WAKE_TIME;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_dt_common_pdata(struct device_node *np,
|
|
struct modem_data *pdata)
|
|
{
|
|
mif_dt_read_string(np, "mif,name", pdata->name);
|
|
mif_dt_read_u32(np, "mif,cp_num", pdata->cp_num);
|
|
|
|
mif_dt_read_enum(np, "mif,modem_type", pdata->modem_type);
|
|
mif_dt_read_enum(np, "mif,ipc_version", pdata->ipc_version);
|
|
|
|
mif_dt_read_u32_noerr(np, "mif,protocol", pdata->protocol);
|
|
mif_info("protocol:%d\n", pdata->protocol);
|
|
|
|
mif_dt_read_u32(np, "mif,link_type", pdata->link_type);
|
|
mif_dt_read_string(np, "mif,link_name", pdata->link_name);
|
|
mif_dt_read_u32(np, "mif,link_attrs", pdata->link_attrs);
|
|
mif_dt_read_u32(np, "mif,interrupt_types", pdata->interrupt_types);
|
|
|
|
mif_dt_read_u32_noerr(np, "mif,capability_check", pdata->capability_check);
|
|
mif_info("capability_check:%d\n", pdata->capability_check);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_dt_mbox_pdata(struct device *dev, struct device_node *np,
|
|
struct modem_data *pdata)
|
|
{
|
|
struct modem_mbox *mbox;
|
|
int ret = 0;
|
|
|
|
if ((pdata->link_type != LINKDEV_SHMEM) &&
|
|
(pdata->link_type != LINKDEV_PCIE)) {
|
|
mif_err("mbox: link type error:0x%08x\n", pdata->link_type);
|
|
return ret;
|
|
}
|
|
|
|
mbox = (struct modem_mbox *)devm_kzalloc(dev, sizeof(struct modem_mbox), GFP_KERNEL);
|
|
if (!mbox) {
|
|
mif_err("mbox: failed to alloc memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
pdata->mbx = mbox;
|
|
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_msg", mbox->int_ap2cp_msg);
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_wakeup", mbox->int_ap2cp_wakeup);
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_status", mbox->int_ap2cp_status);
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_active", mbox->int_ap2cp_active);
|
|
#if IS_ENABLED(CONFIG_CP_LCD_NOTIFIER)
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_lcd_status", mbox->int_ap2cp_lcd_status);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_CP_LLC)
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_llc_status", mbox->int_ap2cp_llc_status);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_CP_PKTPROC_CLAT)
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_clatinfo_send", mbox->int_ap2cp_clatinfo_send);
|
|
#endif
|
|
mif_dt_read_u32(np, "mif,int_ap2cp_uart_noti", mbox->int_ap2cp_uart_noti);
|
|
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_msg", mbox->irq_cp2ap_msg);
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_status", mbox->irq_cp2ap_status);
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_active", mbox->irq_cp2ap_active);
|
|
#if IS_ENABLED(CONFIG_CP_LLC)
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_llc_status", mbox->irq_cp2ap_llc_status);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_CP_PKTPROC_CLAT)
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_clatinfo_ack", mbox->irq_cp2ap_clatinfo_ack);
|
|
#endif
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_wakelock", mbox->irq_cp2ap_wakelock);
|
|
mif_dt_read_u32(np, "mif,irq_cp2ap_ratmode", mbox->irq_cp2ap_rat_mode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_dt_ipc_region_pdata(struct device *dev, struct device_node *np,
|
|
struct modem_data *pdata)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* legacy buffer (fmt, raw) setting */
|
|
mif_dt_read_u32(np, "legacy_fmt_head_tail_offset",
|
|
pdata->legacy_fmt_head_tail_offset);
|
|
mif_dt_read_u32(np, "legacy_fmt_buffer_offset", pdata->legacy_fmt_buffer_offset);
|
|
mif_dt_read_u32(np, "legacy_fmt_txq_size", pdata->legacy_fmt_txq_size);
|
|
mif_dt_read_u32(np, "legacy_fmt_rxq_size", pdata->legacy_fmt_rxq_size);
|
|
mif_dt_read_u32(np, "legacy_raw_head_tail_offset",
|
|
pdata->legacy_raw_head_tail_offset);
|
|
mif_dt_read_u32(np, "legacy_raw_buffer_offset", pdata->legacy_raw_buffer_offset);
|
|
mif_dt_read_u32(np, "legacy_raw_txq_size", pdata->legacy_raw_txq_size);
|
|
mif_dt_read_u32(np, "legacy_raw_rxq_size", pdata->legacy_raw_rxq_size);
|
|
mif_dt_read_u32(np, "legacy_raw_rx_buffer_cached",
|
|
pdata->legacy_raw_rx_buffer_cached);
|
|
|
|
mif_dt_read_u32_noerr(np, "offset_ap_version", pdata->offset_ap_version);
|
|
mif_dt_read_u32_noerr(np, "offset_cp_version", pdata->offset_cp_version);
|
|
mif_dt_read_u32_noerr(np, "offset_cmsg_offset", pdata->offset_cmsg_offset);
|
|
mif_dt_read_u32_noerr(np, "offset_srinfo_offset", pdata->offset_srinfo_offset);
|
|
mif_dt_read_u32_noerr(np, "offset_clk_table_offset", pdata->offset_clk_table_offset);
|
|
mif_dt_read_u32_noerr(np, "offset_buff_desc_offset", pdata->offset_buff_desc_offset);
|
|
mif_dt_read_u32_noerr(np, "offset_capability_offset", pdata->offset_capability_offset);
|
|
|
|
#if IS_ENABLED(CONFIG_MODEM_IF_LEGACY_QOS)
|
|
/* legacy priority queue setting */
|
|
mif_dt_read_u32(np, "legacy_raw_qos_head_tail_offset",
|
|
pdata->legacy_raw_qos_head_tail_offset);
|
|
mif_dt_read_u32(np, "legacy_raw_qos_buffer_offset", pdata->legacy_raw_qos_buffer_offset);
|
|
mif_dt_read_u32(np, "legacy_raw_qos_txq_size", pdata->legacy_raw_qos_txq_size);
|
|
mif_dt_read_u32(np, "legacy_raw_qos_rxq_size", pdata->legacy_raw_qos_rxq_size);
|
|
#endif
|
|
|
|
/* control message offset setting (optional)*/
|
|
mif_dt_read_u32_noerr(np, "cmsg_offset", pdata->cmsg_offset);
|
|
/* srinfo settings */
|
|
mif_dt_read_u32(np, "srinfo_offset", pdata->srinfo_offset);
|
|
mif_dt_read_u32(np, "srinfo_size", pdata->srinfo_size);
|
|
/* clk_table offset (optional)*/
|
|
mif_dt_read_u32_noerr(np, "clk_table_offset", pdata->clk_table_offset);
|
|
/* offset setting for new SIT buffer descriptors (optional) */
|
|
mif_dt_read_u32_noerr(np, "buff_desc_offset", pdata->buff_desc_offset);
|
|
|
|
/* offset setting for capability */
|
|
if (pdata->capability_check) {
|
|
mif_dt_read_u32(np, "capability_offset", pdata->capability_offset);
|
|
mif_dt_read_u32(np, "ap_capability_0", pdata->ap_capability[0]);
|
|
mif_dt_read_u32(np, "ap_capability_1", pdata->ap_capability[1]);
|
|
}
|
|
|
|
of_property_read_u32_array(np, "ap2cp_msg", pdata->ap2cp_msg, 2);
|
|
of_property_read_u32_array(np, "cp2ap_msg", pdata->cp2ap_msg, 2);
|
|
of_property_read_u32_array(np, "cp2ap_united_status", pdata->cp2ap_united_status, 2);
|
|
of_property_read_u32_array(np, "ap2cp_united_status", pdata->ap2cp_united_status, 2);
|
|
#if IS_ENABLED(CONFIG_CP_LLC)
|
|
of_property_read_u32_array(np, "ap2cp_llc_status", pdata->ap2cp_llc_status, 2);
|
|
of_property_read_u32_array(np, "cp2ap_llc_status", pdata->cp2ap_llc_status, 2);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_CP_PKTPROC_CLAT)
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_xlat_v4_addr",
|
|
pdata->ap2cp_clatinfo_xlat_v4_addr, 2);
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_xlat_addr_0",
|
|
pdata->ap2cp_clatinfo_xlat_addr_0, 2);
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_xlat_addr_1",
|
|
pdata->ap2cp_clatinfo_xlat_addr_1, 2);
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_xlat_addr_2",
|
|
pdata->ap2cp_clatinfo_xlat_addr_2, 2);
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_xlat_addr_3",
|
|
pdata->ap2cp_clatinfo_xlat_addr_3, 2);
|
|
mif_dt_count_u32_array(np, "ap2cp_clatinfo_index",
|
|
pdata->ap2cp_clatinfo_index, 2);
|
|
#endif
|
|
of_property_read_u32_array(np, "ap2cp_kerneltime", pdata->ap2cp_kerneltime, 2);
|
|
of_property_read_u32_array(np, "ap2cp_kerneltime_sec", pdata->ap2cp_kerneltime_sec, 2);
|
|
of_property_read_u32_array(np, "ap2cp_kerneltime_usec", pdata->ap2cp_kerneltime_usec, 2);
|
|
|
|
/* Status Bit Info */
|
|
mif_dt_read_u32(np, "sbi_lte_active_mask", pdata->sbi_lte_active_mask);
|
|
mif_dt_read_u32(np, "sbi_lte_active_pos", pdata->sbi_lte_active_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_status_mask", pdata->sbi_cp_status_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_status_pos", pdata->sbi_cp_status_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_rat_mode_mask", pdata->sbi_cp2ap_rat_mode_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_rat_mode_pos", pdata->sbi_cp2ap_rat_mode_pos);
|
|
mif_dt_read_u32(np, "sbi_cp2ap_wakelock_mask", pdata->sbi_cp2ap_wakelock_mask);
|
|
mif_dt_read_u32(np, "sbi_cp2ap_wakelock_pos", pdata->sbi_cp2ap_wakelock_pos);
|
|
mif_dt_read_u32(np, "sbi_pda_active_mask", pdata->sbi_pda_active_mask);
|
|
mif_dt_read_u32(np, "sbi_pda_active_pos", pdata->sbi_pda_active_pos);
|
|
mif_dt_read_u32(np, "sbi_ap_status_mask", pdata->sbi_ap_status_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_status_pos", pdata->sbi_ap_status_pos);
|
|
mif_dt_read_u32(np, "sbi_crash_type_mask", pdata->sbi_crash_type_mask);
|
|
mif_dt_read_u32(np, "sbi_crash_type_pos", pdata->sbi_crash_type_pos);
|
|
#if IS_ENABLED(CONFIG_CP_LCD_NOTIFIER)
|
|
mif_dt_read_u32(np, "sbi_lcd_status_mask", pdata->sbi_lcd_status_mask);
|
|
mif_dt_read_u32(np, "sbi_lcd_status_pos", pdata->sbi_lcd_status_pos);
|
|
#endif
|
|
mif_dt_read_u32(np, "sbi_uart_noti_mask", pdata->sbi_uart_noti_mask);
|
|
mif_dt_read_u32(np, "sbi_uart_noti_pos", pdata->sbi_uart_noti_pos);
|
|
mif_dt_read_u32(np, "sbi_ds_det_mask", pdata->sbi_ds_det_mask);
|
|
mif_dt_read_u32(np, "sbi_ds_det_pos", pdata->sbi_ds_det_pos);
|
|
#if IS_ENABLED(CONFIG_CP_LLC)
|
|
mif_dt_read_u32(np, "sbi_ap_llc_request_mask", pdata->sbi_ap_llc_request_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_request_pos", pdata->sbi_ap_llc_request_pos);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_target_mask", pdata->sbi_ap_llc_target_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_target_pos", pdata->sbi_ap_llc_target_pos);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_way_mask", pdata->sbi_ap_llc_way_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_way_pos", pdata->sbi_ap_llc_way_pos);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_alloc_mask", pdata->sbi_ap_llc_alloc_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_alloc_pos", pdata->sbi_ap_llc_alloc_pos);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_return_mask", pdata->sbi_ap_llc_return_mask);
|
|
mif_dt_read_u32(np, "sbi_ap_llc_return_pos", pdata->sbi_ap_llc_return_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_request_mask", pdata->sbi_cp_llc_request_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_request_pos", pdata->sbi_cp_llc_request_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_target_mask", pdata->sbi_cp_llc_target_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_target_pos", pdata->sbi_cp_llc_target_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_way_mask", pdata->sbi_cp_llc_way_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_way_pos", pdata->sbi_cp_llc_way_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_alloc_mask", pdata->sbi_cp_llc_alloc_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_alloc_pos", pdata->sbi_cp_llc_alloc_pos);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_return_mask", pdata->sbi_cp_llc_return_mask);
|
|
mif_dt_read_u32(np, "sbi_cp_llc_return_pos", pdata->sbi_cp_llc_return_pos);
|
|
#endif
|
|
|
|
mif_dt_read_u32_noerr(np, "sbi_ap2cp_kerneltime_sec_mask",
|
|
pdata->sbi_ap2cp_kerneltime_sec_mask);
|
|
mif_dt_read_u32_noerr(np, "sbi_ap2cp_kerneltime_sec_pos",
|
|
pdata->sbi_ap2cp_kerneltime_sec_pos);
|
|
mif_dt_read_u32_noerr(np, "sbi_ap2cp_kerneltime_usec_mask",
|
|
pdata->sbi_ap2cp_kerneltime_usec_mask);
|
|
mif_dt_read_u32_noerr(np, "sbi_ap2cp_kerneltime_usec_pos",
|
|
pdata->sbi_ap2cp_kerneltime_usec_pos);
|
|
|
|
/* Check pktproc use 36bit addr */
|
|
mif_dt_read_u32(np, "pktproc_use_36bit_addr",
|
|
pdata->pktproc_use_36bit_addr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_dt_iodevs_pdata(struct device *dev, struct device_node *np,
|
|
struct modem_data *pdata)
|
|
{
|
|
struct device_node *child = NULL;
|
|
|
|
for_each_child_of_node(np, child) {
|
|
struct modem_io_t *p_iod = NULL;
|
|
struct modem_io_t *iod;
|
|
unsigned int ch_count = 0;
|
|
char *name;
|
|
|
|
do {
|
|
iod = devm_kzalloc(dev, sizeof(struct modem_io_t), GFP_KERNEL);
|
|
if (!iod) {
|
|
mif_err("failed to alloc iodev\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!p_iod) {
|
|
mif_dt_read_string(child, "iod,name", name);
|
|
mif_dt_read_u32(child, "iod,ch", iod->ch);
|
|
mif_dt_read_enum(child, "iod,format", iod->format);
|
|
mif_dt_read_enum(child, "iod,io_type", iod->io_type);
|
|
mif_dt_read_u32(child, "iod,link_type", iod->link_type);
|
|
mif_dt_read_u32(child, "iod,attrs", iod->attrs);
|
|
mif_dt_read_u32_noerr(child, "iod,max_tx_size",
|
|
iod->ul_buffer_size);
|
|
|
|
if (iod->attrs & IO_ATTR_SBD_IPC) {
|
|
mif_dt_read_u32(child, "iod,ul_num_buffers",
|
|
iod->ul_num_buffers);
|
|
mif_dt_read_u32(child, "iod,ul_buffer_size",
|
|
iod->ul_buffer_size);
|
|
mif_dt_read_u32(child, "iod,dl_num_buffers",
|
|
iod->dl_num_buffers);
|
|
mif_dt_read_u32(child, "iod,dl_buffer_size",
|
|
iod->dl_buffer_size);
|
|
}
|
|
|
|
if (iod->attrs & IO_ATTR_OPTION_REGION)
|
|
mif_dt_read_string(child, "iod,option_region",
|
|
iod->option_region);
|
|
|
|
if (iod->attrs & IO_ATTR_MULTI_CH)
|
|
mif_dt_read_u32(child, "iod,ch_count", iod->ch_count);
|
|
|
|
p_iod = iod;
|
|
} else {
|
|
memcpy(iod, p_iod, sizeof(struct modem_io_t));
|
|
}
|
|
|
|
if (ch_count < iod->ch_count) {
|
|
snprintf(iod->name, sizeof(iod->name), "%s%d", name, ch_count);
|
|
iod->ch = p_iod->ch + ch_count;
|
|
} else {
|
|
snprintf(iod->name, sizeof(iod->name), "%s", name);
|
|
}
|
|
|
|
pdata->iodevs[pdata->num_iodevs] = iod;
|
|
pdata->num_iodevs++;
|
|
} while (++ch_count < iod->ch_count);
|
|
}
|
|
|
|
mif_info("num_iodevs:%d\n", pdata->num_iodevs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct modem_data *modem_if_parse_dt_pdata(struct device *dev)
|
|
{
|
|
struct modem_data *pdata;
|
|
struct device_node *iodevs_node = NULL;
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(struct modem_data), GFP_KERNEL);
|
|
if (!pdata) {
|
|
mif_err("modem_data: alloc fail\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
if (parse_dt_common_pdata(dev->of_node, pdata)) {
|
|
mif_err("DT error: failed to parse common\n");
|
|
goto error;
|
|
}
|
|
|
|
if (parse_dt_mbox_pdata(dev, dev->of_node, pdata)) {
|
|
mif_err("DT error: failed to parse mbox\n");
|
|
goto error;
|
|
}
|
|
|
|
if (parse_dt_ipc_region_pdata(dev, dev->of_node, pdata)) {
|
|
mif_err("DT error: failed to parse control messages\n");
|
|
goto error;
|
|
}
|
|
|
|
iodevs_node = of_get_child_by_name(dev->of_node, "iodevs");
|
|
if (!iodevs_node) {
|
|
mif_err("DT error: failed to get child node\n");
|
|
goto error;
|
|
}
|
|
|
|
if (parse_dt_iodevs_pdata(dev, iodevs_node, pdata)) {
|
|
mif_err("DT error: failed to parse iodevs\n");
|
|
goto error;
|
|
}
|
|
|
|
dev->platform_data = pdata;
|
|
mif_info("DT parse complete!\n");
|
|
|
|
return pdata;
|
|
|
|
error:
|
|
if (pdata) {
|
|
unsigned int id;
|
|
|
|
for (id = 0; id < ARRAY_SIZE(pdata->iodevs); id++) {
|
|
if (pdata->iodevs[id])
|
|
devm_kfree(dev, pdata->iodevs[id]);
|
|
}
|
|
|
|
devm_kfree(dev, pdata);
|
|
}
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
static const struct of_device_id cpif_dt_match[] = {
|
|
{ .compatible = "samsung,exynos-cp", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, cpif_dt_match);
|
|
|
|
enum mif_sim_mode {
|
|
MIF_SIM_NONE = 0,
|
|
MIF_SIM_SINGLE,
|
|
MIF_SIM_DUAL,
|
|
MIF_SIM_TRIPLE,
|
|
};
|
|
|
|
static int simslot_count(struct seq_file *m, void *v)
|
|
{
|
|
enum mif_sim_mode mode = (enum mif_sim_mode)m->private;
|
|
|
|
seq_printf(m, "%u\n", mode);
|
|
return 0;
|
|
}
|
|
|
|
static int simslot_count_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, simslot_count, PDE_DATA(inode));
|
|
}
|
|
|
|
static const struct file_operations simslot_count_fops = {
|
|
.open = simslot_count_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_GPIO_DS_DETECT)
|
|
static enum mif_sim_mode get_sim_mode(struct device_node *of_node)
|
|
{
|
|
enum mif_sim_mode mode = MIF_SIM_SINGLE;
|
|
int gpio_ds_det;
|
|
int retval;
|
|
|
|
gpio_ds_det = of_get_named_gpio(of_node, "mif,gpio_ds_det", 0);
|
|
if (!gpio_is_valid(gpio_ds_det)) {
|
|
mif_err("DT error: failed to get sim mode\n");
|
|
goto make_proc;
|
|
}
|
|
|
|
retval = gpio_request(gpio_ds_det, "DS_DET");
|
|
if (retval) {
|
|
mif_err("Failed to request GPIO(%d)\n", retval);
|
|
goto make_proc;
|
|
} else {
|
|
gpio_direction_input(gpio_ds_det);
|
|
}
|
|
|
|
retval = gpio_get_value(gpio_ds_det);
|
|
if (retval)
|
|
mode = MIF_SIM_DUAL;
|
|
|
|
mif_info("sim_mode: %d\n", mode);
|
|
gpio_free(gpio_ds_det);
|
|
|
|
make_proc:
|
|
if (!proc_create_data("simslot_count", 0444, NULL, &simslot_count_fops,
|
|
(void *)(long)mode)) {
|
|
mif_err("Failed to create proc\n");
|
|
mode = MIF_SIM_SINGLE;
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
#else
|
|
static enum mif_sim_mode get_sim_mode(struct device_node *of_node)
|
|
{
|
|
return MIF_SIM_SINGLE;
|
|
}
|
|
#endif
|
|
|
|
static ssize_t do_cp_crash_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
modem_force_crash_exit_ext();
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t modem_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_ctl *mc = dev_get_drvdata(dev);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n", cp_state_str(mc->phone_state));
|
|
}
|
|
|
|
static DEVICE_ATTR_WO(do_cp_crash);
|
|
static DEVICE_ATTR_RO(modem_state);
|
|
|
|
static struct attribute *modem_attrs[] = {
|
|
&dev_attr_do_cp_crash.attr,
|
|
&dev_attr_modem_state.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(modem);
|
|
|
|
static int cpif_cdev_alloc_region(struct modem_data *pdata, struct modem_shared *msd)
|
|
{
|
|
int ret;
|
|
|
|
ret = alloc_chrdev_region(&msd->cdev_major, 0, pdata->num_iodevs, "cpif");
|
|
if (ret < 0) {
|
|
mif_err("alloc_chrdev_region() failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
msd->cdev_class = class_create(THIS_MODULE, "cpif");
|
|
if (IS_ERR(msd->cdev_class)) {
|
|
mif_err("class_create() failed:%ld\n", PTR_ERR(msd->cdev_class));
|
|
ret = -ENOMEM;
|
|
unregister_chrdev_region(MAJOR(msd->cdev_major), pdata->num_iodevs);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cpif_probe(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
struct device *dev = &pdev->dev;
|
|
struct modem_data *pdata = dev->platform_data;
|
|
struct modem_shared *msd;
|
|
struct modem_ctl *modemctl;
|
|
struct io_device **iod;
|
|
size_t size;
|
|
struct link_device *ld;
|
|
enum mif_sim_mode sim_mode;
|
|
int err;
|
|
|
|
#if IS_ENABLED(CONFIG_CPIF_MEMORY_LOGGER)
|
|
cpif_memlog_init(pdev);
|
|
#endif
|
|
|
|
mif_info("Exynos CP interface driver %s\n", get_cpif_driver_version());
|
|
mif_info("%s: +++ (%s)\n", pdev->name, CONFIG_OPTION_REGION);
|
|
|
|
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
|
|
if (err) {
|
|
mif_err("dma_set_mask_and_coherent() error:%d\n", err);
|
|
goto fail;
|
|
}
|
|
|
|
if (dev->of_node) {
|
|
pdata = modem_if_parse_dt_pdata(dev);
|
|
if (IS_ERR(pdata)) {
|
|
mif_err("MIF DT parse error!\n");
|
|
err = PTR_ERR(pdata);
|
|
goto fail;
|
|
}
|
|
} else {
|
|
if (!pdata) {
|
|
mif_err("Non-DT, incorrect pdata!\n");
|
|
err = -EINVAL;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
msd = create_modem_shared_data(pdev);
|
|
if (!msd) {
|
|
mif_err("%s: msd == NULL\n", pdata->name);
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
modemctl = create_modemctl_device(pdev, msd);
|
|
if (!modemctl) {
|
|
mif_err("%s: modemctl == NULL\n", pdata->name);
|
|
devm_kfree(dev, msd);
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (toe_dev_create(pdev)) {
|
|
mif_err("%s: toe dev not created\n", pdata->name);
|
|
goto free_mc;
|
|
}
|
|
|
|
/* create link device */
|
|
ld = call_link_init_func(pdev, pdata->link_type);
|
|
if (!ld)
|
|
goto free_mc;
|
|
|
|
mif_info("%s: %s link created\n", pdata->name, ld->name);
|
|
|
|
ld->mc = modemctl;
|
|
ld->msd = msd;
|
|
list_add(&ld->list, &msd->link_dev_list);
|
|
|
|
/* set sime mode */
|
|
sim_mode = get_sim_mode(dev->of_node);
|
|
|
|
/* char device */
|
|
err = cpif_cdev_alloc_region(pdata, msd);
|
|
if (err) {
|
|
mif_err("cpif_cdev_alloc_region() err:%d\n", err);
|
|
goto free_mc;
|
|
}
|
|
|
|
/* create io deivces and connect to modemctl device */
|
|
size = sizeof(struct io_device *) * pdata->num_iodevs;
|
|
iod = kzalloc(size, GFP_KERNEL);
|
|
if (!iod) {
|
|
mif_err("kzalloc() err\n");
|
|
goto free_chrdev;
|
|
}
|
|
|
|
for (i = 0; i < pdata->num_iodevs; i++) {
|
|
if (sim_mode < MIF_SIM_DUAL &&
|
|
pdata->iodevs[i]->attrs & IO_ATTR_DUALSIM)
|
|
continue;
|
|
|
|
if (pdata->iodevs[i]->attrs & IO_ATTR_OPTION_REGION &&
|
|
strncmp(pdata->iodevs[i]->option_region, CONFIG_OPTION_REGION,
|
|
strlen(pdata->iodevs[i]->option_region)))
|
|
continue;
|
|
|
|
iod[i] = create_io_device(pdev, pdata->iodevs[i], msd,
|
|
modemctl, pdata);
|
|
if (!iod[i]) {
|
|
mif_err("%s: iod[%d] == NULL\n", pdata->name, i);
|
|
goto free_iod;
|
|
}
|
|
|
|
if (iod[i]->format == IPC_FMT || iod[i]->format == IPC_BOOT
|
|
|| iod[i]->ch == SIPC_CH_ID_CASS)
|
|
list_add_tail(&iod[i]->list,
|
|
&modemctl->modem_state_notify_list);
|
|
|
|
attach_devices(iod[i], dev);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_CPIF_USERSPACE_NETWORK)
|
|
init_usnet_io_device(dev, ld, modemctl);
|
|
#endif
|
|
|
|
cp_btl_create(&pdata->btl, dev);
|
|
|
|
platform_set_drvdata(pdev, modemctl);
|
|
|
|
kfree(iod);
|
|
|
|
if (sysfs_create_groups(&dev->kobj, modem_groups))
|
|
mif_err("failed to create modem groups node\n");
|
|
|
|
#if IS_ENABLED(CONFIG_MODEM_IF_LEGACY_QOS)
|
|
err = cpif_qos_init_list();
|
|
if (err < 0)
|
|
mif_err("failed to initialize hiprio list(%d)\n", err);
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_CPIF_VENDOR_HOOK)
|
|
err = hook_init();
|
|
if (err)
|
|
mif_err("failed to register vendor hook\n");
|
|
#endif
|
|
|
|
mif_info("%s: done ---\n", pdev->name);
|
|
return 0;
|
|
|
|
free_iod:
|
|
for (i = 0; i < pdata->num_iodevs; i++) {
|
|
if (iod[i]) {
|
|
sipc5_deinit_io_device(iod[i]);
|
|
devm_kfree(dev, iod[i]);
|
|
}
|
|
}
|
|
kfree(iod);
|
|
|
|
free_chrdev:
|
|
class_destroy(msd->cdev_class);
|
|
unregister_chrdev_region(MAJOR(msd->cdev_major), pdata->num_iodevs);
|
|
|
|
free_mc:
|
|
if (modemctl)
|
|
devm_kfree(dev, modemctl);
|
|
|
|
if (msd)
|
|
devm_kfree(dev, msd);
|
|
|
|
err = -ENOMEM;
|
|
|
|
fail:
|
|
mif_err("%s: xxx\n", pdev->name);
|
|
|
|
panic("CP interface driver probe failed\n");
|
|
return err;
|
|
}
|
|
|
|
static void cpif_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct modem_ctl *mc = dev_get_drvdata(dev);
|
|
|
|
if (mc->ops.power_shutdown)
|
|
mc->ops.power_shutdown(mc);
|
|
|
|
mc->phone_state = STATE_OFFLINE;
|
|
|
|
mif_err("%s\n", mc->name);
|
|
}
|
|
|
|
static int modem_suspend(struct device *pdev)
|
|
{
|
|
struct modem_ctl *mc = dev_get_drvdata(pdev);
|
|
|
|
if (mc->ops.suspend)
|
|
mc->ops.suspend(mc);
|
|
|
|
set_wakeup_packet_log(true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int modem_resume(struct device *pdev)
|
|
{
|
|
struct modem_ctl *mc = dev_get_drvdata(pdev);
|
|
|
|
set_wakeup_packet_log(false);
|
|
|
|
if (mc->ops.resume)
|
|
mc->ops.resume(mc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops cpif_pm_ops = {
|
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(modem_suspend, modem_resume)
|
|
};
|
|
|
|
static struct platform_driver cpif_driver = {
|
|
.probe = cpif_probe,
|
|
.shutdown = cpif_shutdown,
|
|
.driver = {
|
|
.name = "cp_interface",
|
|
.owner = THIS_MODULE,
|
|
.pm = &cpif_pm_ops,
|
|
.suppress_bind_attrs = true,
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
.of_match_table = of_match_ptr(cpif_dt_match),
|
|
#endif
|
|
},
|
|
};
|
|
|
|
module_platform_driver(cpif_driver);
|
|
|
|
MODULE_DESCRIPTION("Exynos CP interface Driver");
|
|
MODULE_LICENSE("GPL");
|