--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of_platform.h>
+#include <linux/syscalls.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/exynos_ion.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <asm/cacheflush.h>
+#include <soc/samsung/exynos-pd.h>
+#include <linux/suspend.h>
+
+#include <soc/samsung/cal-if.h>
+#include <soc/samsung/bcm.h>
+#include <linux/memblock.h>
+#include <asm/map.h>
+
+#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 <sukju.yoon@samsung.com>");
+MODULE_DESCRIPTION("Samsung BCM driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("interface:bcm");