| /* linux/drivers/modem/modem.c |
| * |
| * Copyright (C) 2010 Google, Inc. |
| * 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/init.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/miscdevice.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> |
| #ifdef CONFIG_OF |
| #include <linux/of_gpio.h> |
| #endif |
| #include <linux/delay.h> |
| #include <linux/wakelock.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/dma-contiguous.h> |
| |
| #ifdef CONFIG_LINK_DEVICE_SHMEM |
| #include <linux/shm_ipc.h> |
| #include <linux/mcu_ipc.h> |
| #endif |
| #if defined(CONFIG_LINK_DEVICE_LLI) &&\ |
| !defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| #include <linux/mipi-lli.h> |
| #endif |
| |
| #include "modem_prj.h" |
| #include "modem_variation.h" |
| #include "modem_utils.h" |
| #include "uart_switch.h" |
| |
| #define FMT_WAKE_TIME (HZ/2) |
| #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_OFFLINE; |
| modemctl->airplane_mode = 0; |
| |
| INIT_LIST_HEAD(&modemctl->modem_state_notify_list); |
| spin_lock_init(&modemctl->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_chan); |
| RB_CLEAR_NODE(&iod->node_fmt); |
| |
| iod->name = io_t->name; |
| iod->id = io_t->id; |
| iod->format = io_t->format; |
| iod->io_typ = io_t->io_type; |
| iod->link_types = io_t->links; |
| iod->attrs = io_t->attrs; |
| iod->app = io_t->app; |
| iod->max_tx_size = io_t->ul_buffer_size; |
| iod->net_typ = pdata->modem_net; |
| iod->use_handover = pdata->use_handover; |
| iod->ipc_version = pdata->ipc_version; |
| atomic_set(&iod->opened, 0); |
| spin_lock_init(&iod->info_id_lock); |
| |
| /* link between io device and modem control */ |
| iod->mc = modemctl; |
| |
| if (iod->format == IPC_FMT && iod->id == SIPC5_CH_ID_FMT_0) |
| modemctl->iod = iod; |
| |
| if (iod->format == IPC_BOOT) { |
| modemctl->bootd = iod; |
| mif_err("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); |
| |
| if (sipc5_is_not_reserved_channel(iod->id)) |
| insert_iod_with_channel(msd, iod->id, iod); |
| |
| /* 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\n", iod->name); |
| return iod; |
| } |
| |
| static int attach_devices(struct io_device *iod, enum modem_link tx_link) |
| { |
| 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)) { |
| /* The count 1 bits of iod->link_types is count |
| * of link devices of this iod. |
| * If use one link device, |
| * or, 2+ link devices and this link is tx_link, |
| * set iod's link device with ld |
| */ |
| if ((count_bits(iod->link_types) <= 1) || |
| (tx_link == ld->link_type)) { |
| mif_debug("set %s->%s\n", iod->name, ld->name); |
| set_current_link(iod, ld); |
| } |
| } |
| } |
| |
| /* if use rx dynamic switch, set tx_link at modem_io_t of |
| * board-*-modems.c |
| */ |
| if (!get_current_link(iod)) { |
| mif_err("%s->link == NULL\n", iod->name); |
| BUG(); |
| } |
| |
| wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); |
| |
| switch (iod->id) { |
| 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; |
| |
| case SIPC_CH_ID_PDP_0 ... SIPC_CH_ID_LOOPBACK2: |
| 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; |
| } |
| |
| #ifdef CONFIG_OF |
| 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_enum(np, "mif,modem_net", pdata->modem_net); |
| mif_dt_read_enum(np, "mif,modem_type", pdata->modem_type); |
| mif_dt_read_bool(np, "mif,use_handover", pdata->use_handover); |
| mif_dt_read_enum(np, "mif,ipc_version", pdata->ipc_version); |
| |
| mif_dt_read_u32(np, "mif,link_types", pdata->link_types); |
| 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,num_iodevs", pdata->num_iodevs); |
| |
| return 0; |
| } |
| |
| #ifndef CONFIG_LINK_DEVICE_SHMEM |
| static int __parse_dt_mandatory_gpio_pdata(struct device_node *np, |
| struct modem_data *pdata) |
| { |
| int ret = 0; |
| |
| /* GPIO_CP_ON */ |
| pdata->gpio_cp_on = of_get_named_gpio(np, "mif,gpio_cp_on", 0); |
| if (!gpio_is_valid(pdata->gpio_cp_on)) { |
| mif_err("cp_on: Invalied gpio pins\n"); |
| return -EINVAL; |
| } |
| |
| mif_err("gpio_cp_on: %d\n", pdata->gpio_cp_on); |
| ret = gpio_request(pdata->gpio_cp_on, "CP_ON"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", "CP_ON", ret); |
| gpio_direction_output(pdata->gpio_cp_on, 0); |
| |
| /* GPIO_CP_RESET */ |
| pdata->gpio_cp_reset = of_get_named_gpio(np, "mif,gpio_cp_reset", 0); |
| if (!gpio_is_valid(pdata->gpio_cp_reset)) { |
| mif_err("cp_rst: Invalied gpio pins\n"); |
| return -EINVAL; |
| } |
| |
| mif_err("gpio_cp_reset: %d\n", pdata->gpio_cp_reset); |
| ret = gpio_request(pdata->gpio_cp_reset, "CP_RST"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", "CP_RST", ret); |
| gpio_direction_output(pdata->gpio_cp_reset, 0); |
| |
| /* GPIO_PDA_ACTIVE */ |
| pdata->gpio_pda_active = of_get_named_gpio(np, |
| "mif,gpio_pda_active", 0); |
| if (!gpio_is_valid(pdata->gpio_pda_active)) { |
| mif_err("pda_active: Invalied gpio pins\n"); |
| return -EINVAL; |
| } |
| |
| mif_err("gpio_pda_active: %d\n", pdata->gpio_pda_active); |
| ret = gpio_request(pdata->gpio_pda_active, "PDA_ACTIVE"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", "PDA_ACTIVE", ret); |
| gpio_direction_output(pdata->gpio_pda_active, 0); |
| |
| /* GPIO_PHONE_ACTIVE */ |
| pdata->gpio_phone_active = of_get_named_gpio(np, |
| "mif,gpio_phone_active", 0); |
| if (!gpio_is_valid(pdata->gpio_phone_active)) { |
| mif_err("phone_active: Invalied gpio pins\n"); |
| return -EINVAL; |
| } |
| |
| mif_err("gpio_phone_active: %d\n", pdata->gpio_phone_active); |
| ret = gpio_request(pdata->gpio_phone_active, "PHONE_ACTIVE"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", "PHONE_ACTIVE", ret); |
| gpio_direction_input(pdata->gpio_phone_active); |
| |
| /* GPIO_IPC_INT2CP */ |
| pdata->gpio_ipc_int2cp = of_get_named_gpio(np, |
| "mif,gpio_ipc_int2cp", 0); |
| if (gpio_is_valid(pdata->gpio_ipc_int2cp)) { |
| mif_err("gpio_ipc_int2cp: %d\n", pdata->gpio_ipc_int2cp); |
| ret = gpio_request(pdata->gpio_ipc_int2cp, "IPC_INT2CP"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", |
| "SEND_SIG", ret); |
| else |
| gpio_direction_output(pdata->gpio_ipc_int2cp, 0); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static void __parse_dt_optional_gpio_pdata(struct device_node *np, |
| struct modem_data *pdata) |
| { |
| #if !defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| int ret; |
| |
| /* GPIO_CP_WAKEUP */ |
| pdata->gpio_cp_wakeup = of_get_named_gpio(np, |
| "mif,gpio_cp_wakeup", 0); |
| if (gpio_is_valid(pdata->gpio_cp_wakeup)) { |
| mif_err("gpio_cp_wakeup: %d\n", pdata->gpio_cp_wakeup); |
| ret = gpio_request(pdata->gpio_cp_wakeup, "CP_WAKEUP"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", |
| "CP_WAKEUP", ret); |
| else |
| gpio_direction_output(pdata->gpio_cp_wakeup, 0); |
| } |
| |
| /* GPIO_AP_STATUS */ |
| pdata->gpio_ap_status = of_get_named_gpio(np, |
| "mif,gpio_ap_status", 0); |
| if (gpio_is_valid(pdata->gpio_ap_status)) { |
| mif_err("gpio_ap_status: %d\n", pdata->gpio_ap_status); |
| ret = gpio_request(pdata->gpio_ap_status, "AP_STATUS"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", |
| "AP_STATUS", ret); |
| else |
| gpio_direction_output(pdata->gpio_ap_status, 0); |
| } |
| |
| /* GPIO_AP_WAKEUP */ |
| pdata->gpio_ap_wakeup = of_get_named_gpio(np, |
| "mif,gpio_ap_wakeup", 0); |
| if (gpio_is_valid(pdata->gpio_ap_wakeup)) { |
| mif_err("gpio_ap_wakeup: %d\n", pdata->gpio_ap_wakeup); |
| ret = gpio_request(pdata->gpio_ap_wakeup, "AP_WAKEUP"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", |
| "AP_WAKEUP", ret); |
| else |
| gpio_direction_input(pdata->gpio_ap_wakeup); |
| } |
| |
| /* GPIO_CP_STATUS */ |
| pdata->gpio_cp_status = of_get_named_gpio(np, |
| "mif,gpio_cp_status", 0); |
| if (gpio_is_valid(pdata->gpio_cp_status)) { |
| mif_err("gpio_cp_status: %d\n", pdata->gpio_cp_status); |
| ret = gpio_request(pdata->gpio_cp_status, "CP_STATUS"); |
| if (ret) |
| mif_err("fail to request gpio %s:%d\n", |
| "CP_STATUS", ret); |
| else |
| gpio_direction_input(pdata->gpio_cp_status); |
| } |
| #endif |
| } |
| |
| static int parse_dt_gpio_pdata(struct device_node *np, struct modem_data *pdata) |
| { |
| int err = 0; |
| #if defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| struct device_node *pm_node; |
| #endif |
| |
| #ifndef CONFIG_LINK_DEVICE_SHMEM |
| err = __parse_dt_mandatory_gpio_pdata(np, pdata); |
| if (err) |
| return err; |
| #endif |
| __parse_dt_optional_gpio_pdata(np, pdata); |
| |
| #if defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| pm_node = of_get_child_by_name(np, PM_DT_NODE_NAME); |
| if (!pm_node) { |
| mif_err("ERR! No PM DT node\n"); |
| return -ENOSYS; |
| } |
| |
| err = link_pm_parse_dt_gpio_pdata(pm_node, pdata); |
| if (err) |
| return err; |
| #endif |
| return err; |
| } |
| |
| 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_types != LINKTYPE(LINKDEV_SHMEM)) |
| 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); |
| 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_perf_req_cpu", |
| mbox->irq_cp2ap_perf_req_cpu); |
| mif_dt_read_u32(np, "mif,irq_cp2ap_perf_req_mif", |
| mbox->irq_cp2ap_perf_req_mif); |
| mif_dt_read_u32 (np, "mif,irq_cp2ap_perf_req_int", |
| mbox->irq_cp2ap_perf_req_int); |
| mif_dt_read_u32(np, "mif,irq_cp2ap_active", mbox->irq_cp2ap_active); |
| mif_dt_read_u32(np, "mif,irq_cp2ap_wakelock", mbox->irq_cp2ap_wakelock); |
| |
| mif_dt_read_u32(np, "mbx_ap2cp_msg", mbox->mbx_ap2cp_msg); |
| mif_dt_read_u32(np, "mbx_cp2ap_msg", mbox->mbx_cp2ap_msg); |
| mif_dt_read_u32(np, "mbx_cp2ap_united_status", mbox->mbx_cp2ap_status); |
| mif_dt_read_u32(np, "mbx_ap2cp_united_status", mbox->mbx_ap2cp_status); |
| mif_dt_read_u32(np, "mbx_cp2ap_dvfsreq_cpu", mbox->mbx_cp2ap_perf_req_cpu); |
| mif_dt_read_u32(np, "mbx_cp2ap_dvfsreq_mif", mbox->mbx_cp2ap_perf_req_mif); |
| mif_dt_read_u32(np, "mbx_cp2ap_dvfsreq_int", mbox->mbx_cp2ap_perf_req_int); |
| mif_dt_read_u32(np, "mbx_ap2cp_kerneltime", mbox->mbx_ap2cp_kerneltime); |
| |
| /* Status Bit Info */ |
| mif_dt_read_u32(np, "sbi_lte_active_mask", mbox->sbi_lte_active_mask); |
| mif_dt_read_u32(np, "sbi_lte_active_pos", mbox->sbi_lte_active_pos); |
| mif_dt_read_u32(np, "sbi_cp_status_mask", mbox->sbi_cp_status_mask); |
| mif_dt_read_u32(np, "sbi_cp_status_pos", mbox->sbi_cp_status_pos); |
| mif_dt_read_u32(np, "sbi_cp_rat_mode_mask", mbox->sbi_cp2ap_rat_mode_mask); |
| mif_dt_read_u32(np, "sbi_cp_rat_mode_pos", mbox->sbi_cp2ap_rat_mode_pos); |
| mif_dt_read_u32(np, "sbi_cp2ap_wakelock_mask", mbox->sbi_cp2ap_wakelock_mask); |
| mif_dt_read_u32(np, "sbi_cp2ap_wakelock_pos", mbox->sbi_cp2ap_wakelock_pos); |
| mif_dt_read_u32(np, "sbi_pda_active_mask", mbox->sbi_pda_active_mask); |
| mif_dt_read_u32(np, "sbi_pda_active_pos", mbox->sbi_pda_active_pos); |
| mif_dt_read_u32(np, "sbi_ap_status_mask", mbox->sbi_ap_status_mask); |
| mif_dt_read_u32(np, "sbi_ap_status_pos", mbox->sbi_ap_status_pos); |
| mif_dt_read_u32(np, "sbi_crash_type_mask", mbox->sbi_crash_type_mask); |
| mif_dt_read_u32(np, "sbi_crash_type_pos", mbox->sbi_crash_type_pos); |
| |
| mif_dt_read_u32(np, "sbi_ap2cp_kerneltime_sec_mask", |
| mbox->sbi_ap2cp_kerneltime_sec_mask); |
| mif_dt_read_u32(np, "sbi_ap2cp_kerneltime_sec_pos", |
| mbox->sbi_ap2cp_kerneltime_sec_pos); |
| mif_dt_read_u32(np, "sbi_ap2cp_kerneltime_usec_mask", |
| mbox->sbi_ap2cp_kerneltime_usec_mask); |
| mif_dt_read_u32(np, "sbi_ap2cp_kerneltime_usec_pos", |
| mbox->sbi_ap2cp_kerneltime_usec_pos); |
| mif_dt_read_u32(np, "sbi_uart_noti_mask", mbox->sbi_uart_noti_mask); |
| mif_dt_read_u32(np, "sbi_uart_noti_pos", mbox->sbi_uart_noti_pos); |
| |
| return ret; |
| } |
| |
| static int parse_dt_iodevs_pdata(struct device *dev, struct device_node *np, |
| struct modem_data *pdata) |
| { |
| struct device_node *child = NULL; |
| size_t size = sizeof(struct modem_io_t) * pdata->num_iodevs; |
| int i = 0; |
| |
| pdata->iodevs = devm_kzalloc(dev, size, GFP_KERNEL); |
| if (!pdata->iodevs) { |
| mif_err("iodevs: failed to alloc memory\n"); |
| return -ENOMEM; |
| } |
| |
| for_each_child_of_node(np, child) { |
| struct modem_io_t *iod; |
| |
| iod = &pdata->iodevs[i]; |
| |
| mif_dt_read_string(child, "iod,name", iod->name); |
| mif_dt_read_u32(child, "iod,id", iod->id); |
| 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,links", iod->links); |
| if (count_bits(iod->links) > 1) |
| mif_dt_read_enum(child, "iod,tx_link", iod->tx_link); |
| mif_dt_read_u32(child, "iod,attrs", iod->attrs); |
| /* mif_dt_read_string(child, "iod,app", iod->app); */ |
| mif_dt_read_u32_noerr(child, "iod,max_tx_size", |
| iod->ul_buffer_size); |
| |
| if (iod->attrs & IODEV_ATTR(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 & IODEV_ATTR(ATTR_OPTION_REGION)) |
| mif_dt_read_string(child, "iod,option_region", |
| iod->option_region); |
| |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static struct modem_data *modem_if_parse_dt_pdata(struct device *dev) |
| { |
| struct modem_data *pdata; |
| struct device_node *iodevs = 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_gpio_pdata(dev->of_node, pdata)) { |
| mif_err("DT error: failed to parse gpio\n"); |
| goto error; |
| } |
| |
| if (parse_dt_mbox_pdata(dev, dev->of_node, pdata)) { |
| mif_err("DT error: failed to parse mbox\n"); |
| goto error; |
| } |
| |
| iodevs = of_get_child_by_name(dev->of_node, "iodevs"); |
| if (!iodevs) { |
| mif_err("DT error: failed to get child node\n"); |
| goto error; |
| } |
| |
| if (parse_dt_iodevs_pdata(dev, iodevs, 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) { |
| if (pdata->iodevs) |
| devm_kfree(dev, pdata->iodevs); |
| devm_kfree(dev, pdata); |
| } |
| return ERR_PTR(-EINVAL); |
| } |
| |
| static const struct of_device_id sec_modem_match[] = { |
| { .compatible = "sec_modem,modem_pdata", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, sec_modem_match); |
| #else |
| static struct modem_data *modem_if_parse_dt_pdata(struct device *dev) |
| { |
| return ERR_PTR(-ENODEV); |
| } |
| #endif |
| |
| 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, |
| }; |
| |
| #ifdef CONFIG_GPIO_DS_DETECT |
| static enum mif_sim_mode get_sim_mode(struct device_node *of_node) |
| { |
| enum mif_sim_mode mode = MIF_SIM_DUAL; |
| 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 reqeust 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_SINGLE; |
| |
| mif_info("sim_mode: %d\n", mode); |
| gpio_free(gpio_ds_det); |
| |
| make_proc: |
| if (!proc_create_data("simslot_count", 0, NULL, &simslot_count_fops, |
| (void *)(long)mode)) { |
| mif_err("Failed to create proc\n"); |
| mode = MIF_SIM_DUAL; |
| } |
| |
| return mode; |
| } |
| #else |
| static enum mif_sim_mode get_sim_mode(struct device_node *of_node) |
| { |
| return MIF_SIM_DUAL; |
| } |
| #endif |
| |
| static ssize_t do_cp_crash_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
| { |
| unsigned long flags; |
| struct modem_ctl *mc = dev_get_drvdata(dev); |
| spin_lock_irqsave(&mc->lock, flags); |
| if (mc->bootd && atomic_read(&mc->bootd->opened) > 0) |
| mc->bootd->modem_state_changed(mc->bootd, STATE_CRASH_EXIT); |
| spin_unlock_irqrestore(&mc->lock, flags); |
| 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 sprintf(buf, "%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 modem_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; |
| |
| mif_err("%s: +++ (%s)\n", |
| pdev->name, CONFIG_OPTION_REGION); |
| |
| if (dev->of_node) { |
| pdata = modem_if_parse_dt_pdata(dev); |
| if (IS_ERR(pdata)) { |
| mif_err("MIF DT parse error!\n"); |
| return PTR_ERR(pdata); |
| } |
| } else { |
| if (!pdata) { |
| mif_err("Non-DT, incorrect pdata!\n"); |
| return -EINVAL; |
| } |
| } |
| |
| msd = create_modem_shared_data(pdev); |
| if (!msd) { |
| mif_err("%s: msd == NULL\n", pdata->name); |
| return -ENOMEM; |
| } |
| |
| modemctl = create_modemctl_device(pdev, msd); |
| if (!modemctl) { |
| mif_err("%s: modemctl == NULL\n", pdata->name); |
| devm_kfree(dev, msd); |
| return -ENOMEM; |
| } |
| |
| /* create link device */ |
| /* support multi-link device */ |
| for (i = 0; i < LINKDEV_MAX; i++) { |
| /* find matching link type */ |
| if (pdata->link_types & LINKTYPE(i)) { |
| ld = call_link_init_func(pdev, i); |
| if (!ld) |
| goto free_mc; |
| |
| mif_err("%s: %s link created\n", pdata->name, ld->name); |
| |
| ld->link_type = i; |
| ld->mc = modemctl; |
| ld->msd = msd; |
| |
| list_add(&ld->list, &msd->link_dev_list); |
| } |
| } |
| |
| sim_mode = get_sim_mode(dev->of_node); |
| |
| /* create io deivces and connect to modemctl device */ |
| size = sizeof(struct io_device *) * pdata->num_iodevs; |
| iod = kzalloc(size, GFP_KERNEL); |
| for (i = 0; i < pdata->num_iodevs; i++) { |
| if (sim_mode < MIF_SIM_DUAL && |
| pdata->iodevs[i].attrs & IODEV_ATTR(ATTR_DUALSIM)) |
| continue; |
| |
| if (pdata->iodevs[i].attrs & IODEV_ATTR(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]->id == SIPC_CH_ID_CASS) |
| list_add_tail(&iod[i]->list, |
| &modemctl->modem_state_notify_list); |
| |
| attach_devices(iod[i], pdata->iodevs[i].tx_link); |
| } |
| |
| platform_set_drvdata(pdev, modemctl); |
| |
| /* reserved buff pool memory init */ |
| of_reserved_mem_device_init(dev); |
| |
| #ifdef CONFIG_CP_RAM_LOGGING |
| /* Send SMC call if cp_ram_logging function is enabled */ |
| if (shm_get_cplog_flag()) |
| security_request_cp_ram_logging(); |
| #endif |
| kfree(iod); |
| |
| if (sysfs_create_groups(&dev->kobj, modem_groups)) |
| mif_err("failed to create modem groups node\n"); |
| |
| err = mif_init_argos_notifier(); |
| if (err < 0) |
| mif_err("failed to initialize argos_notifier(%d)\n", err); |
| |
| if (uart_switch_init(pdev)) |
| mif_err("uart_switch_init fail\n"); |
| |
| mif_err("%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_mc: |
| if (modemctl) |
| devm_kfree(dev, modemctl); |
| |
| if (msd) |
| devm_kfree(dev, msd); |
| |
| mif_err("%s: xxx\n", pdev->name); |
| return -ENOMEM; |
| } |
| |
| static void modem_shutdown(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct modem_ctl *mc = dev_get_drvdata(dev); |
| |
| mc->ops.modem_shutdown(mc); |
| mc->phone_state = STATE_OFFLINE; |
| |
| clean_vss_magic_code(); |
| |
| mif_err("%s\n", mc->name); |
| } |
| |
| static int modem_suspend(struct device *pdev) |
| { |
| struct modem_ctl *mc = dev_get_drvdata(pdev); |
| #ifdef CONFIG_LINK_DEVICE_SHMEM |
| struct modem_mbox *mbox = mc->mdm_data->mbx; |
| struct utc_time t; |
| #endif |
| |
| #if !defined(CONFIG_LINK_DEVICE_HSIC) |
| if (mc->gpio_pda_active) |
| gpio_set_value(mc->gpio_pda_active, 0); |
| #endif |
| |
| #if defined(CONFIG_LINK_DEVICE_LLI) &&\ |
| !defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| mipi_lli_suspend(); |
| #endif |
| |
| #ifdef CONFIG_LINK_DEVICE_SHMEM |
| get_utc_time(&t); |
| mif_err("time = %d.%d\n", t.sec + (t.min * 60), t.us); |
| mbox_update_value(MCU_CP, mbox->mbx_ap2cp_kerneltime, |
| t.sec + (t.min * 60), |
| mbox->sbi_ap2cp_kerneltime_sec_mask, |
| mbox->sbi_ap2cp_kerneltime_sec_pos); |
| mbox_update_value(MCU_CP, mbox->mbx_ap2cp_kerneltime, t.us, |
| mbox->sbi_ap2cp_kerneltime_usec_mask, |
| mbox->sbi_ap2cp_kerneltime_usec_pos); |
| |
| mif_err("%s: pda_active:0\n", mc->name); |
| mbox_update_value(MCU_CP, mc->mbx_ap_status, 0, |
| mc->sbi_pda_active_mask, mc->sbi_pda_active_pos); |
| mbox_set_interrupt(MCU_CP, mc->int_pda_active); |
| #endif |
| |
| mif_err("%s\n", mc->name); |
| set_wakeup_packet_log(true); |
| |
| return 0; |
| } |
| |
| static int modem_resume(struct device *pdev) |
| { |
| struct modem_ctl *mc = dev_get_drvdata(pdev); |
| #ifdef CONFIG_LINK_DEVICE_SHMEM |
| struct modem_mbox *mbox = mc->mdm_data->mbx; |
| struct utc_time t; |
| #endif |
| |
| set_wakeup_packet_log(false); |
| |
| #if !defined(CONFIG_LINK_DEVICE_HSIC) |
| if (mc->gpio_pda_active) |
| gpio_set_value(mc->gpio_pda_active, 1); |
| #endif |
| |
| #if defined(CONFIG_LINK_DEVICE_LLI) &&\ |
| !defined(CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM) |
| mipi_lli_resume(); |
| |
| /* CP initiated resume case */ |
| if (gpio_get_value(mc->gpio_ap_wakeup)) { |
| mif_err("gpio_ap_wakeup high\n"); |
| mipi_lli_set_link_status(LLI_WAITFORMOUNT); |
| gpio_set_value(mc->gpio_ap_status, 1); |
| } |
| #endif |
| |
| #ifdef CONFIG_LINK_DEVICE_SHMEM |
| get_utc_time(&t); |
| mif_err("time = %d.%d\n", t.sec + (t.min * 60), t.us); |
| mbox_update_value(MCU_CP, mbox->mbx_ap2cp_kerneltime, |
| t.sec + (t.min * 60), |
| mbox->sbi_ap2cp_kerneltime_sec_mask, |
| mbox->sbi_ap2cp_kerneltime_sec_pos); |
| mbox_update_value(MCU_CP, mbox->mbx_ap2cp_kerneltime, t.us, |
| mbox->sbi_ap2cp_kerneltime_usec_mask, |
| mbox->sbi_ap2cp_kerneltime_usec_pos); |
| |
| mif_err("%s: pda_active:1\n", mc->name); |
| mbox_update_value(MCU_CP, mc->mbx_ap_status, 1, |
| mc->sbi_pda_active_mask, mc->sbi_pda_active_pos); |
| mbox_set_interrupt(MCU_CP, mc->int_pda_active); |
| #endif |
| |
| mif_err("%s\n", mc->name); |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops modem_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(modem_suspend, modem_resume) |
| }; |
| |
| static struct platform_driver modem_driver = { |
| .probe = modem_probe, |
| .shutdown = modem_shutdown, |
| .driver = { |
| .name = "mif_sipc5", |
| .owner = THIS_MODULE, |
| .pm = &modem_pm_ops, |
| .suppress_bind_attrs = true, |
| #ifdef CONFIG_OF |
| .of_match_table = of_match_ptr(sec_modem_match), |
| #endif |
| }, |
| }; |
| |
| module_platform_driver(modem_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Samsung Modem Interface Driver"); |