/* * Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "modem_prj.h" // todo, #include "modem_utils.h" #include "ccci_core.h" #include "ccci_bm.h" #include "port_ipc.h" #include "port/port_char.h" #include "port/port_proxy.h" #include "inc/ccci_fsm.h" extern const struct file_operations *get_port_char_ops(void); static struct file_operations *ccci_port_ops; void print_ipc_pkt(u16 ch, struct sk_buff *skb) { if (ch == CCCI_RIL_IPC0_RX || ch == CCCI_RIL_IPC1_RX) print_hex_dump(KERN_INFO, "mif: RX: ", DUMP_PREFIX_NONE, 32, 1, skb->data, 32, 0); else if (ch == CCCI_RIL_IPC0_TX || ch == CCCI_RIL_IPC1_TX) print_hex_dump(KERN_INFO, "mif: TX: ", DUMP_PREFIX_NONE, 32, 1, skb->data, 32, 0); } static int misc_open(struct inode *inode, struct file *file) { struct io_device *iod = to_io_device(file->private_data); int minor = iod->minor; struct port_t *port; pr_err("mif: open: %s, minor=%d\n", file->f_path.dentry->d_iname, minor); port = port_get_by_minor(MD_SYS1, minor); if (!port) { pr_err("mif: open: port is null\n"); return -EBUSY; } /*if (atomic_read(&port->usage_cnt)) { pr_err("mif: open: count=%d\n", atomic_read(&port->usage_cnt)); return -EBUSY; }*/ pr_err("mif: open: %s\n", port->name); atomic_inc(&port->usage_cnt); file->private_data = port; nonseekable_open(inode, file); port_user_register(port); return 0; } static int misc_release(struct inode *inode, struct file *filp) { if (!ccci_port_ops) return 0; pr_err("mif: release: %s(%s)\n", filp->f_path.dentry->d_iname, current->comm); return ccci_port_ops->release(inode, filp); } static ssize_t misc_write(struct file *filp, const char __user *data, size_t count, loff_t *fpos) { if (!ccci_port_ops) return 0; return ccci_port_ops->write(filp, data, count, fpos); } static ssize_t misc_read(struct file *filp, char *buf, size_t count, loff_t *fpos) { if (!ccci_port_ops) return 0; return ccci_port_ops->read(filp, buf, count, fpos); } static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) { struct port_t *port = filp->private_data; int md_id = port->md_id; int md_state = ccci_fsm_get_md_state(md_id); unsigned long mask = 0; if (!ccci_port_ops) return 0; mask = ccci_port_ops->poll(filp, wait); if (port->rx_ch != CCCI_RIL_IPC0_RX && port->rx_ch != CCCI_RIL_IPC1_RX) return mask; /* only for S-RIL IPC */ switch (md_state) { case GATED: case BOOT_WAITING_FOR_HS2: case RESET: case EXCEPTION: if (port->md_state_changed) { port->md_state_changed = 0; mask |= POLLHUP; } mif_err("%s: state == %d\n", "ipc", md_state); break; default: break; } return mask; } static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct port_t *port = filp->private_data; int md_id = port->md_id; int md_state = ccci_fsm_get_md_state(md_id); if (!ccci_port_ops) return 0; switch (cmd) { case IOCTL_MODEM_STATUS: /* * MD_BOOT_STAGE_0 = STATE_OFFLINE, * MD_BOOT_STAGE_1 = STATE_BOOTING, * MD_BOOT_STAGE_2 = STATE_ONLINE, * */ switch (md_state) { case GATED: case RESET: return STATE_OFFLINE; case BOOT_WAITING_FOR_HS2: return STATE_BOOTING; case READY: return STATE_ONLINE; case EXCEPTION: return STATE_CRASH_EXIT; default: break; } return ccci_port_ops->unlocked_ioctl(filp, CCCI_IOC_GET_MD_STATE, arg); case IOCTL_MODEM_FORCE_CRASH_EXIT: pr_err("mif: %s: cmd=0x%x\n", __func__, cmd); return ccci_port_ops->unlocked_ioctl(filp, CCCI_IOC_FORCE_MD_ASSERT, arg); case IOCTL_MODEM_RESET: if (md_state == EXCEPTION) { pr_err("mif: %s skip IOCTL_MODEM_RESET md_state:EXCEPTION\n", __func__); return 0; } pr_err("mif: %s: cmd=0x%x\n", __func__, cmd); return ccci_port_ops->unlocked_ioctl(filp, CCCI_IOC_MD_RESET, arg); case CCCI_IOC_ENTER_DEEP_FLIGHT_ENHANCED: case CCCI_IOC_LEAVE_DEEP_FLIGHT_ENHANCED: pr_err("mif: %s: cmd=0x%x\n", __func__, cmd); return ccci_port_ops->unlocked_ioctl(filp, cmd, arg); #if 0 // todo default: /* If you need to handle the ioctl for specific link device, * then assign the link ioctl handler to ld->ioctl * It will be call for specific link ioctl */ if (ld->ioctl) return ld->ioctl(ld, iod, cmd, arg); mif_info("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd); return -EINVAL; #endif } return 0; } static const struct file_operations misc_io_fops = { .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .poll = misc_poll, .unlocked_ioctl = misc_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = misc_ioctl, #endif .write = misc_write, .read = misc_read, }; int sipc5_init_io_device(struct io_device *iod) { int ret = 0; switch (iod->io_typ) { case IODEV_MISC: iod->miscdev.minor = MISC_DYNAMIC_MINOR; iod->miscdev.name = iod->name; iod->miscdev.fops = &misc_io_fops; ret = misc_register(&iod->miscdev); if (ret) mif_info("%s: ERR! misc_register failed\n", iod->name); break; case IODEV_DUMMY: iod->miscdev.minor = MISC_DYNAMIC_MINOR; iod->miscdev.name = iod->name; iod->miscdev.fops = &misc_io_fops; ret = misc_register(&iod->miscdev); if (ret) mif_info("%s: ERR! misc_register fail\n", iod->name); break; default: mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ); return -EINVAL; } return ret; } #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_u32(np, "mif,num_iodevs", pdata->num_iodevs); return 0; } 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 io_device) * 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 io_device *iod = &pdata->iodevs[i]; mif_dt_read_string(child, "iod,name", iod->name); mif_dt_read_u32(child, "iod,minor", iod->minor); mif_dt_read_enum(child, "iod,format", iod->format); mif_dt_read_enum(child, "iod,io_type", iod->io_typ); mif_dt_read_u32(child, "iod,attrs", iod->attrs); /* mif_dt_read_string(child, "iod,app", iod->app); */ i++; } return 0; } #endif 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; } 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); } 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, }; static void 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)) { pr_err("DT error: failed to get sim mode\n"); goto make_proc; } retval = gpio_get_value(gpio_ds_det); if (retval) mode = MIF_SIM_SINGLE; pr_err("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)) { pr_err("Failed to create proc\n"); mode = MIF_SIM_DUAL; } } static int modem_probe(struct platform_device *pdev) { int i; struct device *dev = &pdev->dev; struct modem_data *pdata = dev->platform_data; mif_info("%s: +++\n", pdev->name); if (!dev->of_node) return -EINVAL; pdata = modem_if_parse_dt_pdata(dev); get_sim_mode(dev->of_node); for (i = 0; i < pdata->num_iodevs; i++) { struct io_device *iod = &pdata->iodevs[i]; INIT_LIST_HEAD(&iod->list); atomic_set(&iod->opened, 0); if (sipc5_init_io_device(iod)) { mif_err("%s: iod[%d] == NULL\n", pdata->name, i); goto free_iod; } } // todo: platform_set_drvdata(pdev, modemctl); ccci_port_ops = (struct file_operations *)get_port_char_ops(); mif_info("%s: ---\n", pdev->name); return 0; free_iod: kfree(pdata->iodevs); mif_err("%s: xxx\n", pdev->name); return -ENOMEM; } static const struct of_device_id sec_modem_match[] = { { .compatible = "sec_modem,modem_pdata", }, {}, }; MODULE_DEVICE_TABLE(of, sec_modem_match); static struct platform_driver modem_driver = { .probe = modem_probe, .driver = { .name = "mif_sipc5", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = of_match_ptr(sec_modem_match), #endif }, }; void register_port_ops(const struct file_operations *ops) { if (!ccci_port_ops) ccci_port_ops = (struct file_operations *)ops; } module_platform_driver(modem_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Samsung Modem Interface Driver");