/* * Copyright (C) 2017 Spreadtrum Communications Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 #ifdef CONFIG_SC2342_INTEG #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../wcn_gnss.h" #include "gnss_common.h" #include "gnss_dump.h" #include "wcn_glb.h" #include "wcn_glb_reg.h" #define GNSSCOMM_INFO(format, arg...) pr_info("gnss_ctl: " format, ## arg) #define GNSSCOMM_ERR(format, arg...) pr_err("gnss_ctl: " format, ## arg) #define GNSS_DATA_BASE_TYPE_H 16 #define GNSS_MAX_STRING_LEN 10 /* gnss mem dump success return value is 3 */ #define GNSS_DUMP_DATA_SUCCESS 3 #define FIRMWARE_FILEPATHNAME_LENGTH_MAX 256 struct gnss_common_ctl { struct device *dev; unsigned long chip_ver; unsigned int gnss_status; unsigned int gnss_subsys; char firmware_path[FIRMWARE_FILEPATHNAME_LENGTH_MAX]; }; static struct gnss_common_ctl gnss_common_ctl_dev; enum gnss_status_e { GNSS_STATUS_POWEROFF = 0, GNSS_STATUS_POWERON, GNSS_STATUS_ASSERT, GNSS_STATUS_POWEROFF_GOING, GNSS_STATUS_POWERON_GOING, GNSS_STATUS_MAX, }; #ifdef CONFIG_SC2342_INTEG enum gnss_cp_status_subtype { GNSS_CP_STATUS_CALI = 1, GNSS_CP_STATUS_INIT = 2, GNSS_CP_STATUS_INIT_DONE = 3, GNSS_CP_STATUS_IDLEOFF = 4, GNSS_CP_STATUS_IDLEON = 5, GNSS_CP_STATUS_SLEEP = 6, GNSS_CP_STATUS__MAX, }; static struct completion gnss_dump_complete; #endif static const int gnss_version = 0x22; #ifdef CONFIG_WCN_PARSE_DTS static const struct of_device_id gnss_common_ctl_of_match[] = { {.compatible = "sprd,gnss_common_ctl", .data = (void *)&gnss_version}, {}, }; #endif #ifndef CONFIG_SC2342_INTEG struct gnss_cali { bool cali_done; u32 *cali_data; }; static struct gnss_cali gnss_cali_data; static u32 *gnss_efuse_data; #ifdef GNSSDEBUG static void gnss_cali_done_isr(void) { complete(&marlin_dev->gnss_cali_done); GNSSCOMM_INFO("gnss cali done"); } #endif static int gnss_cali_init(void) { gnss_cali_data.cali_done = false; gnss_cali_data.cali_data = kzalloc(GNSS_CALI_DATA_SIZE, GFP_KERNEL); if (gnss_cali_data.cali_data == NULL) { GNSSCOMM_ERR("%s malloc fail.\n", __func__); return -ENOMEM; } #ifdef GNSSDEBUG init_completion(&marlin_dev.gnss_cali_done); sdio_pub_int_RegCb(GNSS_CALI_DONE, (PUB_INT_ISR)gnss_cali_done_isr); #endif gnss_efuse_data = kzalloc(GNSS_EFUSE_DATA_SIZE, GFP_KERNEL); if (gnss_efuse_data == NULL) { GNSSCOMM_ERR("%s malloc efuse data fail.\n", __func__); return -ENOMEM; } return 0; } static void gnss_cali_deinit(void) { gnss_cali_data.cali_done = false; if(gnss_cali_data.cali_data) kfree(gnss_cali_data.cali_data); if(gnss_efuse_data) kfree(gnss_efuse_data); } int gnss_write_cali_data(void) { GNSSCOMM_INFO("gnss write calidata, flag %d\n", gnss_cali_data.cali_done); if (gnss_cali_data.cali_done) { sprdwcn_bus_direct_write(GNSS_CALI_ADDRESS, gnss_cali_data.cali_data, GNSS_CALI_DATA_SIZE); } return 0; } int gnss_write_efuse_data(void) { GNSSCOMM_INFO("%s flag %d\n", __func__, gnss_cali_data.cali_done); if (gnss_cali_data.cali_done) sprdwcn_bus_direct_write(GNSS_EFUSE_ADDRESS, &gnss_efuse_data[0], GNSS_EFUSE_DATA_SIZE); return 0; } int gnss_write_data(void) { int ret = 0; gnss_write_cali_data(); ret = gnss_write_efuse_data(); return ret; } int gnss_backup_cali(void) { int i = 10; int tempvalue = 0; if (!gnss_cali_data.cali_done) { GNSSCOMM_INFO("%s begin\n", __func__); if (gnss_cali_data.cali_data != NULL) { while (i--) { sprdwcn_bus_direct_read(GNSS_CALI_ADDRESS, gnss_cali_data.cali_data, GNSS_CALI_DATA_SIZE); tempvalue = *(gnss_cali_data.cali_data); GNSSCOMM_INFO(" cali %d time, value is 0x%x\n", i, tempvalue); if (tempvalue != GNSS_CALI_DONE_FLAG) { msleep(100); continue; } GNSSCOMM_INFO("-------------->cali success\n"); gnss_cali_data.cali_done = true; break; } } } else GNSSCOMM_INFO(" no need back again\n"); return 0; } int gnss_backup_efuse(void) { int ret = 1; if (gnss_cali_data.cali_done) { /* efuse data is ok when cali done */ sprdwcn_bus_direct_read(GNSS_EFUSE_ADDRESS, &gnss_efuse_data[0], GNSS_EFUSE_DATA_SIZE); ret = 0; GNSSCOMM_ERR("%s 0x%x\n", __func__, gnss_efuse_data[0]); } else GNSSCOMM_INFO("%s no need back again\n", __func__); return ret; } int gnss_backup_data(void) { int ret; gnss_backup_cali(); ret = gnss_backup_efuse(); return ret; } int gnss_boot_wait(void) { int ret = -1; u32 *magic_value; int i = 125; magic_value = kzalloc(GNSS_BOOTSTATUS_SIZE, GFP_KERNEL); if (magic_value == NULL) { GNSSCOMM_ERR("%s, malloc fail\n", __func__); return -1; } while (i--) { sprdwcn_bus_direct_read(GNSS_BOOTSTATUS_ADDRESS, magic_value, GNSS_BOOTSTATUS_SIZE); GNSSCOMM_ERR("boot read %d time, value is 0x%x\n", i, *magic_value); if (*magic_value != GNSS_BOOTSTATUS_MAGIC) { msleep(20); continue; } ret = 0; GNSSCOMM_INFO("boot read success\n"); break; } kfree(magic_value); return ret; } #else int gnss_file_judge(char *buff, int gnss_type) { unsigned int gnssfile_flag; int file_type, ret = 0; if (buff == NULL) { GNSSCOMM_ERR("%s: buff null\n", __func__); return -1; } gnssfile_flag = *(unsigned int *)(buff + GNSS_FLAG_ADDRESS_INFILE); if (gnssfile_flag == GNSS_FLAG_GLO) file_type = WCN_GNSS_TYPE_GL; else if (gnssfile_flag == GNSS_FLAG_BD) file_type = WCN_GNSS_TYPE_BD; else { GNSSCOMM_INFO("%s: no need to change\n", __func__); return 0; } if (file_type != gnss_type) ret = 1; return ret; } #endif static void gnss_power_on(bool enable) { int ret; GNSSCOMM_INFO("%s:enable=%d,current gnss_status=%d\n", __func__, enable, gnss_common_ctl_dev.gnss_status); if (enable && gnss_common_ctl_dev.gnss_status == GNSS_STATUS_POWEROFF) { gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWERON_GOING; /* special for cali fail flow */ #ifdef CONFIG_SC2342_INTEG if (gnss_get_boot_status() == WCN_BOOT_CP2_ERR_BOOT) { GNSSCOMM_ERR("%s: last start failed\n", __func__); gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWERON; return; } #endif ret = start_marlin(gnss_common_ctl_dev.gnss_subsys); if (ret != 0) GNSSCOMM_ERR("%s: start marlin failed ret=%d\n", __func__, ret); else gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWERON; } else if (!enable && gnss_common_ctl_dev.gnss_status == GNSS_STATUS_POWERON) { gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWEROFF_GOING; #ifdef CONFIG_SC2342_INTEG if (gnss_get_boot_status() == WCN_BOOT_CP2_ERR_BOOT) gnss_set_boot_status(WCN_BOOT_CP2_OK); /* init val */ #endif ret = stop_marlin(gnss_common_ctl_dev.gnss_subsys); if (ret != 0) GNSSCOMM_INFO("%s: stop marlin failed ret=%d\n", __func__, ret); else gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWEROFF; } else { GNSSCOMM_INFO("%s: status is not match\n", __func__); } } static ssize_t gnss_power_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_value; if (kstrtoul(buf, GNSS_MAX_STRING_LEN, &set_value)) { GNSSCOMM_ERR("%s, Maybe store string is too long\n", __func__); return -EINVAL; } GNSSCOMM_INFO("%s,%lu\n", __func__, set_value); if (set_value == 1) gnss_power_on(1); else if (set_value == 0) gnss_power_on(0); else { count = -EINVAL; GNSSCOMM_INFO("%s,unknown control\n", __func__); } return count; } static DEVICE_ATTR_WO(gnss_power_enable); static ssize_t gnss_subsys_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_value; if (kstrtoul(buf, GNSS_MAX_STRING_LEN, &set_value)) return -EINVAL; GNSSCOMM_INFO("%s,%lu\n", __func__, set_value); #ifndef CONFIG_SC2342_INTEG gnss_common_ctl_dev.gnss_subsys = MARLIN_GNSS; #else if (set_value == WCN_GNSS) gnss_common_ctl_dev.gnss_subsys = WCN_GNSS; else if (set_value == WCN_GNSS_BD) gnss_common_ctl_dev.gnss_subsys = WCN_GNSS_BD; else count = -EINVAL; #endif return count; } void gnss_file_path_set(char *buf) { strcpy(&gnss_common_ctl_dev.firmware_path[0], buf); } static ssize_t gnss_subsys_show(struct device *dev, struct device_attribute *attr, char *buf) { int i = 0; GNSSCOMM_INFO("%s\n", __func__); if (gnss_common_ctl_dev.gnss_status == GNSS_STATUS_POWERON) { i += scnprintf(buf + i, PAGE_SIZE - i, "%d:%s\n", gnss_common_ctl_dev.gnss_subsys, &gnss_common_ctl_dev.firmware_path[0]); } else { i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", gnss_common_ctl_dev.gnss_subsys); } return i; } static DEVICE_ATTR_RW(gnss_subsys); #ifdef CONFIG_SC2342_INTEG static int gnss_status_get(void) { phys_addr_t phy_addr; u32 magic_value; phy_addr = wcn_get_gnss_base_addr() + GNSS_STATUS_OFFSET; wcn_read_data_from_phy_addr(phy_addr, &magic_value, sizeof(u32)); GNSSCOMM_INFO("[%s] magic_value=%d\n", __func__, magic_value); return magic_value; } void gnss_dump_mem_ctrl_co(void) { char flag = 0; /* 0: default, all, 1: only data, pmu, aon */ unsigned int temp_status = 0; static char dump_flag; GNSSCOMM_INFO("[%s], flag is %d\n", __func__, dump_flag); if (dump_flag == 1) return; dump_flag = 1; if (gnss_get_boot_status() == WCN_BOOT_CP2_ERR_BOOT) gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWERON; temp_status = gnss_common_ctl_dev.gnss_status; if ((temp_status == GNSS_STATUS_POWERON_GOING) || ((temp_status == GNSS_STATUS_POWERON) && (gnss_status_get() != GNSS_CP_STATUS_SLEEP))) { flag = (temp_status == GNSS_STATUS_POWERON) ? 0 : 1; gnss_dump_mem(flag); gnss_common_ctl_dev.gnss_status = GNSS_STATUS_ASSERT; } complete(&gnss_dump_complete); } #else int gnss_dump_mem_ctrl(void) { int ret = -1; static char dump_flag; GNSSCOMM_INFO("[%s], flag is %d\n", __func__, dump_flag); if (dump_flag == 1) return 0; dump_flag = 1; if (gnss_common_ctl_dev.gnss_status == GNSS_STATUS_POWERON) { ret = gnss_dump_mem(0); gnss_common_ctl_dev.gnss_status = GNSS_STATUS_ASSERT; } return ret; } #endif static ssize_t gnss_dump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_value; int ret = -1; int temp = 0; if (kstrtoul(buf, GNSS_MAX_STRING_LEN, &set_value)) { GNSSCOMM_ERR("%s, store string is too long\n", __func__); return -EINVAL; } GNSSCOMM_INFO("%s,%lu\n", __func__, set_value); if (set_value == 1) { #ifdef CONFIG_SC2342_INTEG temp = wait_for_completion_timeout(&gnss_dump_complete, msecs_to_jiffies(6000)); GNSSCOMM_INFO("%s exit %d\n", __func__, jiffies_to_msecs(temp)); if (temp > 0) ret = GNSS_DUMP_DATA_SUCCESS; else gnss_dump_mem_ctrl_co(); #else temp = gnss_dump_mem_ctrl(); GNSSCOMM_INFO("%s exit temp %d\n", __func__, temp); if (temp == 0) ret = GNSS_DUMP_DATA_SUCCESS; #endif } else count = -EINVAL; return ret; } static DEVICE_ATTR_WO(gnss_dump); static ssize_t gnss_status_show(struct device *dev, struct device_attribute *attr, char *buf) { int i = 0; GNSSCOMM_INFO("%s\n", __func__); i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", gnss_common_ctl_dev.gnss_status); return i; } static DEVICE_ATTR_RO(gnss_status); #ifndef CONFIG_SC2342_INTEG static uint gnss_op_reg; static uint gnss_indirect_reg_offset; static ssize_t gnss_regr_show(struct device *dev, struct device_attribute *attr, char *buf) { int i = 0; unsigned int op_reg = gnss_op_reg; unsigned int buffer; GNSSCOMM_INFO("%s, register is 0x%x\n", __func__, gnss_op_reg); if (op_reg == GNSS_INDIRECT_OP_REG) { int set_value; set_value = gnss_indirect_reg_offset + 0x80000000; sprdwcn_bus_direct_write(op_reg, &set_value, 4); } sprdwcn_bus_direct_read(op_reg, &buffer, 4); GNSSCOMM_INFO("%s,temp value is 0x%x\n", __func__, buffer); i += scnprintf(buf + i, PAGE_SIZE - i, "show: 0x%x\n", buffer); return i; } static DEVICE_ATTR_RO(gnss_regr); static ssize_t gnss_regaddr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_addr; if (kstrtoul(buf, GNSS_DATA_BASE_TYPE_H, &set_addr)) { GNSSCOMM_ERR("%s, input error\n", __func__); return -EINVAL; } GNSSCOMM_INFO("%s,0x%lx\n", __func__, set_addr); gnss_op_reg = (uint)set_addr; return count; } static DEVICE_ATTR_WO(gnss_regaddr); static ssize_t gnss_regspaddr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_addr; if (kstrtoul(buf, GNSS_DATA_BASE_TYPE_H, &set_addr)) { GNSSCOMM_ERR("%s, input error\n", __func__); return -EINVAL; } GNSSCOMM_INFO("%s,0x%lx\n", __func__, set_addr); gnss_op_reg = GNSS_INDIRECT_OP_REG; gnss_indirect_reg_offset = (uint)set_addr; return count; } static DEVICE_ATTR_WO(gnss_regspaddr); static ssize_t gnss_regw_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_value; unsigned int op_reg = gnss_op_reg; if (kstrtoul(buf, GNSS_DATA_BASE_TYPE_H, &set_value)) { GNSSCOMM_ERR("%s, input error\n", __func__); return -EINVAL; } if (op_reg == GNSS_INDIRECT_OP_REG) set_value = gnss_indirect_reg_offset + set_value; GNSSCOMM_INFO("%s,0x%lx\n", __func__, set_value); sprdwcn_bus_direct_write(op_reg, &set_value, 4); return count; } static DEVICE_ATTR_WO(gnss_regw); #endif bool gnss_delay_ctl(void) { return (gnss_common_ctl_dev.gnss_status == GNSS_STATUS_POWERON); } static struct attribute *gnss_common_ctl_attrs[] = { &dev_attr_gnss_power_enable.attr, &dev_attr_gnss_dump.attr, &dev_attr_gnss_status.attr, &dev_attr_gnss_subsys.attr, #ifndef CONFIG_SC2342_INTEG &dev_attr_gnss_regr.attr, &dev_attr_gnss_regaddr.attr, &dev_attr_gnss_regspaddr.attr, &dev_attr_gnss_regw.attr, #endif NULL, }; static struct attribute_group gnss_common_ctl_group = { .name = NULL, .attrs = gnss_common_ctl_attrs, }; static struct miscdevice gnss_common_ctl_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "gnss_common_ctl", .fops = NULL, }; #ifdef CONFIG_SC2342_INTEG static struct sprdwcn_gnss_ops gnss_common_ctl_ops = { .file_judge = gnss_file_judge }; #else static struct sprdwcn_gnss_ops gnss_common_ctl_ops = { .backup_data = gnss_backup_data, .write_data = gnss_write_data, .set_file_path = gnss_file_path_set, .wait_gnss_boot = gnss_boot_wait }; #endif static int gnss_common_ctl_probe(struct platform_device *pdev) { int ret; #ifdef CONFIG_WCN_PARSE_DTS const struct of_device_id *of_id; #endif GNSSCOMM_ERR("%s enter", __func__); gnss_common_ctl_dev.dev = &pdev->dev; gnss_common_ctl_dev.gnss_status = GNSS_STATUS_POWEROFF; gnss_common_ctl_dev.gnss_subsys = MARLIN_GNSS; gnss_cali_init(); #ifdef CONFIG_WCN_PARSE_DTS /* considering backward compatibility, it's not use now start */ of_id = of_match_node(gnss_common_ctl_of_match, pdev->dev.of_node); if (!of_id) { dev_err(&pdev->dev, "get gnss_common_ctl of device id failed!\n"); return -ENODEV; } #endif #ifdef CONFIG_WCN_PARSE_DTS gnss_common_ctl_dev.chip_ver = (unsigned long)(of_id->data); #else gnss_common_ctl_dev.chip_ver = gnss_version; #endif /* considering backward compatibility, it's not use now end */ platform_set_drvdata(pdev, &gnss_common_ctl_dev); ret = misc_register(&gnss_common_ctl_miscdev); if (ret) { GNSSCOMM_ERR("%s failed to register gnss_common_ctl.\n", __func__); return ret; } ret = sysfs_create_group(&gnss_common_ctl_miscdev.this_device->kobj, &gnss_common_ctl_group); if (ret) { GNSSCOMM_ERR("%s failed to create device attributes.\n", __func__); goto err_attr_failed; } #ifdef CONFIG_SC2342_INTEG /* register dump callback func for mdbg */ mdbg_dump_gnss_register(gnss_dump_mem_ctrl_co, NULL); init_completion(&gnss_dump_complete); #endif wcn_gnss_ops_register(&gnss_common_ctl_ops); return 0; err_attr_failed: misc_deregister(&gnss_common_ctl_miscdev); return ret; } static int gnss_common_ctl_remove(struct platform_device *pdev) { gnss_cali_deinit(); wcn_gnss_ops_unregister(); sysfs_remove_group(&gnss_common_ctl_miscdev.this_device->kobj, &gnss_common_ctl_group); misc_deregister(&gnss_common_ctl_miscdev); return 0; } static struct platform_driver gnss_common_ctl_drv = { .driver = { .name = "gnss_common_ctl", .owner = THIS_MODULE, #ifdef CONFIG_WCN_PARSE_DTS .of_match_table = of_match_ptr(gnss_common_ctl_of_match), #endif }, .probe = gnss_common_ctl_probe, .remove = gnss_common_ctl_remove }; int __init gnss_common_ctl_init(void) { return platform_driver_register(&gnss_common_ctl_drv); } void __exit gnss_common_ctl_exit(void) { platform_driver_unregister(&gnss_common_ctl_drv); } #if (0) module_init(gnss_common_ctl_init); module_exit(gnss_common_ctl_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Spreadtrum Gnss Driver"); MODULE_AUTHOR("Jun.an"); #endif