/* * Copyright 2013 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../soc/samsung/pwrcal/pwrcal.h" /* firmware file information */ #define fw_checksum 61921 char *firmware_file = "apm_8890_v6.h"; void __iomem *sram_base; void __iomem *exynos_mailbox_reg; struct regmap *cortexm3_pmu_regmap; extern char* protocol_name; int mbox_irq; unsigned int sram_status = 0; unsigned int dram_checksum = 0; static struct workqueue_struct *mailbox_wq; static struct cl_init_data cl_init; static struct class *mailbox_class; static struct debug_data tx, rx; #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG u32 atl_voltage; u32 apo_voltage; u32 g3d_voltage; u32 mif_voltage; extern u32 mif_in_voltage; extern u32 atl_in_voltage; extern u32 apo_in_voltage; extern u32 g3d_in_voltage; #else u32 default_vol[4] = {100000, 100000, 100000, 100000}; #endif static bool apm_power_down = false; static int idle_ip_index; #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT void samsung_mbox_enable_irq(void) { enable_irq(mbox_irq); } void samsung_mbox_disable_irq(void) { disable_irq(mbox_irq); } #endif static inline struct samsung_mlink *to_samsung_mchan(struct mbox_chan *pchan) { if (!pchan) return NULL; return container_of(pchan, struct samsung_mlink, chan); } static inline struct samsung_mbox *to_samsung_mbox(struct mbox_chan *pchan) { if (!pchan) return NULL; return to_samsung_mchan(pchan)->smc; } static void firmware_load(const char *firmware, int size) { dram_checksum = csum_partial(firmware, size, 0); memcpy(sram_base, firmware, size); pr_info("OK \n"); } static int firmware_update(struct device *dev) { const struct firmware *fw_entry = NULL; int err; dev_info(dev, "Loading APM firmware ... "); err = request_firmware(&fw_entry, firmware_file, dev); if (err) { dev_err(dev, "FAIL \n"); return err; } firmware_load(fw_entry->data, fw_entry->size); release_firmware(fw_entry); return 0; } #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT static irqreturn_t samsung_ipc_handler(int irq, void *p) { struct samsung_mlink *plink = p; u32 tmp; u32 i; mbox_link_txdone(&plink->link, MBOX_OK); /* Acknowledge the interrupt by clearing the interrupt register */ tmp = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX_INT); tmp &= ~RX_INT_CLEAR; __raw_writel(tmp, exynos_mailbox_reg + EXYNOS_MAILBOX_RX_INT); /* Debug information */ rx.buf[rx.cnt][G3D_STATUS] = (exynos_cortexm3_pmu_read(EXYNOS_PMU_G3D_STATUS) & G3D_STATUS_MASK); rx.name[rx.cnt] = protocol_name; for (i = 0; i < MAILBOX_REG_CNT; i++) { rx.time[rx.cnt] = ktime_to_ms(ktime_get()); rx.buf[rx.cnt][i] = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(i)); } #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG /* Debug register clear */ __raw_writel(0x0, exynos_mailbox_reg + EXYNOS_MAILBOX_RX(0)); /* Check firmware setting voltage */ rx.atl_value = ((rx.buf[rx.cnt][0] >> ATL_VOL_SHIFT) & VOL_MASK); rx.apo_value = ((rx.buf[rx.cnt][0] >> APO_VOL_SHIFT) & VOL_MASK); rx.g3d_value = ((rx.buf[rx.cnt][0] >> G3D_VOL_SHIFT) & VOL_MASK); rx.mif_value = ((rx.buf[rx.cnt][0] >> MIF_VOL_SHIFT) & VOL_MASK); atl_voltage = ((rx.atl_value * (u32)PMIC_STEP) + MIN_VOL); apo_voltage = ((rx.apo_value * (u32)PMIC_STEP) + MIN_VOL); g3d_voltage = ((rx.g3d_value * (u32)PMIC_STEP) + MIN_VOL); mif_voltage = ((rx.mif_value * (u32)PMIC_STEP) + MIN_VOL); rx.vol[rx.cnt][0] = atl_voltage; rx.vol[rx.cnt][1] = apo_voltage; rx.vol[rx.cnt][2] = g3d_voltage; rx.vol[rx.cnt][3] = mif_voltage; exynos_ss_mailbox(rx.buf[rx.cnt], 1, protocol_name, rx.vol[rx.cnt]); #else exynos_ss_mailbox(rx.buf[rx.cnt], 1, protocol_name, default_vol); #endif rx.cnt++; if (rx.cnt == DEBUG_COUNT) rx.cnt = 0; return IRQ_HANDLED; } #endif #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT static int samsung_mbox_startup(struct mbox_chan *chan) { return 0; } #else static int samsung_mbox_startup(struct mbox_chan *chan) { return 0;} #endif static int samsung_mbox_send_data(struct mbox_chan *chan, void *msg) { struct samsung_mlink *samsung_link = to_samsung_mchan(chan); u32 *msg_data = (u32 *)msg; u32 i, status, limit_cnt = 0, tmp; samsung_link->data = msg_data; /* Check cortex m3 hardware status */ tmp = exynos_cortexm3_pmu_read(EXYNOS_PMU_CORTEXM3_APM_STATUS); status = (((tmp >> STANDBYWFI) & STANDBYWFI_MASK) & (tmp & APM_STATUS_MASK)); if (status) return -1; /* Check rx interrupt status */ do { status = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(3)) & CM3_INTERRPUT_SHIFT; limit_cnt++; if (limit_cnt > CM3_COUNT_MAX) return -1; } while (status); tx.buf[tx.cnt][G3D_STATUS] = (exynos_cortexm3_pmu_read(EXYNOS_PMU_G3D_STATUS) & G3D_STATUS_MASK); tx.name[tx.cnt] = protocol_name; limit_cnt = 0; /* Check Tx interrupt status */ do { status = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX(3)) & CM3_INTERRPUT_SHIFT; limit_cnt++; if (limit_cnt > CM3_COUNT_MAX) return -1; } while (status); /* Save information and data to mailbox SFR */ for (i = 0; i < MAILBOX_REG_CNT; i++) { writel(msg_data[i], exynos_mailbox_reg + EXYNOS_MAILBOX_TX(i)); tx.buf[tx.cnt][i] = readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX(i)); /* Save a time and message information */ tx.time[tx.cnt] = ktime_to_ms(ktime_get()); } #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG tx.vol[rx.cnt][0] = atl_in_voltage; tx.vol[rx.cnt][1] = apo_in_voltage; tx.vol[rx.cnt][2] = g3d_in_voltage; tx.vol[rx.cnt][3] = mif_in_voltage; exynos_ss_mailbox(msg_data, 0, protocol_name, tx.vol[rx.cnt]); #else exynos_ss_mailbox(msg_data, 0, protocol_name, default_vol); #endif tx.cnt++; if (tx.cnt == DEBUG_COUNT) tx.cnt = 0; return 0; } #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT static void samsung_mbox_shutdown(struct mbox_chan *chan) { } #else static void samsung_mbox_shutdown(struct mbox_chan *chan) {} #endif int samsung_mbox_last_tx_done(struct mbox_chan *chan) { unsigned int status, limit_cnt = 0, tmp, i; do { if (limit_cnt) { cpu_relax(); usleep_range(28, 30); } status = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX_INT) & CM3_POLLING_SHIFT; limit_cnt++; if (limit_cnt > 100) return -EIO; } while(!status); /* Acknowledge the interrupt by clearing the interrupt register */ tmp = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX_INT); tmp &= ~RX_INT_CLEAR; __raw_writel(tmp, exynos_mailbox_reg + EXYNOS_MAILBOX_RX_INT); /* Debug information */ rx.buf[rx.cnt][G3D_STATUS] = (exynos_cortexm3_pmu_read(EXYNOS_PMU_G3D_STATUS) & G3D_STATUS_MASK); rx.name[rx.cnt] = protocol_name; for (i = 0; i < MAILBOX_REG_CNT; i++) { rx.time[rx.cnt] = ktime_to_ms(ktime_get()); rx.buf[rx.cnt][i] = __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(i)); } #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG /* Debug register clear */ __raw_writel(0x0, exynos_mailbox_reg + EXYNOS_MAILBOX_RX(0)); /* Check firmware setting voltage */ rx.atl_value = ((rx.buf[rx.cnt][0] >> ATL_VOL_SHIFT) & VOL_MASK); rx.apo_value = ((rx.buf[rx.cnt][0] >> APO_VOL_SHIFT) & VOL_MASK); rx.g3d_value = ((rx.buf[rx.cnt][0] >> G3D_VOL_SHIFT) & VOL_MASK); rx.mif_value = ((rx.buf[rx.cnt][0] >> MIF_VOL_SHIFT) & VOL_MASK); atl_voltage = ((rx.atl_value * (u32)PMIC_STEP) + MIN_VOL); apo_voltage = ((rx.apo_value * (u32)PMIC_STEP) + MIN_VOL); g3d_voltage = ((rx.g3d_value * (u32)PMIC_STEP) + MIN_VOL); mif_voltage = ((rx.mif_value * (u32)PMIC_STEP) + MIN_VOL); rx.vol[rx.cnt][0] = atl_voltage; rx.vol[rx.cnt][1] = apo_voltage; rx.vol[rx.cnt][2] = g3d_voltage; rx.vol[rx.cnt][3] = mif_voltage; exynos_ss_mailbox(rx.buf[rx.cnt], 1, protocol_name, rx.vol[rx.cnt]); #else exynos_ss_mailbox(rx.buf[rx.cnt], 1, protocol_name, default_vol); #endif rx.cnt++; if (rx.cnt == DEBUG_COUNT) rx.cnt = 0; return 0; } static struct mbox_chan_ops samsung_mbox_ops = { .send_data = samsung_mbox_send_data, .startup = samsung_mbox_startup, .shutdown = samsung_mbox_shutdown, .last_tx_done = samsung_mbox_last_tx_done, }; /* * [DEBUG] Check mailbox related register and TX/RX data history */ int data_history(void) { #if 0 int count; #endif pr_info("EXYNOS_MAILBOX_TX_CON : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX_CON)); pr_info("EXYNOS_MAILBOX_TX_ADDR : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX_ADDR)); pr_info("EXYNOS_MAILBOX_TX_DATA : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX_DATA)); pr_info("EXYNOS_MAILBOX_TX_INT : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_TX_INT)); pr_info("EXYNOS_MAILBOX_RX(0) : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(0))); pr_info("EXYNOS_MAILBOX_RX(1) : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(1))); pr_info("EXYNOS_MAILBOX_RX(2) : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(2))); pr_info("EXYNOS_MAILBOX_RX(3) : %8.0x \n", __raw_readl(exynos_mailbox_reg + EXYNOS_MAILBOX_RX(3))); pr_info("EXYNOS_CORTEXM3_APM_CONFIGURATION : %8.0x \n", exynos_cortexm3_pmu_read(EXYNOS_PMU_CORTEXM3_APM_CONFIGURATION)); pr_info("EXYNOS_CORTEXM3_APM_STATUS : %8.0x \n", exynos_cortexm3_pmu_read(EXYNOS_PMU_CORTEXM3_APM_STATUS)); pr_info("EXYNOS_CORTEXM3_APM_OPTION : %8.0x \n", exynos_cortexm3_pmu_read(EXYNOS_PMU_CORTEXM3_APM_OPTION)); #if 0 for (count = 0; count < DEBUG_COUNT; count++) { pr_info("[%lld ms]TX data : %8.x %8.x %8.x %8.x %8.x\n", tx.time[count], tx.buf[count][0], tx.buf[count][1], tx.buf[count][2], tx.buf[count][3], tx.buf[count][4]); } for (count = 0; count < DEBUG_COUNT; count++) { pr_info("[%lld ms]RX data : %8.x %8.x %8.x %8.x %8.x\n", rx.time[count], rx.buf[count][0], rx.buf[count][1], rx.buf[count][2], rx.buf[count][3], rx.buf[count][4]); } #endif return 0; } static void thread_mailbox_work(struct work_struct *work) { const struct firmware *fw_entry = NULL; unsigned int tmp, status, limit_cnt = 0; int ret, err; unsigned int sram_checksum = 0; if (!apm_power_down) { err = request_firmware(&fw_entry, firmware_file, NULL); if (err) { pr_err("mailbox : request firmware fail \n"); } sram_checksum = csum_partial(sram_base, fw_entry->size, 0); ret = memcmp(sram_base, fw_entry->data, fw_entry->size); if (ret) { /* Cortex M3 compare error case */ sram_status = SRAM_UNSTABLE; pr_info("mailbox : APM SRAM compare error\n"); } else { /* Cortex M3 compare sucess case */ sram_status = SRAM_STABLE; pr_info("mailbox : APM SRAM stable \n"); } pr_info("mailbox : fw_checksum [%d], DRAM checksum [%d], sram checksum [%d] \n", fw_checksum, dram_checksum, sram_checksum); release_firmware(fw_entry); /* This condition is lcd on */ /* Local power up to cortex M3 */ if (sram_status == SRAM_STABLE) { exynos_update_ip_idle_status(idle_ip_index, 0); exynos_apm_power_up(); /* Enable CORTEX M3 */ exynos_apm_reset_release(); cl_init.apm_status = APM_ON; /* Call apm notifier */ sec_core_lock(); cl_dvfs_lock(); apm_notifier_call_chain(APM_READY); cl_dvfs_unlock(); sec_core_unlock(); /* Set CL-DVFS voltage margin limit and CL-DVFS period */ ret = exynos_cl_dvfs_setup(cl_init.atlas_margin, cl_init.apollo_margin, cl_init.g3d_margin, cl_init.mif_margin, cl_init.period); if (ret) pr_warn("mailbox : Do not set margin and period information\n"); } } else { /* This condition is lcd off */ if (sram_status == SRAM_STABLE) { #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) exynos_cl_dvfs_mode_disable(); cl_init.apm_status = APM_OFF; #endif sec_core_lock(); cl_dvfs_lock(); apm_notifier_call_chain(APM_SLEEP); cl_dvfs_unlock(); sec_core_unlock(); /* send message go to wfi */ exynos_apm_enter_wfi(); /* Check cortex M3 core enter wfi mode */ do { tmp = exynos_cortexm3_pmu_read(EXYNOS_PMU_CORTEXM3_APM_STATUS); status = (tmp >> STANDBYWFI) & STANDBYWFI_MASK; limit_cnt++; if (limit_cnt > CM3_COUNT_MAX) break; } while (!status); /* local power down(reset) to CORTEX M3 */ exynos_apm_power_down(); exynos_update_ip_idle_status(idle_ip_index, 1); } } } static DECLARE_WORK(mailbox_work, thread_mailbox_work); static int exynos_mailbox_fb_notifier(struct notifier_block *nb, unsigned long val, void *data) { struct fb_event *evdata = data; struct fb_info *info = evdata->info; unsigned int blank; if (val != FB_EVENT_BLANK && val != FB_R_EARLY_EVENT_BLANK) return 0; /* * If FBNODE is not zero, it is not primary display(LCD) * and don't need to process these scheduling. */ if (info->node) return NOTIFY_OK; blank = *(int *)evdata->data; switch (blank) { case FB_BLANK_POWERDOWN: if (mailbox_wq) { if (work_pending(&mailbox_work)) flush_work(&mailbox_work); apm_power_down = true; queue_work(mailbox_wq, &mailbox_work); } break; case FB_BLANK_UNBLANK: if (mailbox_wq) { if (work_pending(&mailbox_work)) flush_work(&mailbox_work); apm_power_down = false; queue_work(mailbox_wq, &mailbox_work); } break; default: break; } return NOTIFY_OK; } static struct notifier_block exynos_mailbox_fb_notifier_block = { .notifier_call = exynos_mailbox_fb_notifier, }; static int exynos_mailbox_reboot_notifier_call(struct notifier_block *this, unsigned long code, void *_cmd) { /* This condition is lcd off */ #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) exynos_cl_dvfs_mode_disable(); #endif sec_core_lock(); cl_dvfs_lock(); apm_notifier_call_chain(APM_SLEEP); cl_dvfs_unlock(); sec_core_unlock(); pr_info("APM device go to wfi, change to hsi2c \n"); /* Reset CORTEX M3 */ exynos_apm_power_down(); return NOTIFY_DONE; } static struct notifier_block exynos_mailbox_reboot_notifier = { .notifier_call = exynos_mailbox_reboot_notifier_call, .priority = (INT_MIN + 1), }; unsigned int exynos_cortexm3_pmu_read(unsigned int offset) { unsigned int val; int ret; ret = regmap_read(cortexm3_pmu_regmap, offset, &val); if (ret) pr_err("%s, cannot read cortexm3 pmu register!\n", __func__); return val; } void exynos_cortexm3_pmu_write(unsigned int val, unsigned int offset) { int ret; ret = regmap_write(cortexm3_pmu_regmap, offset, val); if (ret) pr_err("%s cannot write cortexm3 pmu register!\n", __func__); } u32 exynos_mailbox_reg_read(unsigned int offset) { return __raw_readl(exynos_mailbox_reg + offset); } void exynos_mailbox_reg_write(unsigned int val, unsigned int offset) { __raw_writel(val, exynos_mailbox_reg+ offset); } static int samsung_mbox_probe(struct platform_device *pdev) { struct samsung_mbox *samsung_mbox; struct device_node *node = pdev->dev.of_node; struct samsung_mlink *mbox_link; struct mbox_chan **chan; int loop, count, ret = 0; struct resource *res; struct device *dev = &pdev->dev; if (!node) { dev_err(&pdev->dev, "driver doesnt support" "non-dt devices\n"); return -ENODEV; } /* read sub link count */ count = of_property_count_strings(node, "samsung,mbox-names"); if (count <= 0) { dev_err(&pdev->dev, "no mbox devices found\n"); return -ENODEV; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); exynos_mailbox_reg = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(exynos_mailbox_reg)) return PTR_ERR(exynos_mailbox_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); sram_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(sram_base)) return PTR_ERR(sram_base); cortexm3_pmu_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,syscon-phandle"); samsung_mbox = devm_kzalloc(&pdev->dev, sizeof(struct samsung_mbox), GFP_KERNEL); if (IS_ERR(samsung_mbox)) return PTR_ERR(samsung_mbox); chan = devm_kzalloc(&pdev->dev, (count + 1) * sizeof(*chan), GFP_KERNEL); if (IS_ERR(chan)) return PTR_ERR(chan); /* copy dev information */ samsung_mbox->dev = &pdev->dev; /* Update firmware */ ret = firmware_update(samsung_mbox->dev); if (ret < 0) { dev_err(samsung_mbox->dev, "failed update firmware\n"); return -ENODEV; } for (loop = 0; loop < count; loop++) { mbox_link = &samsung_mbox->samsung_link[loop]; /* save interrupt information */ mbox_irq = irq_of_parse_and_map(node, loop); if (mbox_irq < 0) { dev_err(&pdev->dev, "Failed get irq map \n"); return -ENODEV; } mbox_link->smc = samsung_mbox; chan[loop] = &mbox_link->chan; if (of_property_read_string_index(node, "samsung,mbox-names", loop, &mbox_link->name)) { dev_err(&pdev->dev, "mbox_name [%d] read failed\n", loop); return -ENODEV; } /* Get of cl dvfs related information to DT */ switch (exynos_get_table_ver()) { case 0 : ret = of_property_read_u32_index(node, "asv_v0_atlas_margin", 0, &cl_init.atlas_margin); if (ret) { dev_err(&pdev->dev, "atlas_margin do not set, Set to default 0mV Value\n"); cl_init.atlas_margin = MARGIN_0MV; } ret = of_property_read_u32_index(node, "asv_v0_apollo_margin", 0, &cl_init.apollo_margin); if (ret) { dev_err(&pdev->dev, "apollo_margin do not set, Set to default 0mV Value\n"); cl_init.apollo_margin = MARGIN_0MV; } ret = of_property_read_u32_index(node, "asv_v0_g3d_margin", 0, &cl_init.g3d_margin); if (ret) { dev_err(&pdev->dev, "g3d_margin do not set, Set to default 0mV Value\n"); cl_init.g3d_margin = MARGIN_0MV; } ret = of_property_read_u32_index(node, "asv_v0_mif_margin", 0, &cl_init.mif_margin); if (ret) { dev_err(&pdev->dev, "mif_margin do not set, Set to default 0mV Value\n"); cl_init.mif_margin = MARGIN_0MV; } break; } ret = of_property_read_u32_index(node, "cl_period", 0, &cl_init.period); if (ret) { dev_err(&pdev->dev, "cl period do not set, Set to default 1ms\n"); cl_init.period = PERIOD_1MS; } dev_info(&pdev->dev, "atlas:%d step, apollo:%d step, g3d:%d step, mif:%d step, period:<%d>\n", cl_init.atlas_margin, cl_init.apollo_margin, cl_init.g3d_margin, cl_init.mif_margin, cl_init.period); } chan[loop] = NULL; /* Terminating chan */ #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT /* Request interrupt */ ret = request_irq(mbox_irq, samsung_ipc_handler, IRQF_SHARED, mbox_link->name, mbox_link); if (ret) { dev_err(&pdev->dev, "failed to register mailbox interrupt:%d\n", ret); return ret; } disable_irq(mbox_irq); #endif mutex_init(&samsung_mbox->lock); #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT samsung_mbox->mbox_con.txdone_irq = true; #endif #ifdef CONFIG_EXYNOS_MBOX_POLLING samsung_mbox->mbox_con.txdone_irq = false; samsung_mbox->mbox_con.txdone_poll = true; samsung_mbox->mbox_con.txpoll_period = POLL_PERIOD; #endif samsung_mbox->mbox_con.ops = &samsung_mbox_ops; samsung_mbox->mbox_con.num_chans = count; samsung_mbox->mbox_con.dev = &pdev->dev; samsung_mbox->mbox_con.chans = &mbox_link->chan; ret = mbox_controller_register(&samsung_mbox->mbox_con); if (ret) { dev_err(&pdev->dev, "%s: MBOX channel register failed\n", __func__); return ret; } platform_set_drvdata(pdev, samsung_mbox); register_reboot_notifier(&exynos_mailbox_reboot_notifier); mailbox_wq = create_singlethread_workqueue("thred-mailbox"); if (!mailbox_wq) { return -ENOMEM; } fb_register_client(&exynos_mailbox_fb_notifier_block); idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev)); exynos_update_ip_idle_status(idle_ip_index, 1); /* Write rcc table to apm sram area */ cal_asv_set_rcc_table(); cal_rcc_print_info(); #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) cl_init.cl_status = CL_ON; #endif return ret; } static int samsung_mbox_remove(struct platform_device *pdev) { struct samsung_mbox *samsung_mbox = platform_get_drvdata(pdev); struct samsung_mlink *mbox_link; mbox_link = &samsung_mbox->samsung_link[0]; #ifdef CONFIG_EXYNOS_MBOX_INTERRUPT free_irq(mbox_irq, mbox_link); #endif mbox_controller_unregister(&samsung_mbox->mbox_con); unregister_reboot_notifier(&exynos_mailbox_reboot_notifier); fb_unregister_client(&exynos_mailbox_fb_notifier_block); return 0; } static int samsung_mailbox_pm_resume_early(struct device *dev) { int ret; ret = firmware_update(dev); if (ret < 0) { dev_err(dev, "failed update firmware\n"); return -ENODEV; } /* Write rcc table to apm sram area */ cal_asv_set_rcc_table(); return 0; } static struct dev_pm_ops samsung_mailbox_pm = { .resume_early = samsung_mailbox_pm_resume_early, }; /* Debug FS */ static int mailbox_message_open_show(struct seq_file *buf, void *d) { int count; /* Print tx message history */ for (count = 0; count < DEBUG_COUNT; count++) { seq_printf(buf, "[TX] [%lld ms]data : %8.x %8.x %8.x %8.x %8.x %s\n", tx.time[count], tx.buf[count][0], tx.buf[count][1], tx.buf[count][2], tx.buf[count][3], tx.buf[count][4], tx.name[count]); seq_printf(buf, "[RX] [%lld ms]data : %8.x %8.x %8.x %8.x %8.x %s\n", rx.time[count], rx.buf[count][0], rx.buf[count][1], rx.buf[count][2], rx.buf[count][3], rx.buf[count][4], rx.name[count]); } return 0; } static int mailbox_send_data_open_show(struct seq_file *buf, void *d) { int count; /* Print tx message history */ for (count = 0; count < DEBUG_COUNT; count++) { seq_printf(buf, "[%lld ms]data : %8.x %8.x %8.x %8.x %8.x %s\n", tx.time[count], tx.buf[count][0], tx.buf[count][1], tx.buf[count][2], tx.buf[count][3], tx.buf[count][4], tx.name[count]); } return 0; } static int mailbox_receive_data_open_show(struct seq_file *buf, void *d) { int count; /* Print tx message history */ for (count = 0; count < DEBUG_COUNT; count++) { seq_printf(buf, "[%lld ms]data : %8.x %8.x %8.x %8.x %8.x %s\n", rx.time[count], rx.buf[count][0], rx.buf[count][1], rx.buf[count][2], rx.buf[count][3], rx.buf[count][4], rx.name[count]); } return 0; } static int cm3_margin_open_show(struct seq_file *buf, void *d) { #ifdef CONFIG_EXYNOS_CL_DVFS_CPU seq_printf(buf, "ATLAS Limit margin : %d uV \n", cl_init.atlas_margin * PMIC_STEP); seq_printf(buf, "APOLLO Limit margin : %d uV \n", cl_init.apollo_margin * PMIC_STEP); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_G3D seq_printf(buf, "G3D Limit margin : %d uV \n", cl_init.g3d_margin * PMIC_STEP); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_MIF seq_printf(buf, "MIF Limit margin : %d uV \n", cl_init.mif_margin * PMIC_STEP); #endif return 0; } #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG static int cl_voltage_open_show(struct seq_file *buf, void *d) { seq_printf(buf, "=========== [input]==[cl_volt]==============\n"); seq_printf(buf, "atl_voltage : %d %d\n", atl_in_voltage, atl_voltage); seq_printf(buf, "apo_voltage : %d %d\n", apo_in_voltage, apo_voltage); seq_printf(buf, "g3d_voltage : %d %d\n", g3d_in_voltage, g3d_voltage); seq_printf(buf, "mif_voltage : %d %d\n", mif_in_voltage, mif_voltage); return 0; } #endif #ifdef CONFIG_EXYNOS_CL_DVFS_CPU static int cpu_margin_get(void *data, u64 *val) { pr_info("ATLAS Limit margin : %d uV \n", cl_init.atlas_margin * PMIC_STEP); pr_info("APOLLO Limit margin : %d uV \n", cl_init.apollo_margin * PMIC_STEP); return 0; } static int cpu_margin_set(void *data, u64 val) { int ret; cl_init.atlas_margin = val; cl_init.apollo_margin = val; ret = exynos_cl_dvfs_setup(cl_init.atlas_margin, cl_init.apollo_margin, cl_init.g3d_margin, cl_init.mif_margin, cl_init.period); if (ret) pr_warn("mailbox : Do not set margin and period information\n"); return 0; } #endif #ifdef CONFIG_EXYNOS_CL_DVFS_G3D static int g3d_margin_get(void *data, u64 *val) { pr_info("G3D Limit margin : %d uV \n", cl_init.g3d_margin * PMIC_STEP); return 0; } static int g3d_margin_set(void *data, u64 val) { int ret; cl_init.g3d_margin = val; ret = exynos_cl_dvfs_setup(cl_init.atlas_margin, cl_init.apollo_margin, cl_init.g3d_margin, cl_init.mif_margin, cl_init.period); if (ret) pr_warn("mailbox : Do not set margin and period information\n"); return 0; } #endif #ifdef CONFIG_EXYNOS_CL_DVFS_MIF static int mif_margin_get(void *data, u64 *val) { pr_info("MIF Limit margin : %d uV \n", cl_init.mif_margin * PMIC_STEP); return 0; } static int mif_margin_set(void *data, u64 val) { int ret; cl_init.mif_margin = val; ret = exynos_cl_dvfs_setup(cl_init.atlas_margin, cl_init.apollo_margin, cl_init.g3d_margin, cl_init.mif_margin, cl_init.period); if (ret) pr_warn("mailbox : Do not set margin and period information\n"); return 0; } #endif #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) static int cl_enable_get(void *data, u64 *val) { if (cl_init.cl_status == CL_OFF && cl_init.apm_status == APM_ON) pr_info("CL STATUS : Disable\n"); else if (cl_init.cl_status == CL_ON && cl_init.apm_status == APM_ON) pr_info("CL STATUS : Enable\n"); else if (cl_init.apm_status == APM_OFF) pr_info("CL STATUS : APM Power Off\n"); return 0; } static int cl_enable_set(void *data, u64 val) { if (val) { if (cl_init.apm_status == APM_ON) { if (cl_init.cl_status == CL_OFF) { exynos_cl_dvfs_mode_enable(); sec_core_lock(); cl_dvfs_lock(); apm_notifier_call_chain(CL_ENABLE); cl_dvfs_unlock(); sec_core_unlock(); cl_init.cl_status = CL_ON; pr_info("CL_ENABLE \n"); } else if (cl_init.cl_status == CL_ON) { pr_info("Already turn on CL-DVFS\n"); } } else if (cl_init.apm_status == APM_OFF) { pr_info("APM OFF Status \n"); } } else { if (cl_init.apm_status == APM_ON) { if (cl_init.cl_status == CL_OFF) { pr_info("Already turn off CL-DVFS\n"); } else if (cl_init.cl_status == CL_ON) { exynos_cl_dvfs_mode_disable(); sec_core_lock(); cl_dvfs_lock(); apm_notifier_call_chain(CL_DISABLE); cl_dvfs_unlock(); sec_core_unlock(); cl_init.cl_status = CL_OFF; pr_info("CL_DISABLE \n"); } } else if (cl_init.apm_status == APM_OFF) { pr_info("APM OFF Status \n"); } } return 0; } #endif static int mailbox_message_data_open(struct inode *inode, struct file *file) { return single_open(file, mailbox_message_open_show, inode->i_private); } static int mailbox_send_data_open(struct inode *inode, struct file *file) { return single_open(file, mailbox_send_data_open_show, inode->i_private); } static int mailbox_receive_data_open(struct inode *inode, struct file *file) { return single_open(file, mailbox_receive_data_open_show, inode->i_private); } static int cm3_margin_open(struct inode *inode, struct file *file) { return single_open(file, cm3_margin_open_show, inode->i_private); } #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG static int cl_voltage_open(struct inode *inode, struct file *file) { return single_open(file, cl_voltage_open_show, inode->i_private); } #endif static const struct file_operations message_status_fops = { .open = mailbox_message_data_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations send_status_fops = { .open = mailbox_send_data_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations receive_status_fops = { .open = mailbox_receive_data_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations mode_status_fops = { .open = cm3_status_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations mode_margin_fops = { .open = cm3_margin_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG static const struct file_operations cl_voltage_fops = { .open = cl_voltage_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif #ifdef CONFIG_EXYNOS_CL_DVFS_CPU DEFINE_SIMPLE_ATTRIBUTE(cpu_margin_fops, cpu_margin_get, cpu_margin_set, "%llx\n"); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_G3D DEFINE_SIMPLE_ATTRIBUTE(g3d_margin_fops, g3d_margin_get, g3d_margin_set, "%llx\n"); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_MIF DEFINE_SIMPLE_ATTRIBUTE(mif_margin_fops, mif_margin_get, mif_margin_set, "%llx\n"); #endif #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) DEFINE_SIMPLE_ATTRIBUTE(cl_enable_fops, cl_enable_get, cl_enable_set, "%llx\n"); #endif void mailbox_debugfs(void) { struct dentry *den; den = debugfs_create_dir("mailbox", NULL); debugfs_create_file("message", 0644, den, NULL, &message_status_fops); debugfs_create_file("send_data", 0644, den, NULL, &send_status_fops); debugfs_create_file("receive_data", 0644, den, NULL, &receive_status_fops); debugfs_create_file("mode", 0644, den, NULL, &mode_status_fops); debugfs_create_file("cl_dvs_margin", 0644, den, NULL, &mode_margin_fops); #ifdef CONFIG_EXYNOS_APM_VOLTAGE_DEBUG debugfs_create_file("cl_voltage", 0644, den, NULL, &cl_voltage_fops); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_CPU debugfs_create_file("cpu_cl_margin", 0644, den, NULL, &cpu_margin_fops); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_G3D debugfs_create_file("g3d_cl_margin", 0644, den, NULL, &g3d_margin_fops); #endif #ifdef CONFIG_EXYNOS_CL_DVFS_MIF debugfs_create_file("mif_cl_margin", 0644, den, NULL, &mif_margin_fops); #endif #if (defined(CONFIG_EXYNOS_CL_DVFS_CPU) || defined(CONFIG_EXYNOS_CL_DVFS_G3D) || defined(CONFIG_EXYNOS_CL_DVFS_MIF)) debugfs_create_file("cl_control", 0644, den, NULL, &cl_enable_fops); #endif } static const struct of_device_id mailbox_smc_match[] = { { .compatible = "samsung,exynos-mailbox" }, {}, }; static struct platform_driver samsung_mbox_driver = { .probe = samsung_mbox_probe, .remove = samsung_mbox_remove, .driver = { .name = "exynos-mailbox", .owner = THIS_MODULE, .of_match_table = mailbox_smc_match, .pm = &samsung_mailbox_pm, }, }; static int __init exynos_mailbox_init(void) { mailbox_debugfs(); return platform_driver_register(&samsung_mbox_driver); } fs_initcall(exynos_mailbox_init); static void __exit exynos_mailbox_exit(void) { class_destroy(mailbox_class); } module_exit(exynos_mailbox_exit);