meson: add timer/cpu_info/cpu_version/mailbox/reg_access/secmon drivers

PD#138714: add base amlogic drivers
bc timer/cpu_info/cpu_version/mailbox/reg_access/secmon

Change-Id: If45019748dbdc646300ecd67fda2a04f03b7ea52
Signed-off-by: Yun Cai <yun.cai@amlogic.com>
This commit is contained in:
Yun Cai
2017-01-25 16:15:05 +08:00
committed by Victor Wan
parent 8cd9ca2e48
commit f79bffd6eb
29 changed files with 2081 additions and 3 deletions

View File

@@ -13409,3 +13409,25 @@ F: */
AMLOGIC Pinmux
M: Jianxin Pan <jianxin.pan@amlogic.com>
F: drivers/amlogic/pinctrl/*
AMLOGIC CLOCKSOURCE DRIVER
M: Yun Cai <yun.cai@amlogic.com>
F: drivers/amlogic/clocksource/
AMLOGIC CPU INFORMATION DRIVER
M: Yun Cai <yun.cai@amlogic.com>
F: drivers/amlogic/cpu_info/
F: drivers/amlogic/cpu_version/
AMLOGIC MAILBOX DRIVER
M: Yun Cai <yun.cai@amlogic.com>
F: drivers/amlogic/mailbox/
AMLOGIC REGISTER DEBUG DRIVER
M: Yun Cai <yun.cai@amlogic.com>
F: drivers/amlogic/reg_access/
AMLOGIC SECURE MONITOR DRIVER
M: Yun Cai <yun.cai@amlogic.com>
F: drivers/amlogic/secmon/

View File

@@ -130,7 +130,6 @@
interrupts = <GIC_PPI 9 0xf04>;
};
psci {
compatible = "arm,psci";
method = "smc";
@@ -139,12 +138,14 @@
cpu_on = <0xC4000003>;
migrate = <0xC4000005>;
};
secmon {
compatible = "amlogic, secmon";
memory-region = <&secmon_reserved>;
in_base_func = <0x82000020>;
out_base_func = <0x82000021>;
};
cpu_iomap{
compatible = "amlogic, iomap";
#address-cells=<2>;
@@ -163,7 +164,26 @@
cpu_version{
reg=<0x0 0xc8100220 0x0 0x4>;
status = "okay";
};
cpu_info{
compatible = "amlogic, cpuinfo";
cpuinfo_cmd = <0x82000044>;
status = "okay";
};
mailbox: mhu@c883c400 {
compatible = "amlogic, meson_mhu";
reg = <0x0 0xc883c400 0x0 0x4c>, /* MHU registers */
<0x0 0xc8013000 0x0 0x800>; /* Payload area */
interrupts = <0 209 8>, /* low priority interrupt */
<0 210 8>; /* high priority interrupt */
#mbox-cells = <1>;
mbox-names = "cpu_to_scp_low", "cpu_to_scp_high";
mboxes = <&mailbox 0 &mailbox 1>;
};
aobus: aobus@c8100000 {
compatible = "simple-bus";
reg = <0x0 0xc8100000 0x0 0x100000>;

View File

@@ -175,7 +175,6 @@
interrupts = <GIC_PPI 9 0xf04>;
};
psci {
compatible = "arm,psci";
method = "smc";
@@ -184,12 +183,14 @@
cpu_on = <0xC4000003>;
migrate = <0xC4000005>;
};
secmon {
compatible = "amlogic, secmon";
memory-region = <&secmon_reserved>;
in_base_func = <0x82000020>;
out_base_func = <0x82000021>;
};
cpu_iomap{
compatible = "amlogic, iomap";
#address-cells=<2>;
@@ -208,7 +209,26 @@
cpu_version{
reg=<0x0 0xc8100220 0x0 0x4>;
status = "okay";
};
cpu_info{
compatible = "amlogic, cpuinfo";
cpuinfo_cmd = <0x82000044>;
status = "okay";
};
mailbox: mhu@c883c400 {
compatible = "amlogic, meson_mhu";
reg = <0x0 0xc883c400 0x0 0x4c>, /* MHU registers */
<0x0 0xc8013000 0x0 0x800>; /* Payload area */
interrupts = <0 209 8>, /* low priority interrupt */
<0 210 8>; /* high priority interrupt */
#mbox-cells = <1>;
mbox-names = "cpu_to_scp_low", "cpu_to_scp_high";
mboxes = <&mailbox 0 &mailbox 1>;
};
aobus: aobus@c8100000 {
compatible = "simple-bus";
reg = <0x0 0xc8100000 0x0 0x100000>;

View File

@@ -158,6 +158,14 @@ CONFIG_AMLOGIC_UART=y
CONFIG_AMLOGIC_SERIAL_MESON_CONSOLE=y
CONFIG_AMLOGIC_IOMAP=y
CONFIG_AMLOGIC_PINCTRL=y
CONFIG_AMLOGIC_SEC=y
CONFIG_AMLOGIC_CPU_VERSION=y
CONFIG_AMLOGIC_MESON64_VERSION=y
CONFIG_AMLOGIC_CPU_INFO=y
CONFIG_AMLOGIC_MHU_MBOX=y
CONFIG_AMLOGIC_REG_ACCESS=y
CONFIG_AMLOGIC_TIMER=y
CONFIG_AMLOGIC_BC_TIMER=y
CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
@@ -306,4 +314,3 @@ CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_CRYPTO_USER_API_SKCIPHER=y
CONFIG_CRC_T10DIF=y
CONFIG_CRC7=y

View File

@@ -16,5 +16,18 @@ source "drivers/amlogic/uart/Kconfig"
source "drivers/amlogic/iomap/Kconfig"
source "drivers/amlogic/pinctrl/Kconfig"
source "drivers/amlogic/secmon/Kconfig"
source "drivers/amlogic/cpu_version/Kconfig"
source "drivers/amlogic/cpu_info/Kconfig"
source "drivers/amlogic/mailbox/Kconfig"
source "drivers/amlogic/reg_access/Kconfig"
source "drivers/amlogic/clocksource/Kconfig"
endmenu
endif

View File

@@ -11,4 +11,18 @@
obj-$(CONFIG_AMLOGIC_UART) += uart/
obj-$(CONFIG_AMLOGIC_PINCTRL) += pinctrl/
obj-$(CONFIG_AMLOGIC_IOMAP) += iomap/
obj-$(CONFIG_AMLOGIC_SEC) += secmon/
obj-$(CONFIG_AMLOGIC_CPU_VERSION) += cpu_version/
obj-$(CONFIG_AMLOGIC_CPU_INFO) += cpu_info/
obj-$(CONFIG_AMLOGIC_MHU_MBOX) += mailbox/
obj-$(CONFIG_AMLOGIC_REG_ACCESS) += reg_access/
obj-$(CONFIG_AMLOGIC_TIMER) += clocksource/

View File

@@ -0,0 +1,14 @@
menuconfig AMLOGIC_TIMER
bool "Amlogic Meson timer driver"
default n
help
This is the Amlogic Meson driver interface driver
if AMLOGIC_TIMER
config AMLOGIC_BC_TIMER
bool "meson broadcast timer support"
def_bool n
depends on AMLOGIC_TIMER
select CLKSRC_OF if OF
help
This is a new clocksource driver for amlogic timer
endif

View File

@@ -0,0 +1 @@
obj-$(CONFIG_AMLOGIC_BC_TIMER) += meson_bc_timer.o

View File

@@ -0,0 +1,282 @@
/*
* drivers/amlogic/clocksource/meson_bc_timer.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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 <linux/kernel.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/delay.h>
#include <linux/stat.h>
#include <asm/memory.h>
#include <linux/sched_clock.h>
#include <linux/of.h>
#include <asm/smp_plat.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/cpu.h>
#undef pr_fmt
#define pr_fmt(fmt) "meson_bc_timer: " fmt
/***********************************************************************
* System timer
**********************************************************************/
#define TIMER_E_RESOLUTION_BIT 8
#define TIMER_E_ENABLE_BIT 20
#define TIMER_E_RESOLUTION_MASK (7UL << TIMER_E_RESOLUTION_BIT)
#define TIMER_E_RESOLUTION_SYS 0
#define TIMER_E_RESOLUTION_1us 1
#define TIMER_E_RESOLUTION_10us 2
#define TIMER_E_RESOLUTION_100us 3
#define TIMER_E_RESOLUTION_1ms 4
#define TIMER_DI_RESOLUTION_BIT 6
#define TIMER_CH_RESOLUTION_BIT 4
#define TIMER_BG_RESOLUTION_BIT 2
#define TIMER_AF_RESOLUTION_BIT 0
#define TIMER_DI_ENABLE_BIT 19
#define TIMER_CH_ENABLE_BIT 18
#define TIMER_BG_ENABLE_BIT 17
#define TIMER_AF_ENABLE_BIT 16
#define TIMER_DI_MODE_BIT 15
#define TIMER_CH_MODE_BIT 14
#define TIMER_BG_MODE_BIT 13
#define TIMER_AF_MODE_BIT 12
#define TIMER_RESOLUTION_1us 0
#define TIMER_RESOLUTION_10us 1
#define TIMER_RESOLUTION_100us 2
#define TIMER_RESOLUTION_1ms 3
void __iomem *timer_ctrl_base;
static inline void aml_set_reg32_mask(void __iomem *_reg, const uint32_t _mask)
{
uint32_t _val;
_val = readl_relaxed(_reg) | _mask;
writel_relaxed(_val, _reg);
}
static inline void aml_write_reg32(void __iomem *_reg, const uint32_t _value)
{
writel_relaxed(_value, _reg);
}
static inline void aml_clr_reg32_mask(void __iomem *_reg, const uint32_t _mask)
{
writel_relaxed((readl_relaxed(_reg) & (~(_mask))), _reg);
}
static inline void
aml_set_reg32_bits(void __iomem *_reg, const uint32_t _value,
const uint32_t _start, const uint32_t _len)
{
writel_relaxed(((readl_relaxed(_reg) &
~(((1L << (_len))-1) << (_start))) |
(((_value)&((1L<<(_len))-1)) << (_start))),
_reg);
}
/********** Clock Event Device, Timer-ABCD/FGHI *********/
struct meson_clock {
struct irqaction irq;
const char *name; /*A,B,C,D,F,G,H,I*/
int bit_enable;
int bit_mode;
int bit_resolution;
void __iomem *mux_reg;
void __iomem *reg;
unsigned int init_flag;
};
static struct meson_clock bc_clock;
static irqreturn_t meson_timer_interrupt(int irq, void *dev_id);
static int meson_set_next_event(unsigned long evt,
struct clock_event_device *dev);
static DEFINE_SPINLOCK(time_lock);
static int meson_set_next_event(unsigned long evt,
struct clock_event_device *dev)
{
struct meson_clock *clk = &bc_clock;
/* use a big number to clear previous trigger cleanly */
aml_set_reg32_mask(clk->reg, evt & 0xffff);
/* then set next event */
aml_set_reg32_bits(clk->reg, evt, 0, 16);
return 0;
}
static int meson_clkevt_shutdown(struct clock_event_device *dev)
{
struct meson_clock *clk = &bc_clock;
spin_lock(&time_lock);
/* pr_info("Disable timer %p %s\n",dev,dev->name); */
aml_set_reg32_bits(clk->mux_reg, 0, clk->bit_enable, 1);
spin_unlock(&time_lock);
return 0;
}
static int meson_clkevt_set_periodic(struct clock_event_device *dev)
{
struct meson_clock *clk = &bc_clock;
spin_lock(&time_lock);
aml_set_reg32_bits(clk->mux_reg, 1, clk->bit_mode, 1);
aml_set_reg32_bits(clk->mux_reg, 1, clk->bit_enable, 1);
/* pr_info("Periodic timer %s!,mux_reg=%x\n",*/
/* dev->name,readl_relaxed(clk->mux_reg)); */
spin_unlock(&time_lock);
return 0;
}
static int meson_clkevt_set_oneshot(struct clock_event_device *dev)
{
struct meson_clock *clk = &bc_clock;
spin_lock(&time_lock);
aml_set_reg32_bits(clk->mux_reg, 0, clk->bit_mode, 1);
aml_set_reg32_bits(clk->mux_reg, 1, clk->bit_enable, 1);
/* pr_info("One shot timer %s!mux_reg=%x\n",*/
/* dev->name,readl_relaxed(clk->mux_reg)); */
spin_unlock(&time_lock);
return 0;
}
static int meson_clkevt_resume(struct clock_event_device *dev)
{
struct meson_clock *clk = &bc_clock;
spin_lock(&time_lock);
/* pr_info("Resume timer%s\n", dev->name); */
aml_set_reg32_bits(clk->mux_reg, 1, clk->bit_enable, 1);
spin_unlock(&time_lock);
return 0;
}
/* Clock event timer interrupt handler */
static irqreturn_t meson_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = dev_id;
if (evt == NULL || evt->event_handler == NULL) {
WARN_ONCE(evt == NULL || evt->event_handler == NULL,
"%p %s %p %d",
evt, evt?evt->name:NULL,
evt?evt->event_handler:NULL, irq);
return IRQ_HANDLED;
}
evt->event_handler(evt);
return IRQ_HANDLED;
}
static struct clock_event_device meson_clockevent = {
.cpumask = cpu_all_mask,
.set_next_event = meson_set_next_event,
.set_state_shutdown = meson_clkevt_shutdown,
.set_state_periodic = meson_clkevt_set_periodic,
.set_state_oneshot = meson_clkevt_set_oneshot,
.tick_resume = meson_clkevt_resume,
};
/*
* This sets up the system timers, clock source and clock event.
*/
int clockevent_init_and_register(struct device_node *np)
{
struct device_node *timer;
struct meson_clock *mclk = &bc_clock;
timer = np;
if (!timer) {
pr_info(" * %s missing timer phandle\n",
timer->full_name);
return -1;
}
if (of_property_read_string(timer, "timer_name",
&meson_clockevent.name))
return -1;
if (of_property_read_u32(timer, "clockevent-rating",
&meson_clockevent.rating))
return -1;
if (of_property_read_u32(timer, "clockevent-shift",
&meson_clockevent.shift))
return -1;
if (of_property_read_u32(timer, "clockevent-features",
&meson_clockevent.features))
return -1;
if (of_property_read_u32(timer, "bit_enable", &mclk->bit_enable))
return -1;
if (of_property_read_u32(timer, "bit_mode", &mclk->bit_mode))
return -1;
if (of_property_read_u32(timer, "bit_resolution",
&mclk->bit_resolution))
return -1;
mclk->mux_reg = timer_ctrl_base;
mclk->reg = of_iomap(timer, 1);
pr_info("mclk->mux_reg =%p,mclk->reg =%p\n", mclk->mux_reg, mclk->reg);
mclk->irq.irq = irq_of_parse_and_map(timer, 0);
aml_set_reg32_mask(mclk->mux_reg,
((1 << mclk->bit_mode)
|(TIMER_RESOLUTION_1us << mclk->bit_resolution)));
meson_clockevent.mult = div_sc(1000000, NSEC_PER_SEC, 20);
meson_clockevent.max_delta_ns =
clockevent_delta2ns(0xfffe, &meson_clockevent);
meson_clockevent.min_delta_ns =
clockevent_delta2ns(1, &meson_clockevent);
mclk->irq.dev_id = &meson_clockevent;
mclk->irq.handler = meson_timer_interrupt;
mclk->irq.name = meson_clockevent.name;
mclk->irq.flags =
IRQF_TIMER | IRQF_IRQPOLL|IRQF_TRIGGER_RISING;
/* Set up the IRQ handler */
meson_clockevent.irq = mclk->irq.irq;
clockevents_register_device(&meson_clockevent);
setup_irq(mclk->irq.irq, &mclk->irq);
return 0;
}
/*meson broadcast timer*/
int __init meson_timer_init(struct device_node *np)
{
timer_ctrl_base = of_iomap(np, 0);
if (clockevent_init_and_register(np) < 0)
pr_err("%s err\n", __func__);
return 0;
}
CLOCKSOURCE_OF_DECLARE(meson_timer, "arm, meson-bc-timer", meson_timer_init);

View File

@@ -0,0 +1,6 @@
config AMLOGIC_CPU_INFO
bool "Amlogic chipid"
default n
help
say y to enable Amlogic chipid driver.

View File

@@ -0,0 +1 @@
obj-$(CONFIG_AMLOGIC_CPU_INFO) += cpu_info.o

View File

@@ -0,0 +1,136 @@
/*
* drivers/amlogic/cpu_info/cpu_info.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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 <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/amlogic/iomap.h>
#ifndef CONFIG_ARM64
#include <asm/opcodes-sec.h>
#endif
#include <linux/amlogic/secmon.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/amlogic/cpu_version.h>
static int cpuinfo_func_id;
unsigned int system_serial_low0;
unsigned int system_serial_low1;
unsigned int system_serial_high0;
unsigned int system_serial_high1;
#undef pr_fmt
#define pr_fmt(fmt) "cpuinfo: " fmt
struct aml_cpu_info {
unsigned int version;
u8 chipid[12];
unsigned int reserved[103];
};
static void __iomem *sharemem_output;
static struct aml_cpu_info *cpu_info_buf;
static noinline int fn_smc(u64 function_id, u64 arg0, u64 arg1,
u64 arg2)
{
register long x0 asm("x0") = function_id;
register long x1 asm("x1") = arg0;
register long x2 asm("x2") = arg1;
register long x3 asm("x3") = arg2;
asm volatile(
__asmeq("%0", "x0")
__asmeq("%1", "x1")
__asmeq("%2", "x2")
__asmeq("%3", "x3")
"smc #0\n"
: "+r" (x0)
: "r" (x1), "r" (x2), "r" (x3));
return function_id;
}
static int cpuinfo_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
unsigned int id;
unsigned int *p = NULL;
unsigned int version =
(get_meson_cpu_version(MESON_CPU_VERSION_LVL_MAJOR) << 24) |
(get_meson_cpu_version(MESON_CPU_VERSION_LVL_MINOR) << 16) |
(get_meson_cpu_version(MESON_CPU_VERSION_LVL_PACK) << 8);
if (!of_property_read_u32(np, "cpuinfo_cmd", &id))
cpuinfo_func_id = id;
cpu_info_buf = kzalloc(sizeof(struct aml_cpu_info), GFP_KERNEL);
if (!cpu_info_buf) {
pr_info("No memory to alloc\n");
return -ENOMEM;
}
sharemem_output = get_secmon_sharemem_output_base();
if (!sharemem_output) {
pr_info("secmon share mem prepare not okay\n");
return -ENOMEM;
}
sharemem_mutex_lock();
fn_smc(cpuinfo_func_id, 0, 0, 0);
memcpy((void *)cpu_info_buf,
(const void *)sharemem_output, sizeof(struct aml_cpu_info));
sharemem_mutex_unlock();
#if 0
int i;
unsigned int *p = (unsigned int *)(cpu_info_buf->chipid);
for (i = 0; i < 12; i++)
pr_info("cpu_info_buf->chipid[%d]=%x\n",
i, cpu_info_buf->chipid[i]);
#endif
p = (unsigned int *)(cpu_info_buf->chipid);
system_serial_low0 = *p;
system_serial_low1 = *(p+1);
system_serial_high0 = *(p+2);
system_serial_high1 = version;
pr_info("probe done\n");
return 0;
}
static const struct of_device_id cpuinfo_dt_match[] = {
{ .compatible = "amlogic, cpuinfo" },
{ /* sentinel */ },
};
static struct platform_driver cpuinfo_platform_driver = {
.probe = cpuinfo_probe,
.driver = {
.owner = THIS_MODULE,
.name = "cpuinfo",
.of_match_table = cpuinfo_dt_match,
},
};
static int __init meson_cpuinfo_init(void)
{
return platform_driver_register(&cpuinfo_platform_driver);
}
module_init(meson_cpuinfo_init);

View File

@@ -0,0 +1,18 @@
menuconfig AMLOGIC_CPU_VERSION
bool "Amlogic cpu version support"
default n
help
This is the Amlogic CPU version interface driver
if AMLOGIC_CPU_VERSION
config AMLOGIC_MESON64_VERSION
bool "Amlogic Meson64 cpu version"
default n
help
say y to enable
Amlogic gx-X
cpu version driver
.
endif

View File

@@ -0,0 +1 @@
obj-$(CONFIG_AMLOGIC_MESON64_VERSION) += meson64_cpu.o

View File

@@ -0,0 +1,92 @@
/*
* drivers/amlogic/cpu_version/meson64_cpu.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/of_address.h>
#include <linux/io.h>
static int meson_cpu_version[MESON_CPU_VERSION_LVL_MAX+1];
void __iomem *assist_hw_rev;
int get_meson_cpu_version(int level)
{
if (level >= 0 && level <= MESON_CPU_VERSION_LVL_MAX)
return meson_cpu_version[level];
return 0;
}
EXPORT_SYMBOL(get_meson_cpu_version);
/*
* detect if a cpu id is big cpu
*/
int arch_big_cpu(int cpu)
{
int type;
struct device_node *cpu_version;
cpu_version = of_find_node_by_name(NULL, "cpu_version");
if (cpu_version)
assist_hw_rev = of_iomap(cpu_version, 0);
else
return 0;
type = readl(assist_hw_rev) >> 24;
switch (type) {
case MESON_CPU_MAJOR_ID_GXM: /* 0 ~ 3 is faster cpu for GXM */
if (cpu < 4)
return 1;
default:
return 0;
}
return 0;
}
EXPORT_SYMBOL(arch_big_cpu);
int __init meson_cpu_version_init(void)
{
unsigned int ver;
struct device_node *cpu_version;
cpu_version = of_find_node_by_name(NULL, "cpu_version");
if (cpu_version)
assist_hw_rev = of_iomap(cpu_version, 0);
else
return 0;
meson_cpu_version[MESON_CPU_VERSION_LVL_MAJOR] =
readl(assist_hw_rev) >> 24;
ver = (readl(assist_hw_rev) >> 8) & 0xff;
meson_cpu_version[MESON_CPU_VERSION_LVL_MINOR] = ver;
ver = (readl(assist_hw_rev) >> 16) & 0xff;
meson_cpu_version[MESON_CPU_VERSION_LVL_PACK] = ver;
pr_info("Meson chip version = Rev%X (%X:%X - %X:%X)\n",
meson_cpu_version[MESON_CPU_VERSION_LVL_MINOR],
meson_cpu_version[MESON_CPU_VERSION_LVL_MAJOR],
meson_cpu_version[MESON_CPU_VERSION_LVL_MINOR],
meson_cpu_version[MESON_CPU_VERSION_LVL_PACK],
meson_cpu_version[MESON_CPU_VERSION_LVL_MISC]
);
return 0;
}
early_initcall(meson_cpu_version_init);

View File

@@ -0,0 +1,31 @@
#
# Amlogic mhu mailbox device configuration
#
menu "MESON MHU mailbox Support"
config AMLOGIC_MHU_MBOX
tristate "MESON MHU Mailbox"
default n
select MAILBOX
select ARM_SCPI_PROTOCOL
help
MESON MHU Mailbox driver.
.
.
.
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
default n
help
System Control and Power Interface (SCPI) Message Protocol is
defined for the purpose of communication between the Application
Cores(AP) and the System Control Processor(SCP). The MHU peripheral
provides a mechanism for inter-processor communication between SCP
and AP.
This protocol library provides interface for all the client drivers
making use of the features offered by the SCP.
endmenu

View File

@@ -0,0 +1,2 @@
obj-$(CONFIG_AMLOGIC_MHU_MBOX) += meson_mhu.o
obj-$(CONFIG_ARM_SCPI_PROTOCOL) += scpi_protocol.o

View File

@@ -0,0 +1,304 @@
/*
* drivers/amlogic/mailbox/meson_mhu.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include "meson_mhu.h"
struct device *the_scpi_device;
#define DRIVER_NAME "meson_mhu"
/*
* +--------------------+-------+---------------+
* | Hardware Register | Offset| Driver View |
* +--------------------+-------+---------------+
* | SCP_INTR_L_STAT | 0x014 | RX_STATUS(L) |
* | SCP_INTR_L_SET | 0x010 | RX_SET(L) |
* | SCP_INTR_L_CLEAR | 0x018 | RX_CLEAR(L) |
* +--------------------+-------+---------------+
* | SCP_INTR_H_STAT | 0x020 | RX_STATUS(H) |
* | SCP_INTR_H_SET | 0x01c | RX_SET(H) |
* | SCP_INTR_H_CLEAR | 0x024 | RX_CLEAR(H) |
* +--------------------+-------+---------------+
* | CPU_INTR_L_STAT | 0x038 | TX_STATUS(L) |
* | CPU_INTR_L_SET | 0x034 | TX_SET(L) |
* | CPU_INTR_L_CLEAR | 0x03c | TX_CLEAR(L) |
* +--------------------+-------+---------------+
* | CPU_INTR_H_STAT | 0x044 | TX_STATUS(H) |
* | CPU_INTR_H_SET | 0x040 | TX_SET(H) |
* | CPU_INTR_H_CLEAR | 0x048 | TX_CLEAR(H) |
* +--------------------+-------+---------------+
*/
#define RX_OFFSET(chan) (0x10 + (idx) * 0xc)
#define RX_STATUS(chan) (RX_OFFSET(chan) + 0x4)
#define RX_SET(chan) RX_OFFSET(chan)
#define RX_CLEAR(chan) (RX_OFFSET(chan) + 0x8)
#define TX_OFFSET(chan) (0x34 + (idx) * 0xc)
#define TX_STATUS(chan) (TX_OFFSET(chan) + 0x4)
#define TX_SET(chan) TX_OFFSET(chan)
#define TX_CLEAR(chan) (TX_OFFSET(chan) + 0x8)
/*
* +---------------+-------+----------------+
* | Payload | Offset| Driver View |
* +---------------+-------+----------------+
* | SCP->AP Low | 0x000 | RX_PAYLOAD(L) |
* | SCP->AP High | 0x400 | RX_PAYLOAD(H) |
* +---------------+-------+----------------+
* | AP->SCP Low | 0x200 | TX_PAYLOAD(H) |
* | AP->SCP High | 0x600 | TX_PAYLOAD(H) |
* +---------------+-------+----------------+
*/
#define PAYLOAD_MAX_SIZE 0x200
#define PAYLOAD_OFFSET 0x400
#define RX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET)
#define TX_PAYLOAD(chan) ((chan) * PAYLOAD_OFFSET + PAYLOAD_MAX_SIZE)
struct mhu_chan {
int index;
int rx_irq;
struct mhu_ctlr *ctlr;
struct mhu_data_buf *data;
};
struct mhu_ctlr {
struct device *dev;
void __iomem *mbox_base;
void __iomem *payload_base;
struct mbox_controller mbox_con;
struct mhu_chan channels[CHANNEL_MAX];
};
static irqreturn_t mbox_handler(int irq, void *p)
{
struct mbox_chan *link = (struct mbox_chan *)p;
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
void __iomem *payload = ctlr->payload_base;
int idx = chan->index;
struct mhu_data_buf *data;
unsigned int *pp;
u32 status = readl(mbox_base + RX_STATUS(idx));
if (status && irq == chan->rx_irq) {
data = chan->data;
pp = (unsigned int *)data->rx_buf;
if (!data)
return IRQ_NONE; /* spurious */
if (data->rx_buf)
memcpy(data->rx_buf, payload + RX_PAYLOAD(idx),
data->rx_size);
chan->data = NULL;
mbox_chan_received_data(link, data);
writel(~0, mbox_base + RX_CLEAR(idx));
}
return IRQ_HANDLED;
}
static int mhu_send_data(struct mbox_chan *link, void *msg)
{
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
void __iomem *payload = ctlr->payload_base;
struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
int idx = chan->index;
if (!data)
return -EINVAL;
chan->data = data;
if (data->tx_buf)
memcpy(payload + TX_PAYLOAD(idx), data->tx_buf, data->tx_size);
writel(data->cmd, mbox_base + TX_SET(idx));
return 0;
}
static int mhu_startup(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
int err, mbox_irq = chan->rx_irq;
err = request_threaded_irq(mbox_irq, mbox_handler, NULL, IRQF_ONESHOT,
DRIVER_NAME, link);
return err;
}
static void mhu_shutdown(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
chan->data = NULL;
free_irq(chan->rx_irq, link);
}
static bool mhu_last_tx_done(struct mbox_chan *link)
{
struct mhu_chan *chan = link->con_priv;
struct mhu_ctlr *ctlr = chan->ctlr;
void __iomem *mbox_base = ctlr->mbox_base;
int idx = chan->index;
return !readl(mbox_base + TX_STATUS(idx));
}
static struct mbox_chan_ops mhu_ops = {
.send_data = mhu_send_data,
.startup = mhu_startup,
.shutdown = mhu_shutdown,
.last_tx_done = mhu_last_tx_done,
};
static int mhu_probe(struct platform_device *pdev)
{
struct mhu_ctlr *ctlr;
struct mhu_chan *chan;
struct device *dev = &pdev->dev;
struct mbox_chan *l;
struct resource *res;
int idx;
static const char * const channel_names[] = {
CHANNEL_LOW_PRIORITY,
CHANNEL_HIGH_PRIORITY
};
ctlr = devm_kzalloc(dev, sizeof(*ctlr), GFP_KERNEL);
if (!ctlr)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "failed to get mailbox memory resource\n");
return -ENXIO;
}
ctlr->mbox_base = devm_ioremap_resource(dev, res);
if (IS_ERR(ctlr->mbox_base))
return PTR_ERR(ctlr->mbox_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res) {
dev_err(dev, "failed to get payload memory resource\n");
return -ENXIO;
}
ctlr->payload_base = devm_ioremap_resource(dev, res);
if (IS_ERR(ctlr->payload_base))
return PTR_ERR(ctlr->payload_base);
ctlr->dev = dev;
platform_set_drvdata(pdev, ctlr);
l = devm_kzalloc(dev, sizeof(*l) * CHANNEL_MAX, GFP_KERNEL);
if (!l)
return -ENOMEM;
ctlr->mbox_con.chans = l;
ctlr->mbox_con.num_chans = CHANNEL_MAX;
ctlr->mbox_con.txdone_irq = true;
ctlr->mbox_con.ops = &mhu_ops;
ctlr->mbox_con.dev = dev;
for (idx = 0; idx < CHANNEL_MAX; idx++) {
chan = &ctlr->channels[idx];
chan->index = idx;
chan->ctlr = ctlr;
chan->rx_irq = platform_get_irq(pdev, idx);
if (chan->rx_irq < 0) {
dev_err(dev, "failed to get interrupt for %s\n",
channel_names[idx]);
return -ENXIO;
}
l[idx].con_priv = chan;
}
if (mbox_controller_register(&ctlr->mbox_con)) {
dev_err(dev, "failed to register mailbox controller\n");
return -ENOMEM;
}
the_scpi_device = dev;
return 0;
}
static int mhu_remove(struct platform_device *pdev)
{
struct mhu_ctlr *ctlr = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
mbox_controller_unregister(&ctlr->mbox_con);
devm_kfree(dev, ctlr->mbox_con.chans);
devm_iounmap(dev, ctlr->payload_base);
devm_iounmap(dev, ctlr->mbox_base);
platform_set_drvdata(pdev, NULL);
devm_kfree(dev, ctlr);
return 0;
}
static const struct of_device_id mhu_of_match[] = {
{ .compatible = "amlogic, meson_mhu" },
{},
};
static struct platform_driver mhu_driver = {
.probe = mhu_probe,
.remove = mhu_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = mhu_of_match,
},
};
static int __init mhu_init(void)
{
return platform_driver_register(&mhu_driver);
}
core_initcall(mhu_init);
static void __exit mhu_exit(void)
{
platform_driver_unregister(&mhu_driver);
}
module_exit(mhu_exit);
MODULE_AUTHOR("yan wang <yan.wang@amlogic.com>");
MODULE_DESCRIPTION("MESON MHU mailbox driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,33 @@
/*
* drivers/amlogic/mailbox/meson_mhu.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#define CONTROLLER_NAME "mhu_ctlr"
#define CHANNEL_MAX 2
#define CHANNEL_LOW_PRIORITY "cpu_to_scp_low"
#define CHANNEL_HIGH_PRIORITY "cpu_to_scp_high"
struct mhu_data_buf {
u32 cmd;
int tx_size;
void *tx_buf;
int rx_size;
void *rx_buf;
void *cl_data;
};
extern struct device *the_scpi_device;

View File

@@ -0,0 +1,428 @@
/*
* drivers/amlogic/mailbox/scpi_protocol.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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 <linux/err.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/mailbox_client.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/slab.h>
#include "meson_mhu.h"
#define CMD_ID_SHIFT 0
#define CMD_ID_MASK 0xff
#define CMD_SENDER_ID_SHIFT 8
#define CMD_SENDER_ID_MASK 0xff
#define CMD_DATA_SIZE_SHIFT 20
#define CMD_DATA_SIZE_MASK 0x1ff
#define PACK_SCPI_CMD(cmd, sender, txsz) \
((((cmd) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
(((sender) & CMD_SENDER_ID_MASK) << CMD_SENDER_ID_SHIFT) | \
(((txsz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT))
#define MAX_DVFS_DOMAINS 3
#define MAX_DVFS_OPPS 16
#define DVFS_LATENCY(hdr) ((hdr) >> 16)
#define DVFS_OPP_COUNT(hdr) (((hdr) >> 8) & 0xff)
enum scpi_error_codes {
SCPI_SUCCESS = 0, /* Success */
SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
SCPI_ERR_ALIGN = 2, /* Invalid alignment */
SCPI_ERR_SIZE = 3, /* Invalid size */
SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */
SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */
SCPI_ERR_RANGE = 6, /* Value out of range */
SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */
SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */
SCPI_ERR_PWRSTATE = 9, /* Invalid power state */
SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */
SCPI_ERR_DEVICE = 11, /* Device error */
SCPI_ERR_MAX
};
struct scpi_data_buf {
int client_id;
struct mhu_data_buf *data;
struct completion complete;
};
static int high_priority_cmds[] = {
SCPI_CMD_GET_CSS_PWR_STATE,
SCPI_CMD_CFG_PWR_STATE_STAT,
SCPI_CMD_GET_PWR_STATE_STAT,
SCPI_CMD_SET_DVFS,
SCPI_CMD_GET_DVFS,
SCPI_CMD_SET_RTC,
SCPI_CMD_GET_RTC,
SCPI_CMD_SET_CLOCK_INDEX,
SCPI_CMD_SET_CLOCK_VALUE,
SCPI_CMD_GET_CLOCK_VALUE,
SCPI_CMD_SET_PSU,
SCPI_CMD_GET_PSU,
SCPI_CMD_SENSOR_CFG_PERIODIC,
SCPI_CMD_SENSOR_CFG_BOUNDS,
};
static struct scpi_dvfs_info *scpi_opps[MAX_DVFS_DOMAINS];
static int scpi_linux_errmap[SCPI_ERR_MAX] = {
0, -EINVAL, -ENOEXEC, -EMSGSIZE,
-EINVAL, -EACCES, -ERANGE, -ETIMEDOUT,
-ENOMEM, -EINVAL, -EOPNOTSUPP, -EIO,
};
static inline int scpi_to_linux_errno(int errno)
{
if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX)
return scpi_linux_errmap[errno];
return -EIO;
}
static bool high_priority_chan_supported(int cmd)
{
int idx;
for (idx = 0; idx < ARRAY_SIZE(high_priority_cmds); idx++)
if (cmd == high_priority_cmds[idx])
return true;
return false;
}
static void scpi_rx_callback(struct mbox_client *cl, void *msg)
{
struct mhu_data_buf *data = (struct mhu_data_buf *)msg;
struct scpi_data_buf *scpi_buf = data->cl_data;
complete(&scpi_buf->complete);
}
static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, bool high_priority)
{
struct mbox_chan *chan;
struct mbox_client cl = {0};
struct mhu_data_buf *data = scpi_buf->data;
u32 status;
cl.dev = the_scpi_device;
cl.rx_callback = scpi_rx_callback;
chan = mbox_request_channel(&cl, high_priority);
if (IS_ERR(chan))
return PTR_ERR(chan);
init_completion(&scpi_buf->complete);
if (mbox_send_message(chan, (void *)data) < 0) {
status = SCPI_ERR_TIMEOUT;
goto free_channel;
}
wait_for_completion(&scpi_buf->complete);
status = *(u32 *)(data->rx_buf); /* read first word */
free_channel:
mbox_free_channel(chan);
return scpi_to_linux_errno(status);
}
#define SCPI_SETUP_DBUF(scpi_buf, mhu_buf, _client_id,\
_cmd, _tx_buf, _rx_buf) \
do { \
struct mhu_data_buf *pdata = &mhu_buf; \
pdata->cmd = _cmd; \
pdata->tx_buf = &_tx_buf; \
pdata->tx_size = sizeof(_tx_buf); \
pdata->rx_buf = &_rx_buf; \
pdata->rx_size = sizeof(_rx_buf); \
scpi_buf.client_id = _client_id; \
scpi_buf.data = pdata; \
} while (0)
#define SCPI_SETUP_DBUF_SIZE(scpi_buf, mhu_buf, _client_id,\
_cmd, _tx_buf, _tx_size, _rx_buf, _rx_size) \
do { \
struct mhu_data_buf *pdata = &mhu_buf; \
pdata->cmd = _cmd; \
pdata->tx_buf = _tx_buf; \
pdata->tx_size = _tx_size; \
pdata->rx_buf = _rx_buf; \
pdata->rx_size = _rx_size; \
scpi_buf.client_id = _client_id; \
scpi_buf.data = pdata; \
} while (0)
static int scpi_execute_cmd(struct scpi_data_buf *scpi_buf)
{
struct mhu_data_buf *data;
bool high_priority;
if (!scpi_buf || !scpi_buf->data)
return -EINVAL;
data = scpi_buf->data;
high_priority = high_priority_chan_supported(data->cmd);
data->cmd = PACK_SCPI_CMD(data->cmd, scpi_buf->client_id,
data->tx_size);
data->cl_data = scpi_buf;
return send_scpi_cmd(scpi_buf, high_priority);
}
unsigned long scpi_clk_get_val(u16 clk_id)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 clk_rate;
} buf;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
SCPI_CMD_GET_CLOCK_VALUE, clk_id, buf);
if (scpi_execute_cmd(&sdata))
return 0;
return buf.clk_rate;
}
EXPORT_SYMBOL_GPL(scpi_clk_get_val);
int scpi_clk_set_val(u16 clk_id, unsigned long rate)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
int stat;
struct __packed {
u32 clk_rate;
u16 clk_id;
} buf;
buf.clk_rate = (u32)rate;
buf.clk_id = clk_id;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_CLOCKS,
SCPI_CMD_SET_CLOCK_VALUE, buf, stat);
return scpi_execute_cmd(&sdata);
}
EXPORT_SYMBOL_GPL(scpi_clk_set_val);
struct scpi_dvfs_info *scpi_dvfs_get_opps(u8 domain)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 header;
struct scpi_opp_entry opp[MAX_DVFS_OPPS];
} buf;
struct scpi_dvfs_info *opps;
size_t opps_sz;
int count, ret;
if (domain >= MAX_DVFS_DOMAINS)
return ERR_PTR(-EINVAL);
if (scpi_opps[domain]) /* data already populated */
return scpi_opps[domain];
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_GET_DVFS_INFO, domain, buf);
ret = scpi_execute_cmd(&sdata);
if (ret)
return ERR_PTR(ret);
opps = kmalloc(sizeof(*opps), GFP_KERNEL);
if (!opps)
return ERR_PTR(-ENOMEM);
count = DVFS_OPP_COUNT(buf.header);
opps_sz = count * sizeof(*(opps->opp));
opps->count = count;
opps->latency = DVFS_LATENCY(buf.header);
opps->opp = kmalloc(opps_sz, GFP_KERNEL);
if (!opps->opp) {
kfree(opps);
return ERR_PTR(-ENOMEM);
}
memcpy(opps->opp, &buf.opp[0], opps_sz);
scpi_opps[domain] = opps;
return opps;
}
EXPORT_SYMBOL_GPL(scpi_dvfs_get_opps);
int scpi_dvfs_get_idx(u8 domain)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u8 dvfs_idx;
} buf;
int ret;
if (domain >= MAX_DVFS_DOMAINS)
return -EINVAL;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_GET_DVFS, domain, buf);
ret = scpi_execute_cmd(&sdata);
if (!ret)
ret = buf.dvfs_idx;
return ret;
}
EXPORT_SYMBOL_GPL(scpi_dvfs_get_idx);
int scpi_dvfs_set_idx(u8 domain, u8 idx)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u8 dvfs_domain;
u8 dvfs_idx;
} buf;
int stat;
buf.dvfs_idx = idx;
buf.dvfs_domain = domain;
if (domain >= MAX_DVFS_DOMAINS)
return -EINVAL;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_DVFS,
SCPI_CMD_SET_DVFS, buf, stat);
return scpi_execute_cmd(&sdata);
}
EXPORT_SYMBOL_GPL(scpi_dvfs_set_idx);
int scpi_get_sensor(char *name)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u16 sensors;
} cap_buf;
struct __packed {
u32 status;
u16 sensor;
u8 class;
u8 trigger;
char name[20];
} info_buf;
int ret;
u16 sensor_id;
/* This should be handled by a generic macro */
do {
struct mhu_data_buf *pdata = &mdata;
pdata->cmd = SCPI_CMD_SENSOR_CAPABILITIES;
pdata->tx_size = 0;
pdata->rx_buf = &cap_buf;
pdata->rx_size = sizeof(cap_buf);
sdata.client_id = SCPI_CL_THERMAL;
sdata.data = pdata;
} while (0);
ret = scpi_execute_cmd(&sdata);
if (ret)
goto out;
ret = -ENODEV;
for (sensor_id = 0; sensor_id < cap_buf.sensors; sensor_id++) {
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_THERMAL,
SCPI_CMD_SENSOR_INFO, sensor_id, info_buf);
ret = scpi_execute_cmd(&sdata);
if (ret)
break;
if (!strcmp(name, info_buf.name)) {
ret = sensor_id;
break;
}
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_sensor);
int scpi_get_sensor_value(u16 sensor, u32 *val)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 val;
} buf;
int ret;
SCPI_SETUP_DBUF(sdata, mdata, SCPI_CL_THERMAL, SCPI_CMD_SENSOR_VALUE,
sensor, buf);
ret = scpi_execute_cmd(&sdata);
if (ret == 0)
*val = buf.val;
return ret;
}
EXPORT_SYMBOL_GPL(scpi_get_sensor_value);
/****Send fail when data size > 0x1fd. ***
* Because of USER_LOW_TASK_SHARE_MEM_BASE ***
* size limitation.
* You can call scpi_send_usr_data()
* multi-times when your data is bigger
* than 0x1fe
*/
int scpi_send_usr_data(u32 client_id, u32 *val, u32 size)
{
struct scpi_data_buf sdata;
struct mhu_data_buf mdata;
struct __packed {
u32 status;
u32 val;
} buf;
int ret;
/*client_id bit map should locates @ 0xff.
* bl30 will send client_id via half-Word
*/
if (client_id & ~0xff)
return -E2BIG;
/*Check size here because of USER_LOW_TASK_SHARE_MEM_BASE
* size limitation, and first Word is used as command,
* second word is used as tx_size.
*/
if (size > 0x1fd)
return -EPERM;
SCPI_SETUP_DBUF_SIZE(sdata, mdata, client_id, SCPI_CMD_SET_USR_DATA,
val, size, &buf, sizeof(buf));
ret = scpi_execute_cmd(&sdata);
return ret;
}
EXPORT_SYMBOL_GPL(scpi_send_usr_data);

View File

@@ -0,0 +1,7 @@
# Amlogic DEBUG
#
config AMLOGIC_REG_ACCESS
bool "Amlogic register access from userspace support"
default n
help
This is the Amlogic debug sysfs interface driver using debugfs interface

View File

@@ -0,0 +1 @@
obj-$(CONFIG_AMLOGIC_REG_ACCESS) =reg_access.o

View File

@@ -0,0 +1,210 @@
/*
* drivers/amlogic/reg_access/reg_access.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
/* Standard Linux headers */
#include <linux/types.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
static struct dentry *debugfs_root;
/* amlogic debug device*/
struct aml_ddev {
unsigned int cached_reg_addr;
unsigned int size;
int (*debugfs_reg_access)(struct aml_ddev *indio_dev,
unsigned int reg, unsigned int writeval,
unsigned int *readval);
};
int aml_reg_access(struct aml_ddev *indio_dev,
unsigned int reg, unsigned int writeval,
unsigned int *readval)
{
void __iomem *vaddr;
reg = round_down(reg, 0x3);
vaddr = ioremap(reg, 0x4);
if (readval)
*readval = readl(vaddr);
else
writel(writeval, vaddr);
iounmap(vaddr);
return 0;
}
static struct aml_ddev aml_dev;
static ssize_t paddr_read_file(struct file *file, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct aml_ddev *indio_dev = file->private_data;
char buf[80];
unsigned int val = 0;
ssize_t len;
int ret;
ret = indio_dev->debugfs_reg_access(indio_dev,
indio_dev->cached_reg_addr,
0, &val);
if (ret)
pr_err("%s: read failed\n", __func__);
len = snprintf(buf, sizeof(buf), "[0x%x] = 0x%X\n",
indio_dev->cached_reg_addr, val);
return simple_read_from_buffer(userbuf, count, ppos, buf, len);
}
static ssize_t paddr_write_file(struct file *file, const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct aml_ddev *indio_dev = file->private_data;
unsigned int reg, val;
char buf[80];
int ret;
count = min_t(size_t, count, (sizeof(buf)-1));
if (copy_from_user(buf, userbuf, count))
return -EFAULT;
buf[count] = 0;
ret = sscanf(buf, "%x %x", &reg, &val);
switch (ret) {
case 1:
indio_dev->cached_reg_addr = reg;
break;
case 2:
indio_dev->cached_reg_addr = reg;
ret = indio_dev->debugfs_reg_access(indio_dev, reg,
val, NULL);
if (ret) {
pr_err("%s: write failed\n", __func__);
return ret;
}
break;
default:
return -EINVAL;
}
return count;
}
static const struct file_operations paddr_file_ops = {
.open = simple_open,
.read = paddr_read_file,
.write = paddr_write_file,
};
static ssize_t dump_write_file(struct file *file, const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct aml_ddev *indio_dev = s->private;
unsigned int reg, val;
char buf[80];
int ret;
count = min_t(size_t, count, (sizeof(buf)-1));
if (copy_from_user(buf, userbuf, count))
return -EFAULT;
buf[count] = 0;
ret = sscanf(buf, "%x %i", &reg, &val);
switch (ret) {
case 2:
indio_dev->cached_reg_addr = reg;
indio_dev->size = val;
break;
default:
return -EINVAL;
}
return count;
}
static int dump_show(struct seq_file *s, void *what)
{
unsigned int i = 0, val;
int ret;
struct aml_ddev *indio_dev = s->private;
for (i = 0; i < indio_dev->size ; i++) {
ret = indio_dev->debugfs_reg_access(indio_dev,
indio_dev->cached_reg_addr,
0, &val);
if (ret)
pr_err("%s: read failed\n", __func__);
seq_printf(s, "[0x%x] = 0x%X\n",
indio_dev->cached_reg_addr, val);
indio_dev->cached_reg_addr += 4;
}
return 0;
}
static int dump_open(struct inode *inode, struct file *file)
{
return single_open(file, dump_show, inode->i_private);
}
static const struct file_operations dump_file_ops = {
.open = dump_open,
.read = seq_read,
.write = dump_write_file,
.llseek = seq_lseek,
};
static int __init aml_debug_init(void)
{
debugfs_root = debugfs_create_dir("aml_reg", NULL);
if (IS_ERR(debugfs_root) || !debugfs_root) {
pr_warn("failed to create debugfs directory\n");
debugfs_root = NULL;
return -1;
}
aml_dev.debugfs_reg_access = aml_reg_access;
debugfs_create_file("paddr", S_IFREG | 0444,
debugfs_root, &aml_dev, &paddr_file_ops);
debugfs_create_file("dump", S_IFREG | 0444,
debugfs_root, &aml_dev, &dump_file_ops);
return 0;
}
static void __exit aml_debug_exit(void)
{
}
module_init(aml_debug_init);
module_exit(aml_debug_exit);
MODULE_DESCRIPTION("Amlogic debug module");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xing Xu <xing.xu@amlogic.com>");

View File

@@ -0,0 +1,12 @@
# Amlogic secure monitor driver
config AMLOGIC_SEC
bool "Amlogic secure monitor driver support"
default n
help
This is the Amlogic secure monitor driver
this config only ctrl in makefile
prepare share memory
etc

View File

@@ -0,0 +1 @@
obj-$(CONFIG_AMLOGIC_SEC) += secmon.o

View File

@@ -0,0 +1,154 @@
/*
* drivers/amlogic/secmon/secmon.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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 <linux/errno.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/of_fdt.h>
#include <linux/libfdt_env.h>
#include <linux/of_reserved_mem.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <asm/compiler.h>
#ifndef CONFIG_ARM64
#include <asm/opcodes-sec.h>
#endif
#undef pr_fmt
#define pr_fmt(fmt) "secmon: " fmt
static void __iomem *sharemem_in_base;
static void __iomem *sharemem_out_base;
static long phy_in_base;
static long phy_out_base;
#ifdef CONFIG_ARM64
#define IN_SIZE 0x1000
#else
#define IN_SIZE 0x8000
#endif
#define OUT_SIZE 0x1000
static DEFINE_MUTEX(sharemem_mutex);
#ifdef CONFIG_ARM64
static long get_sharemem_info(unsigned int function_id)
{
asm volatile(
__asmeq("%0", "x0")
"smc #0\n"
: "+r" (function_id));
return function_id;
}
#else
static long get_sharemem_info(unsigned int function_id)
{
register long r0 asm("r0") = function_id;
asm volatile(
__asmeq("%0", "r0")
__asmeq("%1", "r0")
__SMC(0)
: "=r" (r0)
: "r"(r0));
return r0;
}
#endif
static int secmon_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
unsigned int id;
int ret;
if (!of_property_read_u32(np, "in_base_func", &id))
phy_in_base = get_sharemem_info(id);
if (!of_property_read_u32(np, "out_base_func", &id))
phy_out_base = get_sharemem_info(id);
ret = of_reserved_mem_device_init(&pdev->dev);
if (ret == 0)
pr_info("probe done\n");
return ret;
}
static const struct of_device_id secmon_dt_match[] = {
{ .compatible = "amlogic, secmon" },
{ /* sentinel */ },
};
static struct platform_driver secmon_platform_driver = {
.probe = secmon_probe,
.driver = {
.owner = THIS_MODULE,
.name = "secmon",
.of_match_table = secmon_dt_match,
},
};
int __init meson_secmon_init(void)
{
return platform_driver_register(&secmon_platform_driver);
}
module_init(meson_secmon_init);
void sharemem_mutex_lock(void)
{
mutex_lock(&sharemem_mutex);
}
void sharemem_mutex_unlock(void)
{
mutex_unlock(&sharemem_mutex);
}
void __iomem *get_secmon_sharemem_input_base(void)
{
return sharemem_in_base;
}
void __iomem *get_secmon_sharemem_output_base(void)
{
return sharemem_out_base;
}
static int secmon_mem_device_init(struct reserved_mem *rmem, struct device *dev)
{
sharemem_in_base = ioremap_cache(phy_in_base, IN_SIZE);
if (!sharemem_in_base) {
pr_info("secmon share mem in buffer remap fail!\n");
return -ENOMEM;
}
sharemem_out_base = ioremap_cache(phy_out_base, OUT_SIZE);
if (!sharemem_out_base) {
pr_info("secmon share mem out buffer remap fail!\n");
return -ENOMEM;
}
pr_info("share in base: 0x%lx, share out base: 0x%lx\n",
(long)sharemem_in_base, (long)sharemem_out_base);
return 0;
}
static const struct reserved_mem_ops rmem_secmon_ops = {
.device_init = secmon_mem_device_init,
};
static int __init secmon_mem_setup(struct reserved_mem *rmem)
{
rmem->ops = &rmem_secmon_ops;
pr_debug("share mem setup\n");
return 0;
}
RESERVEDMEM_OF_DECLARE(secmonmem, "amlogic, aml_secmon_memory",
secmon_mem_setup);

View File

@@ -0,0 +1,132 @@
/*
* include/linux/amlogic/cpu_version.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#ifndef __PLAT_MESON_CPU_H
#define __PLAT_MESON_CPU_H
#define MESON_CPU_MAJOR_ID_GXBB 0x1F
#define MESON_CPU_MAJOR_ID_GXTVBB 0x20
#define MESON_CPU_MAJOR_ID_GXL 0x21
#define MESON_CPU_MAJOR_ID_GXM 0x22
#define MESON_CPU_MAJOR_ID_TXL 0x23
#define MESON_CPU_VERSION_LVL_MAJOR 0
#define MESON_CPU_VERSION_LVL_MINOR 1
#define MESON_CPU_VERSION_LVL_PACK 2
#define MESON_CPU_VERSION_LVL_MISC 3
#define MESON_CPU_VERSION_LVL_MAX MESON_CPU_VERSION_LVL_MISC
extern unsigned int system_serial_low0;
extern unsigned int system_serial_low1;
extern unsigned int system_serial_high0;
extern unsigned int system_serial_high1;
int meson_cpu_version_init(void);
#ifdef CONFIG_AMLOGIC_CPU_VERSION
int get_meson_cpu_version(int level);
int arch_big_cpu(int cpu);
#else
static inline int get_meson_cpu_version(int level)
{
return -1;
}
static inline int arch_big_cpu(int cpu)
{
return 0;
}
#endif
static inline int get_cpu_type(void)
{
return get_meson_cpu_version(MESON_CPU_VERSION_LVL_MAJOR);
}
static inline u32 get_cpu_package(void)
{
unsigned int pk;
pk = get_meson_cpu_version(MESON_CPU_VERSION_LVL_PACK) & 0xF0;
return pk;
}
static inline bool package_id_is(unsigned int id)
{
return get_cpu_package() == id;
}
static inline bool is_meson_gxbb_cpu(void)
{
return get_cpu_type() == MESON_CPU_MAJOR_ID_GXBB;
}
static inline bool is_meson_gxtvbb_cpu(void)
{
return get_cpu_type() == MESON_CPU_MAJOR_ID_GXTVBB;
}
static inline bool is_meson_gxbb_package_905(void)
{
return (get_cpu_type() == MESON_CPU_MAJOR_ID_GXBB) &&
(get_cpu_package() != 0x20);
}
static inline bool is_meson_gxbb_package_905m(void)
{
return (get_cpu_type() == MESON_CPU_MAJOR_ID_GXBB) &&
(get_cpu_package() == 0x20);
}
static inline bool is_meson_gxl_cpu(void)
{
return get_cpu_type() == MESON_CPU_MAJOR_ID_GXL;
}
static inline bool is_meson_gxl_package_905D(void)
{
return is_meson_gxl_cpu() && package_id_is(0x0);
}
static inline bool is_meson_gxl_package_905X(void)
{
return is_meson_gxl_cpu() && package_id_is(0x80);
}
static inline bool is_meson_gxl_package_905L(void)
{
return is_meson_gxl_cpu() && package_id_is(0xc0);
}
static inline bool is_meson_gxl_package_905M2(void)
{
return is_meson_gxl_cpu() && package_id_is(0xe0);
}
static inline bool is_meson_gxm_cpu(void)
{
return get_cpu_type() == MESON_CPU_MAJOR_ID_GXM;
}
static inline bool is_meson_txl_cpu(void)
{
return get_cpu_type() == MESON_CPU_MAJOR_ID_TXL;
}
static inline bool cpu_after_eq(unsigned int id)
{
return get_cpu_type() >= id;
}
#endif

View File

@@ -0,0 +1,90 @@
/*
* include/linux/amlogic/scpi_protocol.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#ifndef _SCPI_PROTOCOL_H_
#define _SCPI_PROTOCOL_H_
#include <linux/types.h>
enum scpi_client_id {
SCPI_CL_NONE,
SCPI_CL_CLOCKS,
SCPI_CL_DVFS,
SCPI_CL_POWER,
SCPI_CL_THERMAL,
SCPI_CL_REMOTE,
SCPI_CL_LED_TIMER,
SCPI_MAX,
};
enum scpi_std_cmd {
SCPI_CMD_INVALID = 0x00,
SCPI_CMD_SCPI_READY = 0x01,
SCPI_CMD_SCPI_CAPABILITIES = 0x02,
SCPI_CMD_EVENT = 0x03,
SCPI_CMD_SET_CSS_PWR_STATE = 0x04,
SCPI_CMD_GET_CSS_PWR_STATE = 0x05,
SCPI_CMD_CFG_PWR_STATE_STAT = 0x06,
SCPI_CMD_GET_PWR_STATE_STAT = 0x07,
SCPI_CMD_SYS_PWR_STATE = 0x08,
SCPI_CMD_L2_READY = 0x09,
SCPI_CMD_SET_AP_TIMER = 0x0a,
SCPI_CMD_CANCEL_AP_TIME = 0x0b,
SCPI_CMD_DVFS_CAPABILITIES = 0x0c,
SCPI_CMD_GET_DVFS_INFO = 0x0d,
SCPI_CMD_SET_DVFS = 0x0e,
SCPI_CMD_GET_DVFS = 0x0f,
SCPI_CMD_GET_DVFS_STAT = 0x10,
SCPI_CMD_SET_RTC = 0x11,
SCPI_CMD_GET_RTC = 0x12,
SCPI_CMD_CLOCK_CAPABILITIES = 0x13,
SCPI_CMD_SET_CLOCK_INDEX = 0x14,
SCPI_CMD_SET_CLOCK_VALUE = 0x15,
SCPI_CMD_GET_CLOCK_VALUE = 0x16,
SCPI_CMD_PSU_CAPABILITIES = 0x17,
SCPI_CMD_SET_PSU = 0x18,
SCPI_CMD_GET_PSU = 0x19,
SCPI_CMD_SENSOR_CAPABILITIES = 0x1a,
SCPI_CMD_SENSOR_INFO = 0x1b,
SCPI_CMD_SENSOR_VALUE = 0x1c,
SCPI_CMD_SENSOR_CFG_PERIODIC = 0x1d,
SCPI_CMD_SENSOR_CFG_BOUNDS = 0x1e,
SCPI_CMD_SENSOR_ASYNC_VALUE = 0x1f,
SCPI_CMD_SET_USR_DATA = 0x20,
SCPI_CMD_COUNT
};
struct scpi_opp_entry {
u32 freq_hz;
u32 volt_mv;
} __packed;
struct scpi_dvfs_info {
unsigned int count;
unsigned int latency; /* in usecs */
struct scpi_opp_entry *opp;
} __packed;
unsigned long scpi_clk_get_val(u16 clk_id);
int scpi_clk_set_val(u16 clk_id, unsigned long rate);
int scpi_dvfs_get_idx(u8 domain);
int scpi_dvfs_set_idx(u8 domain, u8 idx);
struct scpi_dvfs_info *scpi_dvfs_get_opps(u8 domain);
int scpi_get_sensor(char *name);
int scpi_get_sensor_value(u16 sensor, u32 *val);
int scpi_send_usr_data(u32 client_id, u32 *val, u32 size);
#endif /*_SCPI_PROTOCOL_H_*/

View File

@@ -0,0 +1,26 @@
/*
* include/linux/amlogic/secmon.h
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#ifndef __SEC_MON_H__
#define __SEC_MON_H__
void __iomem *get_secmon_sharemem_input_base(void);
void __iomem *get_secmon_sharemem_output_base(void);
void sharemem_mutex_lock(void);
void sharemem_mutex_unlock(void);
#endif