android_kernel_samsung_on5x.../drivers/soc/samsung/exynos-bcm.c
2018-06-19 23:16:04 +02:00

672 lines
15 KiB
C

/*
* 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 "../../../drivers/soc/samsung/pwrcal/pwrcal.h"
#include <linux/suspend.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
enum outform {
OUT_FILE = 1,
OUT_LOG,
};
static size_t fd_virt_addr;
struct bcm_info {
struct clk *clk;
char *clk_name;
char *pd_name;
int on;
};
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;
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;
struct bcm_monitor_data {
spinlock_t lock;
struct platform_device *pdev;
void __iomem *bin_base;
unsigned long bin_size;
} *g_data;
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 = kzalloc(sizeof(char) * MAX_STR, GFP_KERNEL);
char *filename;
unsigned long flags;
struct file *fp = NULL;
mm_segment_t old_fs = get_fs();
spin_lock_irqsave(&g_data->lock, flags);
if (fw_func)
filename = fw_func->fw_getresult(result);
spin_unlock_irqrestore(&g_data->lock, flags);
if (!fw_func || !filename) {
goto exit;
}
set_fs(KERNEL_DS);
fp = filp_open(filename, O_WRONLY|O_CREAT|O_APPEND, 0);
if (IS_ERR(fp)) {
BCM_BDG("filp_open fail!!");
goto exit;
}
do {
if (result)
vfs_write(fp, result, strlen(result), &fp->f_pos);
spin_lock_irqsave(&g_data->lock, flags);
filename = fw_func->fw_getresult(result);
spin_unlock_irqrestore(&g_data->lock, flags);
} while(filename);
filp_close(fp, NULL);
exit:
set_fs(old_fs);
kfree(result);
}
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 = -1;
unsigned long flags;
struct clk *clk = bcm->clk;
if (on ^ bcm->on) {
if (on && clk)
clk_enable(clk);
spin_lock_irqsave(&g_data->lock, flags);
ret = fw_func->pd_sync(bcm, on, get_time());
spin_unlock_irqrestore(&g_data->lock, flags);
if (!on && clk)
clk_disable(clk);
}
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(&g_data->lock, flags);
duration = fw_func->fw_periodic(get_time(),
cal_dfs_cached_get_rate, NULL);
spin_unlock_irqrestore(&g_data->lock, flags);
if (duration > 0) {
hrtimer_forward_now(hrtimer, ns_to_ktime(duration * 1000));
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(&g_data->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(&g_data->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 * 1000),
HRTIMER_MODE_REL);
}
}
return data;
}
EXPORT_SYMBOL(bcm_start);
static void bcm_log(void)
{
unsigned long flags;
char *str = kzalloc(sizeof(char) * MAX_STR, GFP_KERNEL);
spin_lock_irqsave(&g_data->lock, flags);
while (fw_func->fw_getresult(str)) {
spin_unlock_irqrestore(&g_data->lock, flags);
pr_info("%s", str);
spin_lock_irqsave(&g_data->lock, flags);
}
spin_unlock_irqrestore(&g_data->lock, flags);
kfree(str);
}
struct output_data *bcm_stop(const int *usr)
{
unsigned long flags;
struct output_data *data = NULL;
if (fw_func) {
spin_lock_irqsave(&g_data->lock, flags);
data = fw_func->fw_stop(get_time(),
cal_dfs_cached_get_rate, usr);
spin_unlock_irqrestore(&g_data->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)
{
return ioremap(phys_addr, size);
}
typedef struct fw_system_func*(*start_up_func_t)(void **func);
static void pd_init(void)
{
struct exynos_pm_domain *exypd;
struct bcm_info *bcm;
while (bcm = fw_func->get_pd(), bcm) {
if (bcm->clk_name) {
bcm->clk = clk_get(NULL, bcm->clk_name);
if (IS_ERR(bcm->clk))
BCM_BDG("failed to get clk %s\n",
bcm->clk_name);
else
clk_prepare(bcm->clk);
}
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 void 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("/data/bcm.bin", O_RDONLY, 0);
if (IS_ERR(fp)) {
BCM_BDG("filp_open fail!!");
ret = -EIO;
goto exit1;
}
fsize = fp->f_path.dentry->d_inode->i_size;
BCM_BDG("start, file path %s, size %ld Bytes\n",
"/data/bcm.bin", fsize);
buf = vmalloc(fsize);
if (!buf) {
BCM_BDG("failed to allocate memory");
ret = -ENOMEM;
goto exit2;
}
nread = vfs_read(fp, (char __user *)buf, fsize, &fp->f_pos);
if (nread != fsize) {
BCM_BDG("failed to read firmware file, %ld Bytes", nread);
ret = -EIO;
goto exit3;
}
lib_isp = (char *)fd_virt_addr;
/* TODO: Must change below size of reserved memory */
memset((char *)fd_virt_addr, 0x0, SZ_64K);
spin_lock_irqsave(&g_data->lock, flags);
flush_icache_range((unsigned long)lib_isp,
(unsigned long)lib_isp + SZ_64K);
memcpy((void *)lib_isp, (void *)buf, fsize);
flush_cache_all();
spin_unlock_irqrestore(&g_data->lock, flags);
fw_func = ((start_up_func_t)lib_isp)((void **)&os_func);
pd_init();
exit3:
vfree((void *)buf);
exit2:
filp_close(fp, NULL);
exit1:
set_fs(old_fs);
return;
}
static void pd_exit(void)
{
struct bcm_info *bcm;
struct exynos_pm_domain *exypd;
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)
clk_disable_unprepare(bcm->clk);
else
clk_unprepare(bcm->clk);
}
}
static ssize_t store_load_bcm_fw(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct bcm_monitor_data *data = platform_get_drvdata(pdev);
unsigned long flags;
int ret;
int value;
ret = sscanf(buf, "%d\n", &value);
if (ret != 1) {
dev_err(dev, "failed sscanf %d\n", ret);
return -EINVAL;
}
/* bcm stop and pd unprepare */
if (fw_func) {
pd_exit();
spin_lock_irqsave(&data->lock, flags);
if (fw_func->fw_stop(0, NULL, NULL))
hrtimer_try_to_cancel(&bcm_hrtimer);
fw_func->fw_cmd("0");
spin_unlock_irqrestore(&data->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 */
load_bcm_bin(NULL);
} else {
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)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct bcm_monitor_data *data = platform_get_drvdata(pdev);
unsigned long flags;
ssize_t count = 0;
spin_lock_irqsave(&data->lock, flags);
if(fw_func ) {
count += snprintf(buf, PAGE_SIZE, "\n[PPMU_binary load]\n");
count += snprintf(buf, PAGE_SIZE, "\n %llx, %ld\n",
(u64)data->bin_base, data->bin_size);
}
spin_unlock_irqrestore(&data->lock, flags);
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)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct bcm_monitor_data *data = platform_get_drvdata(pdev);
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(&data->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(&data->lock, flags);
switch (fw_func->get_outform()) {
case OUT_LOG:
bcm_log();
break;
default:
write_file(NULL);
}
break;
case BCM_START:
spin_lock_irqsave(&data->lock, flags);
info_str = fw_func->fw_cmd(buf);
spin_unlock_irqrestore(&data->lock, flags);
if (info_str && option)
bcm_start(NULL);
break;
default:
pd_exit();
spin_lock_irqsave(&data->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(&data->lock, flags);
pd_init();
break;
}
if (info_str)
BCM_BDG ("command: %s\n", info_str);
} else {
BCM_BDG ("binary is not loaded!\n");
}
return count;
}
static ssize_t show_cmd_bcm_fw(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = container_of(dev,
struct platform_device, dev);
struct bcm_monitor_data *data = platform_get_drvdata(pdev);
ssize_t count = 0;
unsigned long flags;
if (fw_func) {
spin_lock_irqsave(&data->lock, flags);
if (fw_func->fw_show)
count += fw_func->fw_show(buf);
spin_unlock_irqrestore(&data->lock, flags);
} else {
BCM_BDG ("LDFW 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 void param_init(struct bcm_monitor_data *data)
{
data->bin_base = (char *)fd_virt_addr;
data->bin_size = SZ_64K;
}
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(&g_data->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(&g_data->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;
BCM_BDG("%s: bcm probe\n", __func__);
g_data = devm_kzalloc(&pdev->dev, sizeof(struct bcm_monitor_data),
GFP_KERNEL);
if (!g_data) {
dev_err(&pdev->dev, "failed to data resource alloc\n");
return -ENOMEM;
}
g_data->pdev = pdev;
platform_set_drvdata(pdev, g_data);
g_data = g_data;
spin_lock_init(&g_data->lock);
param_init(g_data);
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__);
return PTR_ERR(bcm_wq);
}
work_file_out = (struct bcm_work_struct *)
devm_kzalloc(&pdev->dev, sizeof(struct bcm_work_struct),
GFP_KERNEL);
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;
err_sysfs:
return 0;
}
static int bcm_remove(struct platform_device *dev)
{
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(SZ_64K, SZ_4K);
fd_table.virtual = (ulong)fd_virt_addr;
fd_table.pfn = __phys_to_pfn(fd_phys_addr);
fd_table.length = SZ_64K;
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");