From ac089c372556a068ba32620e500ed7536ec39c92 Mon Sep 17 00:00:00 2001 From: Seokju Yoon Date: Mon, 20 Jun 2016 11:19:09 +0900 Subject: [PATCH] [COMMON] soc: samsung: Add the bcm driver Change-Id: Ibe3ce4189b269d21fb9781160b6c119dcdb82e07 Signed-off-by: Seokju Yoon --- drivers/soc/samsung/exynos-bcm.c | 694 +++++++++++++++++++++++++++++++ include/soc/samsung/bcm.h | 38 ++ 2 files changed, 732 insertions(+) create mode 100644 drivers/soc/samsung/exynos-bcm.c create mode 100644 include/soc/samsung/bcm.h diff --git a/drivers/soc/samsung/exynos-bcm.c b/drivers/soc/samsung/exynos-bcm.c new file mode 100644 index 000000000000..0b663d5ddb2b --- /dev/null +++ b/drivers/soc/samsung/exynos-bcm.c @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BCM_BDGGEN +#ifdef BCM_BDGGEN +#define BCM_BDG(x...) pr_info("bcm: " x) +#else +#define BCM_BDG(x...) do {} while (0) +#endif + +#define BCM_MAX_DATA 4 * 1024 * 1024 / 32 +#define MAX_STR 4 * 1024 +#define BCM_SIZE SZ_64K +#define NUM_CLK_MAX 8 +#define FILE_STR 32 + +enum outform { + OUT_FILE = 1, + OUT_LOG, +}; + +static size_t fd_virt_addr; + +struct bcm_info { + char *pd_name; + int on; + char *clk_name[NUM_CLK_MAX]; + struct clk *clk[NUM_CLK_MAX]; +}; + +static struct fw_system_func { + int (*fw_show)(char *); + char * (*fw_cmd)(const char *); + struct output_data *(*fw_init)(const int *); + struct output_data *(*fw_stop)(u64, unsigned long (*func)(unsigned int),const int *); + int (*fw_periodic)(u64, unsigned long (*func)(unsigned int), const int *); + char *(*fw_getresult)(char *str); + int (*fw_exit)(void); + int (*pd_sync)(struct bcm_info *, int, u64); + struct bcm_info *(*get_pd)(void); + int (*get_outform)(void); +} *fw_func; + +static struct os_system_func { + struct output_data *fdata; + struct output_data *ldata; + void __iomem *(*remap)(phys_addr_t phys_addr, size_t size); + void (*unmap)(volatile void __iomem *addr); + int (*sprint)(char *buf, const char *fmt, ...); + int (*print)(const char *, ...); +} os_func; + +static DEFINE_SPINLOCK(bcm_lock); +static char input_file[FILE_STR] = "/data/bcm.bin"; + +static struct hrtimer bcm_hrtimer; +static struct workqueue_struct *bcm_wq; +static struct bcm_work_struct { + struct work_struct work; + char *data; +} *work_file_out; + +static void write_file(struct work_struct *work) +{ + char *result; + char *filename; + unsigned long flags; + struct file *fp = NULL; + mm_segment_t old_fs = get_fs(); + + result = kzalloc(sizeof(char) * MAX_STR, GFP_KERNEL); + if (!result) { + pr_err("result memory allocation fail!\n"); + goto err_alloc; + } + + spin_lock_irqsave(&bcm_lock, flags); + if (fw_func) + filename = fw_func->fw_getresult(result); + spin_unlock_irqrestore(&bcm_lock, flags); + if (!fw_func || !filename) { + goto err_firm; + } + + set_fs(KERNEL_DS); + + fp = filp_open(filename, O_WRONLY|O_CREAT|O_APPEND, 0); + if (IS_ERR(fp)) { + pr_err("name : %s filp_open fail!!\n", filename); + goto err_filp_open; + + } + do { + if (result) + vfs_write(fp, result, strlen(result), &fp->f_pos); + spin_lock_irqsave(&bcm_lock, flags); + filename = fw_func->fw_getresult(result); + spin_unlock_irqrestore(&bcm_lock, flags); + } while(filename); + + filp_close(fp, NULL); +err_filp_open: + set_fs(old_fs); +err_firm: + kfree(result); +err_alloc: + return; +} + +static void bcm_file_out (char *data) +{ + work_file_out->data = data; + + queue_work(bcm_wq, (struct work_struct *)work_file_out); +} + +static u64 get_time(void) +{ + return sched_clock(); +} + +int bcm_pd_sync(struct bcm_info *bcm, bool on) +{ + int ret = 0; + unsigned long flags; + int i; + + if (on ^ bcm->on) { + if (on) { + for (i = 0; bcm->clk[i] && i < NUM_CLK_MAX; i++) + clk_enable(bcm->clk[i]); + } + spin_lock_irqsave(&bcm_lock, flags); + ret = fw_func->pd_sync(bcm, on, get_time()); + spin_unlock_irqrestore(&bcm_lock, flags); + + if (!on) { + for (i = 0; bcm->clk[i] && i < NUM_CLK_MAX; i++) + clk_disable(bcm->clk[i]); + } + } + return ret; +} +EXPORT_SYMBOL(bcm_pd_sync); + +static enum hrtimer_restart monitor_fn(struct hrtimer *hrtimer) +{ + unsigned long flags; + int duration; + enum hrtimer_restart ret = HRTIMER_NORESTART; + + spin_lock_irqsave(&bcm_lock, flags); + duration = fw_func->fw_periodic(get_time(), + cal_dfs_cached_get_rate, NULL); + spin_unlock_irqrestore(&bcm_lock, flags); + + if (duration > 0) { + hrtimer_forward_now(hrtimer, ns_to_ktime(duration * + NSEC_PER_USEC)); + ret = HRTIMER_RESTART; + } + return ret; +} + +struct output_data *bcm_start(const int *usr) +{ + unsigned long flags; + struct output_data *data = NULL; + int duration = 0; + if (fw_func) { + spin_lock_irqsave(&bcm_lock, flags); + data = fw_func->fw_init(usr); + if (data) { + duration = fw_func->fw_periodic(get_time(), + cal_dfs_cached_get_rate, usr); + } + spin_unlock_irqrestore(&bcm_lock, flags); + if (duration > 0) { + if (bcm_hrtimer.state) + hrtimer_try_to_cancel(&bcm_hrtimer); + if (!bcm_hrtimer.state) + hrtimer_start(&bcm_hrtimer, + ns_to_ktime(duration * + NSEC_PER_USEC), + HRTIMER_MODE_REL); + } + } + return data; +} +EXPORT_SYMBOL(bcm_start); + +static int bcm_log(void) +{ + unsigned long flags; + char *str; + int err; + str = kzalloc(sizeof(char) * MAX_STR, GFP_KERNEL); + if (!str) { + err = -ENOMEM; + return err; + } + spin_lock_irqsave(&bcm_lock, flags); + while (fw_func->fw_getresult(str)) { + spin_unlock_irqrestore(&bcm_lock, flags); + pr_info("%s", str); + spin_lock_irqsave(&bcm_lock, flags); + } + spin_unlock_irqrestore(&bcm_lock, flags); + kfree(str); + return 0; +} + +struct output_data *bcm_stop(const int *usr) +{ + unsigned long flags; + struct output_data *data = NULL; + if (fw_func) { + spin_lock_irqsave(&bcm_lock, flags); + data = fw_func->fw_stop(get_time(), + cal_dfs_cached_get_rate, usr); + spin_unlock_irqrestore(&bcm_lock, flags); + if (data) { + hrtimer_try_to_cancel(&bcm_hrtimer); + switch (fw_func->get_outform()) { + case OUT_FILE: + bcm_file_out(NULL); + break; + case OUT_LOG: + bcm_log(); + break; + } + } + } + return data; +} +EXPORT_SYMBOL(bcm_stop); + +static void __iomem *bcm_ioremap(phys_addr_t phys_addr, size_t size) +{ + void __iomem *ret; + ret = ioremap(phys_addr, size); + if (!ret) + pr_err("failed to map bcm physical address\n"); + return ret; +} + +typedef struct fw_system_func*(*start_up_func_t)(void **func); + +static void pd_init(void) +{ + struct exynos_pm_domain *exypd = NULL; + struct bcm_info *bcm; + int i; + while (bcm = fw_func->get_pd(), bcm) { + for (i = 0; i < NUM_CLK_MAX; i++){ + if (bcm->clk_name[i]) { + bcm->clk[i] = clk_get(NULL, bcm->clk_name[i]); + if (IS_ERR(bcm->clk[i])) + pr_err("failed to get clk %s\n", + bcm->clk_name[i]); + else + clk_prepare(bcm->clk[i]); + } else { + bcm->clk[i] = NULL; + break; + } + } + exypd = NULL; + bcm->on = false; + exypd = exynos_pd_lookup_name(bcm->pd_name); + if (exypd) { + mutex_lock(&exypd->access_lock); + exypd->bcm = bcm; + if (cal_pd_status(exypd->cal_pdid)) { + bcm_pd_sync(bcm, true); + } + mutex_unlock(&exypd->access_lock); + } else { + bcm_pd_sync(bcm, true); + } + } +} + +static int load_bcm_bin(struct work_struct *work) +{ + int ret = 0; + struct file *fp = NULL; + long fsize, nread; + unsigned long flags; + u8 *buf = NULL; + char *lib_isp = NULL; + mm_segment_t old_fs; + + os_func.print = printk; + os_func.sprint = sprintf; + os_func.remap = bcm_ioremap; + os_func.unmap = iounmap; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + fp = filp_open(input_file, O_RDONLY, 0); + if (IS_ERR(fp)) { + pr_err("name : %s filp_open fail!!\n", input_file); + ret = -EIO; + goto err_fopen; + } + + fsize = fp->f_path.dentry->d_inode->i_size; + BCM_BDG("start, file path %s, size %ld Bytes\n", + input_file, fsize); + buf = vmalloc(fsize); + if (!buf) { + pr_err("failed to allocate memory\n"); + ret = -ENOMEM; + goto err_alloc; + + } + + nread = vfs_read(fp, (char __user *)buf, fsize, &fp->f_pos); + if (nread != fsize) { + pr_err("failed to read firmware file, %ld Bytes\n", nread); + ret = -EIO; + goto err_vfs_read; + } + + lib_isp = (char *)fd_virt_addr; + /* TODO: Must change below size of reserved memory */ + memset((char *)fd_virt_addr, 0x0, BCM_SIZE); + + spin_lock_irqsave(&bcm_lock, flags); + flush_icache_range((unsigned long)lib_isp, + (unsigned long)lib_isp + BCM_SIZE); + memcpy((void *)lib_isp, (void *)buf, fsize); + flush_cache_all(); + + spin_unlock_irqrestore(&bcm_lock, flags); + fw_func = ((start_up_func_t)lib_isp)((void **)&os_func); + pd_init(); + +err_vfs_read: + vfree((void *)buf); +err_alloc: + filp_close(fp, NULL); +err_fopen: + set_fs(old_fs); + return ret; +} + +static void pd_exit(void) +{ + struct bcm_info *bcm; + struct exynos_pm_domain *exypd = NULL; + int i; + while (bcm = fw_func->get_pd(), bcm) { + exypd = exynos_pd_lookup_name(bcm->pd_name); + if (exypd) { + mutex_lock(&exypd->access_lock); + exypd->bcm = NULL; + bcm_pd_sync(bcm, false); + mutex_unlock(&exypd->access_lock); + } else { + bcm_pd_sync(bcm, false); + } + if (bcm->on) { + for (i = 0; bcm->clk[i] && i < NUM_CLK_MAX; i++) + clk_disable_unprepare(bcm->clk[i]); + } else { + for (i = 0; bcm->clk[i] && i < NUM_CLK_MAX; i++) + clk_unprepare(bcm->clk[i]); + } + } +} + +static ssize_t store_load_bcm_fw(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long flags; + int ret; + bool value = true; + char str[FILE_STR]; + + ret = sscanf(buf, "%s", str); + if (ret != 1) { + dev_err(dev, "failed sscanf %d\n", ret); + return -EINVAL; + } + if (str[0] == '0') + value = false; + else if (str[0] == '/') + strncpy(input_file, str, strlen(str) + 1); + + /* bcm stop and pd unprepare */ + if (fw_func) { + pd_exit(); + spin_lock_irqsave(&bcm_lock, flags); + if (fw_func->fw_stop(0, NULL, NULL)) + hrtimer_try_to_cancel(&bcm_hrtimer); + fw_func->fw_cmd("0"); + spin_unlock_irqrestore(&bcm_lock, flags); + fw_func->fw_exit(); + fw_func = NULL; + } + if (value) { + if (!os_func.fdata) { + os_func.fdata = kzalloc(sizeof(struct output_data) * + BCM_MAX_DATA, GFP_KERNEL); + os_func.ldata = os_func.fdata + BCM_MAX_DATA; + } else { + memset((char *)os_func.fdata, 0x0, + sizeof(struct output_data) * BCM_MAX_DATA); + } + + /* load binary */ + ret = load_bcm_bin(NULL); + if (!ret) + return count; + } + + kfree(os_func.fdata); + os_func.fdata = NULL; + os_func.ldata = NULL; + return count; +} + +static ssize_t show_load_bcm_fw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + + count += snprintf(buf, PAGE_SIZE, "[BCM] addr %llx size %d: ", + (u64)fd_virt_addr, BCM_SIZE); + if(fw_func) { + count += snprintf(buf + count, PAGE_SIZE, "%s done\n", + input_file); + } else { + count += snprintf(buf + count, PAGE_SIZE, "%s not yet\n", + input_file); + } + return count; +} + +#define BCM_START 1 +#define BCM_STOP 0 + +static ssize_t store_cmd_bcm_fw(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char * info_str = NULL; + int cmd; + int option = 1; + int ret = 0; + unsigned long flags; + + if (fw_func) { + ret = sscanf(buf, "%d %d", &cmd, &option); + switch (cmd) { + case BCM_STOP: + spin_lock_irqsave(&bcm_lock, flags); + if (fw_func->fw_stop(get_time(), + cal_dfs_cached_get_rate, NULL)) + hrtimer_try_to_cancel(&bcm_hrtimer); + info_str = fw_func->fw_cmd(buf); + spin_unlock_irqrestore(&bcm_lock, flags); + switch (fw_func->get_outform()) { + case OUT_LOG: + bcm_log(); + break; + default: + write_file(NULL); + } + break; + case BCM_START: + spin_lock_irqsave(&bcm_lock, flags); + info_str = fw_func->fw_cmd(buf); + spin_unlock_irqrestore(&bcm_lock, flags); + if (info_str && option) + bcm_start(NULL); + break; + default: + pd_exit(); + spin_lock_irqsave(&bcm_lock, flags); + if (fw_func->fw_stop(get_time(), + cal_dfs_cached_get_rate, NULL)) + hrtimer_try_to_cancel(&bcm_hrtimer); + info_str = fw_func->fw_cmd(buf); + spin_unlock_irqrestore(&bcm_lock, flags); + pd_init(); + break; + } + + if (info_str) + BCM_BDG ("command: %s\n", info_str); + } else { + BCM_BDG ("bcm binary is not loaded!\n"); + } + + return count; +} + +static ssize_t show_cmd_bcm_fw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + unsigned long flags; + + if (fw_func) { + spin_lock_irqsave(&bcm_lock, flags); + + if (fw_func->fw_show) + count += fw_func->fw_show(buf); + + spin_unlock_irqrestore(&bcm_lock, flags); + } else { + BCM_BDG ("bcm binary is not loaded!\n"); + } + return count; +} +static DEVICE_ATTR(load_bin, 0640, show_load_bcm_fw, store_load_bcm_fw); +static DEVICE_ATTR(command, 0640, show_cmd_bcm_fw, store_cmd_bcm_fw); + +static struct attribute *bcm_sysfs_entries[] = { + &dev_attr_load_bin.attr, + &dev_attr_command.attr, + NULL, +}; + +static struct attribute_group bcm_attr_group = { + .attrs = bcm_sysfs_entries, +}; + +static int exynos_bcm_notifier_event(struct notifier_block *this, + unsigned long event, + void *ptr) +{ + unsigned long flags; + + if (fw_func) { + switch ((unsigned int)event) { + case PM_POST_SUSPEND: + bcm_start(NULL); + return NOTIFY_OK; + case PM_SUSPEND_PREPARE: + spin_lock_irqsave(&bcm_lock, flags); + if (fw_func->fw_stop(get_time(), + cal_dfs_cached_get_rate, NULL)) + hrtimer_try_to_cancel(&bcm_hrtimer); + fw_func->fw_cmd(NULL); + spin_unlock_irqrestore(&bcm_lock, flags); + return NOTIFY_OK; + } + } + + return NOTIFY_DONE; +} + +static struct notifier_block exynos_bcm_notifier = { + .notifier_call = exynos_bcm_notifier_event, +}; + +static int bcm_probe(struct platform_device *pdev) +{ + int ret; + + ret = sysfs_create_group(&pdev->dev.kobj, &bcm_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed create sysfs for sci debug data\n"); + goto err_sysfs; + } + + bcm_wq = create_freezable_workqueue("bcm_wq"); + if (IS_ERR(bcm_wq)) { + pr_err("%s: couldn't create workqueue\n", __FILE__); + goto err_workqueue; + } + + work_file_out = (struct bcm_work_struct *) + devm_kzalloc(&pdev->dev, sizeof(struct bcm_work_struct), + GFP_KERNEL); + if(!work_file_out) { + goto err_file_out; + } + INIT_WORK((struct work_struct *)work_file_out, write_file); + + register_pm_notifier(&exynos_bcm_notifier); + + hrtimer_init(&bcm_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + bcm_hrtimer.function = &monitor_fn; + + BCM_BDG("bcm driver is probed\n"); + + return 0; + +err_file_out: + destroy_workqueue(bcm_wq); +err_workqueue: + sysfs_remove_group(&pdev->dev.kobj, &bcm_attr_group); +err_sysfs: + return 0; +} + +static int bcm_remove(struct platform_device *pdev) +{ + unregister_pm_notifier(&exynos_bcm_notifier); + destroy_workqueue(bcm_wq); + sysfs_remove_group(&pdev->dev.kobj, &bcm_attr_group); + return 0; +} + +static const struct of_device_id bcm_dt_match[] = { + { + .compatible = "samsung,bcm", + }, + {}, +}; + +static struct platform_driver bcm_driver = { + .probe = bcm_probe, + .remove = bcm_remove, + .driver = { + .name = "bcm", + .owner = THIS_MODULE, + }, +}; + +static struct platform_device bcm_device = { + .name = "bcm", + .id = -1, +}; + +static int __init bcm_setup(char *str) +{ + struct map_desc fd_table; + phys_addr_t fd_phys_addr; + if (kstrtoul(str, 0, (unsigned long *)&fd_virt_addr)) + goto out; + fd_phys_addr = memblock_alloc(BCM_SIZE, SZ_4K); + fd_table.virtual = (ulong)fd_virt_addr; + fd_table.pfn = __phys_to_pfn(fd_phys_addr); + fd_table.length = BCM_SIZE; + fd_table.type = MT_MEMORY; + + iotable_init_exec(&fd_table, 1); + return 0; +out: + return -1; +} +__setup("bcm_setup=", bcm_setup); + +static int __init bcm_drv_register(void) +{ + int ret; + BCM_BDG("%s: bcm init\n", __func__); + + if (!fd_virt_addr) + return -EINVAL; + + ret = platform_device_register(&bcm_device ); + if (ret) + return ret; + + return platform_driver_register(&bcm_driver); +} +late_initcall(bcm_drv_register); + +MODULE_AUTHOR("Seokju Yoon "); +MODULE_DESCRIPTION("Samsung BCM driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("interface:bcm"); diff --git a/include/soc/samsung/bcm.h b/include/soc/samsung/bcm.h new file mode 100644 index 000000000000..a4b614575977 --- /dev/null +++ b/include/soc/samsung/bcm.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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. + */ + +#ifndef __EXYNOS_BCM_H_ +#define __EXYNOS_BCM_H_ + +struct bcm_info; + +struct output_data { + int index; + u32 rd0; + u32 rd1; + u32 rd2; + u32 rd3; + u32 rd4; + u64 rd5; +}; + + +#if defined(CONFIG_EXYNOS_BCM) +int bcm_pd_sync(struct bcm_info *, bool); +struct output_data *bcm_start(const int *); +struct output_data *bcm_stop(const int *); +#else + +#define bcm_pd_sync(a, b) do {} while (0) +#define bcm_start(a) do {} while (0) +#define bcm_stop(a) do {} while (0) +#endif + +#endif -- 2.20.1