diff options
author | Guodong Xu <guodong.xu@linaro.org> | 2015-09-18 16:29:04 +0800 |
---|---|---|
committer | Guodong Xu <guodong.xu@linaro.org> | 2015-09-18 16:29:04 +0800 |
commit | c98d7d03606acab1820b7b202e855d88cff0b8cb (patch) | |
tree | fb5f0185393451d3456a2ffc29ba630ad1f4c490 | |
parent | 6ff33f3902c3b1c5d0db6b1e2c70b6d76fba357f (diff) | |
parent | 0078f0703ea2254f13e49766d052ac70f9bf0037 (diff) |
Merge pull request #119 from Leo-Yan/tracking-hikey-pmtracking-integration-hilt-linux-linaro-ll-20151002.0tracking-integration-hilt-linux-linaro-ll-20150925.0
Tracking-hikey-pm: Enable Hikey power management features
-rw-r--r-- | Documentation/devicetree/bindings/mailbox/hisilicon,hi6220-mailbox.txt | 57 | ||||
-rw-r--r-- | arch/arm64/Kconfig.platforms | 1 | ||||
-rw-r--r-- | arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 16 | ||||
-rw-r--r-- | arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 191 | ||||
-rw-r--r-- | drivers/clk/hisilicon/Kconfig | 8 | ||||
-rw-r--r-- | drivers/clk/hisilicon/Makefile | 3 | ||||
-rw-r--r-- | drivers/mailbox/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mailbox/Makefile | 2 | ||||
-rw-r--r-- | drivers/mailbox/hi6220-mailbox.c | 519 |
9 files changed, 799 insertions, 6 deletions
diff --git a/Documentation/devicetree/bindings/mailbox/hisilicon,hi6220-mailbox.txt b/Documentation/devicetree/bindings/mailbox/hisilicon,hi6220-mailbox.txt new file mode 100644 index 000000000000..3dfb0b0ecd33 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/hisilicon,hi6220-mailbox.txt @@ -0,0 +1,57 @@ +Hisilicon Hi6220 Mailbox Driver +=============================== + +Hisilicon Hi6220 mailbox supports up to 32 channels. Each channel +is unidirectional with a maximum message size of 8 words. I/O is +performed using register access (there is no DMA) and the cell +raises an interrupt when messages are received. + +Mailbox Device Node: +==================== + +Required properties: +-------------------- +- compatible: Shall be "hisilicon,hi6220-mbox" +- reg: Contains the mailbox register address range (base + address and length); the first item is for IPC + registers, the second item is shared buffer for + slots. +- #mbox-cells Common mailbox binding property to identify the number + of cells required for the mailbox specifier. Should be 1. +- interrupts: Contains the interrupt information for the mailbox + device. The format is dependent on which interrupt + controller the SoCs use. + +Example: +-------- + + mailbox: mailbox@F7510000 { + #mbox-cells = <1>; + compatible = "hisilicon,hi6220-mbox"; + reg = <0x0 0xF7510000 0x0 0x1000>, /* IPC_S */ + <0x0 0x06DFF800 0x0 0x0800>; /* Mailbox */ + interrupt-parent = <&gic>; + interrupts = <0 94 4>; + }; + + +Mailbox client +=============== + +"mboxes" and the optional "mbox-names" (please see +Documentation/devicetree/bindings/mailbox/mailbox.txt for details). Each value +of the mboxes property should contain a phandle to the mailbox controller +device node and second argument is the channel index. It must be 0 (hardware +support only one channel). The equivalent "mbox-names" property value can be +used to give a name to the communication channel to be used by the client user. + +Example: +-------- + + stub_clock: stub_clock { + compatible = "hisilicon,hi6220-stub-clk"; + hisilicon,hi6220-clk-sram = <&sram>; + #clock-cells = <1>; + mbox-names = "mbox-tx"; + mboxes = <&mailbox 1>; + }; diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 23800a19a7bc..6d730fbf00df 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -35,6 +35,7 @@ config ARCH_FSL_LS2085A config ARCH_HISI bool "Hisilicon SoC Family" + select ARM_TIMER_SP804 help This enables support for Hisilicon ARMv8 SoC family diff --git a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts index e36a539468a5..e3f4cb3f47cc 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts +++ b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts @@ -7,9 +7,6 @@ /dts-v1/; -/*Reserved 1MB memory for MCU*/ -/memreserve/ 0x05e00000 0x00100000; - #include "hi6220.dtsi" / { @@ -24,8 +21,19 @@ stdout-path = "serial0:115200n8"; }; + /* + * Reserve below regions from memory node: + * + * - 0x05e0,0000 - 0x05ef,ffff: MCU firmware runtime using + * - 0x06df,f000 - 0x06df,ffff: Mailbox message data + * - 0x0740,f000 - 0x0740,ffff: MCU firmware section + * - 0x3e00,0000 - 0x3fff,ffff: OP-TEE + */ memory@0 { device_type = "memory"; - reg = <0x0 0x0 0x0 0x40000000>; + reg = <0x00000000 0x00000000 0x00000000 0x05e00000>, + <0x00000000 0x05f00000 0x00000000 0x00eff000>, + <0x00000000 0x06e00000 0x00000000 0x0060f000>, + <0x00000000 0x07410000 0x00000000 0x36bf0000>; }; }; diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index 3f03380815b6..0b8a740660df 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -5,6 +5,7 @@ */ #include <dt-bindings/interrupt-controller/arm-gic.h> +#include <dt-bindings/thermal/thermal.h> / { compatible = "hisilicon,hi6220"; @@ -57,6 +58,18 @@ device_type = "cpu"; reg = <0x0 0x0>; enable-method = "psci"; + clocks = <&stub_clock 0>; + clock-latency = <0>; + operating-points = < + /* kHz */ + 1200000 0 + 960000 0 + 729000 0 + 432000 0 + 208000 0 + >; + #cooling-cells = <2>; /* min followed by max */ + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu1: cpu@1 { @@ -64,6 +77,7 @@ device_type = "cpu"; reg = <0x0 0x1>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu2: cpu@2 { @@ -71,6 +85,7 @@ device_type = "cpu"; reg = <0x0 0x2>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu3: cpu@3 { @@ -78,6 +93,7 @@ device_type = "cpu"; reg = <0x0 0x3>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu4: cpu@100 { @@ -85,6 +101,7 @@ device_type = "cpu"; reg = <0x0 0x100>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu5: cpu@101 { @@ -92,6 +109,7 @@ device_type = "cpu"; reg = <0x0 0x101>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu6: cpu@102 { @@ -99,6 +117,7 @@ device_type = "cpu"; reg = <0x0 0x102>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu7: cpu@103 { @@ -106,6 +125,30 @@ device_type = "cpu"; reg = <0x0 0x103>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; + }; + + idle-states { + entry-method = "arm,psci"; + + CPU_SLEEP_0_0: cpu-sleep-0-0 { + compatible = "arm,idle-state"; + local-timer-stop; + arm,psci-suspend-param = <0x0010000>; + entry-latency-us = <250>; + exit-latency-us = <500>; + min-residency-us = <950>; + }; + + CLUSTER_SLEEP_0: cluster-sleep-0 { + compatible = "arm,idle-state"; + local-timer-stop; + arm,psci-suspend-param = <0x1010000>; + entry-latency-us = <600>; + exit-latency-us = <1100>; + min-residency-us = <2700>; + wakeup-latency-us = <1500>; + }; }; }; @@ -136,6 +179,11 @@ #size-cells = <2>; ranges; + sram: sram@fff80000 { + compatible = "hisilicon,hi6220-sramctrl", "syscon"; + reg = <0x0 0xfff80000 0x0 0x12000>; + }; + ao_ctrl: ao_ctrl@f7800000 { compatible = "hisilicon,hi6220-aoctrl", "syscon"; reg = <0x0 0xf7800000 0x0 0x2000>; @@ -160,6 +208,22 @@ #clock-cells = <1>; }; + stub_clock: stub_clock { + compatible = "hisilicon,hi6220-stub-clk"; + hisilicon,hi6220-clk-sram = <&sram>; + #clock-cells = <1>; + mboxes = <&mailbox 1>; + }; + + dual_timer0: dual_timer@f8008000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x0 0xf8008000 0x0 0x1000>; + interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ao_ctrl 27>, <&ao_ctrl 27>; + clock-names = "apb_pclk", "apb_pclk"; + }; + uart0: uart@f8015000 { /* console */ compatible = "arm,pl011", "arm,primecell"; reg = <0x0 0xf8015000 0x0 0x1000>; @@ -167,5 +231,132 @@ clocks = <&ao_ctrl 36>, <&ao_ctrl 36>; clock-names = "uartclk", "apb_pclk"; }; + + mailbox: mailbox@f7510000 { + #mbox-cells = <1>; + compatible = "hisilicon,hi6220-mbox"; + reg = <0x0 0xf7510000 0x0 0x1000>, /* IPC_S */ + <0x0 0x06dff800 0x0 0x0800>; /* Mailbox buffer */ + interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>; + }; + + tsensor: tsensor@0,f7030700 { + compatible = "hisilicon,tsensor"; + reg = <0x0 0xf7030700 0x0 0x1000>; + interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&sys_ctrl 22>; + clock-names = "thermal_clk"; + #thermal-sensor-cells = <1>; + }; + + thermal-zones { + local: local { + polling-delay-passive = <1000>; /* milliseconds */ + polling-delay = <5000>; /* milliseconds */ + + /* sensor ID */ + thermal-sensors = <&tsensor 0>; + + trips { + local_alert: local_alert { + temperature = <70000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "passive"; + }; + + local_crit: local_crit { + temperature = <90000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "critical"; + }; + }; + + cooling-maps { + /* There are currently no cooling maps because there are no cooling devices */ + }; + }; + + cluster1: cluster1 { + polling-delay-passive = <1000>; /* milliseconds */ + polling-delay = <5000>; /* milliseconds */ + + /* sensor ID */ + thermal-sensors = <&tsensor 1>; + + trips { + cluster1_alert: cluster1_alert { + temperature = <70000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "passive"; + }; + + cluster1_crit: cluster1_crit { + temperature = <90000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "critical"; + }; + }; + + cooling-maps { + /* There are currently no cooling maps because there are no cooling devices */ + }; + }; + + cluster0: cluster0 { + polling-delay-passive = <1000>; /* milliseconds */ + polling-delay = <5000>; /* milliseconds */ + + /* sensor ID */ + thermal-sensors = <&tsensor 2>; + + trips { + cluster0_alert: cluster0_alert { + temperature = <70000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "passive"; + }; + + cluster0_crit: cluster0_crit { + temperature = <90000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cluster0_alert>; + cooling-device = + <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + + gpu: gpu { + polling-delay-passive = <1000>; /* milliseconds */ + polling-delay = <5000>; /* milliseconds */ + + /* sensor ID */ + thermal-sensors = <&tsensor 3>; + + trips { + gpu_alert: gpu_alert { + temperature = <70000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "passive"; + }; + + gpu_crit: gpu_crit { + temperature = <90000>; /* millicelsius */ + hysteresis = <2000>; /* millicelsius */ + type = "critical"; + }; + }; + + cooling-maps { + /* There are currently no cooling maps because there are no cooling devices */ + }; + }; + }; }; }; diff --git a/drivers/clk/hisilicon/Kconfig b/drivers/clk/hisilicon/Kconfig index 2c16807341dc..e43485448612 100644 --- a/drivers/clk/hisilicon/Kconfig +++ b/drivers/clk/hisilicon/Kconfig @@ -1,6 +1,12 @@ config COMMON_CLK_HI6220 bool "Hi6220 Clock Driver" - depends on (ARCH_HISI || COMPILE_TEST) && MAILBOX + depends on ARCH_HISI || COMPILE_TEST default ARCH_HISI help Build the Hisilicon Hi6220 clock driver based on the common clock framework. + +config STUB_CLK_HI6220 + bool "Hi6220 Stub Clock Driver" + depends on COMMON_CLK_HI6220 && MAILBOX + help + Build the Hisilicon Hi6220 stub clock driver. diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile index 4a1001a11f04..74dba31590f9 100644 --- a/drivers/clk/hisilicon/Makefile +++ b/drivers/clk/hisilicon/Makefile @@ -7,4 +7,5 @@ obj-y += clk.o clkgate-separated.o clkdivider-hi6220.o obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o obj-$(CONFIG_ARCH_HIX5HD2) += clk-hix5hd2.o -obj-$(CONFIG_COMMON_CLK_HI6220) += clk-hi6220.o clk-hi6220-stub.o +obj-$(CONFIG_COMMON_CLK_HI6220) += clk-hi6220.o +obj-$(CONFIG_STUB_CLK_HI6220) += clk-hi6220-stub.o diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index bbec5009cdc2..41fb7fac1337 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -71,4 +71,12 @@ config BCM2835_MBOX the services of the Videocore. Say Y here if you want to use the BCM2835 Mailbox. +config HI6220_MBOX + tristate "Hi6220 Mailbox" + depends on ARCH_HISI + help + An implementation of the hi6220 mailbox. It is used to send message + between application processors and MCU. Say Y here if you want to build + the Hi6220 mailbox controller driver. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 8e6d82218a09..4ba9f5fe5f7d 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -13,3 +13,5 @@ obj-$(CONFIG_PCC) += pcc.o obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o obj-$(CONFIG_BCM2835_MBOX) += bcm2835-mailbox.o + +obj-$(CONFIG_HI6220_MBOX) += hi6220-mailbox.o diff --git a/drivers/mailbox/hi6220-mailbox.c b/drivers/mailbox/hi6220-mailbox.c new file mode 100644 index 000000000000..8f63d0d42277 --- /dev/null +++ b/drivers/mailbox/hi6220-mailbox.c @@ -0,0 +1,519 @@ +/* + * Hisilicon's Hi6220 mailbox driver + * + * RX channel's message queue is based on the code written in + * drivers/mailbox/omap-mailbox.c. + * + * Copyright (c) 2015 Hisilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan <leo.yan@linaro.org> + * + * 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, version 2 of the License. + * + * 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/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kfifo.h> +#include <linux/mailbox_controller.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/slab.h> + +#define HI6220_MBOX_CHAN_MAX 32 +#define HI6220_MBOX_CHAN_NUM 2 +#define HI6220_MBOX_CHAN_SLOT_SIZE 64 + +#define HI6220_MBOX_RX 0x0 +#define HI6220_MBOX_TX 0x1 + +/* Mailbox message length: 32 bytes */ +#define HI6220_MBOX_MSG_LEN 32 + +/* Mailbox kfifo size */ +#define HI6220_MBOX_MSG_FIFO_SIZE 512 + +/* Status & Mode Register */ +#define HI6220_MBOX_MODE_REG 0x0 + +#define HI6220_MBOX_STATUS_MASK (0xF << 4) +#define HI6220_MBOX_STATUS_IDLE (0x1 << 4) +#define HI6220_MBOX_STATUS_TX (0x2 << 4) +#define HI6220_MBOX_STATUS_RX (0x4 << 4) +#define HI6220_MBOX_STATUS_ACK (0x8 << 4) +#define HI6220_MBOX_ACK_CONFIG_MASK (0x1 << 0) +#define HI6220_MBOX_ACK_AUTOMATIC (0x1 << 0) +#define HI6220_MBOX_ACK_IRQ (0x0 << 0) + +/* Data Registers */ +#define HI6220_MBOX_DATA_REG(i) (0x4 + (i << 2)) + +/* ACPU Interrupt Register */ +#define HI6220_MBOX_ACPU_INT_RAW_REG 0x400 +#define HI6220_MBOX_ACPU_INT_MSK_REG 0x404 +#define HI6220_MBOX_ACPU_INT_STAT_REG 0x408 +#define HI6220_MBOX_ACPU_INT_CLR_REG 0x40c +#define HI6220_MBOX_ACPU_INT_ENA_REG 0x500 +#define HI6220_MBOX_ACPU_INT_DIS_REG 0x504 + +/* MCU Interrupt Register */ +#define HI6220_MBOX_MCU_INT_RAW_REG 0x420 + +/* Core Id */ +#define HI6220_CORE_ACPU 0x0 +#define HI6220_CORE_MCU 0x2 + +struct hi6220_mbox_queue { + struct kfifo fifo; + struct work_struct work; + struct mbox_chan *chan; + bool full; +}; + +struct hi6220_mbox_chan { + + /* + * Description for channel's hardware info: + * - direction; + * - peer core id for communication; + * - local irq vector or number; + * - remoted irq vector or number for peer core; + */ + unsigned int dir; + unsigned int peer_core; + unsigned int remote_irq; + unsigned int local_irq; + + /* + * Slot address is cached value derived from index + * within buffer for every channel + */ + void __iomem *slot; + + /* For rx's fifo operations */ + struct hi6220_mbox_queue *mq; + + struct hi6220_mbox *parent; +}; + +struct hi6220_mbox { + struct device *dev; + + spinlock_t lock; + + int irq; + + /* flag of enabling tx's irq mode */ + bool tx_irq_mode; + + /* region for ipc event */ + void __iomem *ipc; + + /* region for share mem */ + void __iomem *buf; + + unsigned int chan_num; + struct hi6220_mbox_chan *mchan; + + void *irq_map_chan[HI6220_MBOX_CHAN_MAX]; + struct mbox_chan *chan; + struct mbox_controller controller; +}; + +static void hi6220_mbox_set_status(struct hi6220_mbox_chan *mchan, u32 val) +{ + u32 status; + + status = readl(mchan->slot + HI6220_MBOX_MODE_REG); + status &= ~HI6220_MBOX_STATUS_MASK; + status |= val; + writel(status, mchan->slot + HI6220_MBOX_MODE_REG); +} + +static void hi6220_mbox_set_mode(struct hi6220_mbox_chan *mchan, u32 val) +{ + u32 mode; + + mode = readl(mchan->slot + HI6220_MBOX_MODE_REG); + mode &= ~HI6220_MBOX_ACK_CONFIG_MASK; + mode |= val; + writel(mode, mchan->slot + HI6220_MBOX_MODE_REG); +} + +static bool hi6220_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + u32 status; + + /* Only set idle state for polling mode */ + BUG_ON(mbox->tx_irq_mode); + + status = readl(mchan->slot + HI6220_MBOX_MODE_REG); + status = status & HI6220_MBOX_STATUS_MASK; + return (status == HI6220_MBOX_STATUS_IDLE); +} + +static int hi6220_mbox_send_data(struct mbox_chan *chan, void *msg) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + int irq = mchan->remote_irq; + u32 *buf = msg; + unsigned long flags; + int i; + + hi6220_mbox_set_status(mchan, HI6220_MBOX_STATUS_TX); + + if (mbox->tx_irq_mode) + hi6220_mbox_set_mode(mchan, HI6220_MBOX_ACK_IRQ); + else + hi6220_mbox_set_mode(mchan, HI6220_MBOX_ACK_AUTOMATIC); + + for (i = 0; i < (HI6220_MBOX_MSG_LEN >> 2); i++) + writel(buf[i], mchan->slot + HI6220_MBOX_DATA_REG(i)); + + /* trigger remote request */ + spin_lock_irqsave(&mbox->lock, flags); + writel(1 << irq, mbox->ipc + HI6220_MBOX_MCU_INT_RAW_REG); + spin_unlock_irqrestore(&mbox->lock, flags); + return 0; +} + +static void hi6220_mbox_rx_work(struct work_struct *work) +{ + struct hi6220_mbox_queue *mq = + container_of(work, struct hi6220_mbox_queue, work); + struct mbox_chan *chan = mq->chan; + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + int irq = mchan->local_irq, len; + u32 msg[HI6220_MBOX_MSG_LEN >> 2]; + + while (kfifo_len(&mq->fifo) >= sizeof(msg)) { + len = kfifo_out(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); + WARN_ON(len != sizeof(msg)); + + mbox_chan_received_data(chan, (void *)msg); + spin_lock_irq(&mbox->lock); + if (mq->full) { + mq->full = false; + writel(1 << irq, + mbox->ipc + HI6220_MBOX_ACPU_INT_ENA_REG); + } + spin_unlock_irq(&mbox->lock); + } +} + +static void hi6220_mbox_tx_interrupt(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + int irq = mchan->local_irq; + + spin_lock(&mbox->lock); + writel(1 << irq, mbox->ipc + HI6220_MBOX_ACPU_INT_CLR_REG); + spin_unlock(&mbox->lock); + + hi6220_mbox_set_status(mchan, HI6220_MBOX_STATUS_IDLE); + mbox_chan_txdone(chan, 0); +} + +static void hi6220_mbox_rx_interrupt(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox_queue *mq = mchan->mq; + struct hi6220_mbox *mbox = mchan->parent; + int irq = mchan->local_irq; + int msg[HI6220_MBOX_MSG_LEN >> 2]; + int i, len; + + if (unlikely(kfifo_avail(&mq->fifo) < sizeof(msg))) { + spin_lock(&mbox->lock); + writel(1 << irq, mbox->ipc + HI6220_MBOX_ACPU_INT_DIS_REG); + mq->full = true; + spin_unlock(&mbox->lock); + goto nomem; + } + + for (i = 0; i < (HI6220_MBOX_MSG_LEN >> 2); i++) + msg[i] = readl(mchan->slot + HI6220_MBOX_DATA_REG(i)); + + /* clear IRQ source */ + spin_lock(&mbox->lock); + writel(1 << irq, mbox->ipc + HI6220_MBOX_ACPU_INT_CLR_REG); + spin_unlock(&mbox->lock); + + hi6220_mbox_set_status(mchan, HI6220_MBOX_STATUS_IDLE); + + len = kfifo_in(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); + WARN_ON(len != sizeof(msg)); + +nomem: + schedule_work(&mq->work); +} + +static irqreturn_t hi6220_mbox_interrupt(int irq, void *p) +{ + struct hi6220_mbox *mbox = p; + struct hi6220_mbox_chan *mchan; + struct mbox_chan *chan; + unsigned int state; + unsigned int intr_bit; + + state = readl(mbox->ipc + HI6220_MBOX_ACPU_INT_STAT_REG); + if (!state) { + dev_warn(mbox->dev, "%s: spurious interrupt\n", + __func__); + return IRQ_HANDLED; + } + + while (state) { + intr_bit = __ffs(state); + state &= (state - 1); + + chan = mbox->irq_map_chan[intr_bit]; + if (!chan) { + dev_warn(mbox->dev, "%s: unexpected irq vector %d\n", + __func__, intr_bit); + continue; + } + + mchan = chan->con_priv; + if (mchan->dir == HI6220_MBOX_TX) + hi6220_mbox_tx_interrupt(chan); + else + hi6220_mbox_rx_interrupt(chan); + } + + return IRQ_HANDLED; +} + +static struct hi6220_mbox_queue *hi6220_mbox_queue_alloc( + struct mbox_chan *chan, + void (*work)(struct work_struct *)) +{ + struct hi6220_mbox_queue *mq; + + mq = kzalloc(sizeof(struct hi6220_mbox_queue), GFP_KERNEL); + if (!mq) + return NULL; + + if (kfifo_alloc(&mq->fifo, HI6220_MBOX_MSG_FIFO_SIZE, GFP_KERNEL)) + goto error; + + mq->chan = chan; + INIT_WORK(&mq->work, work); + return mq; + +error: + kfree(mq); + return NULL; +} + +static void hi6220_mbox_queue_free(struct hi6220_mbox_queue *mq) +{ + kfifo_free(&mq->fifo); + kfree(mq); +} + +static int hi6220_mbox_startup(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + unsigned int irq = mchan->local_irq; + struct hi6220_mbox_queue *mq; + unsigned long flags; + + mq = hi6220_mbox_queue_alloc(chan, hi6220_mbox_rx_work); + if (!mq) + return -ENOMEM; + mchan->mq = mq; + mbox->irq_map_chan[irq] = (void *)chan; + + /* enable interrupt */ + spin_lock_irqsave(&mbox->lock, flags); + writel(1 << irq, mbox->ipc + HI6220_MBOX_ACPU_INT_ENA_REG); + spin_unlock_irqrestore(&mbox->lock, flags); + return 0; +} + +static void hi6220_mbox_shutdown(struct mbox_chan *chan) +{ + struct hi6220_mbox_chan *mchan = chan->con_priv; + struct hi6220_mbox *mbox = mchan->parent; + unsigned int irq = mchan->local_irq; + unsigned long flags; + + /* disable interrupt */ + spin_lock_irqsave(&mbox->lock, flags); + writel(1 << irq, mbox->ipc + HI6220_MBOX_ACPU_INT_DIS_REG); + spin_unlock_irqrestore(&mbox->lock, flags); + + mbox->irq_map_chan[irq] = NULL; + flush_work(&mchan->mq->work); + hi6220_mbox_queue_free(mchan->mq); +} + +static struct mbox_chan_ops hi6220_mbox_chan_ops = { + .send_data = hi6220_mbox_send_data, + .startup = hi6220_mbox_startup, + .shutdown = hi6220_mbox_shutdown, + .last_tx_done = hi6220_mbox_last_tx_done, +}; + +static void hi6220_mbox_init_hw(struct hi6220_mbox *mbox) +{ + struct hi6220_mbox_chan init_data[HI6220_MBOX_CHAN_NUM] = { + { HI6220_MBOX_RX, HI6220_CORE_MCU, 1, 10 }, + { HI6220_MBOX_TX, HI6220_CORE_MCU, 0, 11 }, + }; + struct hi6220_mbox_chan *mchan = mbox->mchan; + int i; + + for (i = 0; i < HI6220_MBOX_CHAN_NUM; i++) { + memcpy(&mchan[i], &init_data[i], sizeof(*mchan)); + mchan[i].slot = mbox->buf + HI6220_MBOX_CHAN_SLOT_SIZE * i; + mchan[i].parent = mbox; + } + + /* mask and clear all interrupt vectors */ + writel(0x0, mbox->ipc + HI6220_MBOX_ACPU_INT_MSK_REG); + writel(~0x0, mbox->ipc + HI6220_MBOX_ACPU_INT_CLR_REG); + + /* use interrupt for tx's ack */ + mbox->tx_irq_mode = true; +} + +static const struct of_device_id hi6220_mbox_of_match[] = { + { .compatible = "hisilicon,hi6220-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi6220_mbox_of_match); + +static int hi6220_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hi6220_mbox *mbox; + struct resource *res; + int i, err; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + mbox->dev = dev; + mbox->chan_num = HI6220_MBOX_CHAN_NUM; + mbox->mchan = devm_kzalloc(dev, + mbox->chan_num * sizeof(*mbox->mchan), GFP_KERNEL); + if (!mbox->mchan) + return -ENOMEM; + + mbox->chan = devm_kzalloc(dev, + mbox->chan_num * sizeof(*mbox->chan), GFP_KERNEL); + if (!mbox->chan) + return -ENOMEM; + + mbox->irq = platform_get_irq(pdev, 0); + if (mbox->irq < 0) + return mbox->irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mbox->ipc = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox->ipc)) { + dev_err(dev, "ioremap ipc failed\n"); + return PTR_ERR(mbox->ipc); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mbox->buf = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox->buf)) { + dev_err(dev, "ioremap buffer failed\n"); + return PTR_ERR(mbox->buf); + } + + err = devm_request_irq(dev, mbox->irq, hi6220_mbox_interrupt, 0, + dev_name(dev), mbox); + if (err) { + dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", + err); + return -ENODEV; + } + + /* init hardware parameters */ + hi6220_mbox_init_hw(mbox); + + spin_lock_init(&mbox->lock); + + for (i = 0; i < mbox->chan_num; i++) { + mbox->chan[i].con_priv = &mbox->mchan[i]; + mbox->irq_map_chan[i] = NULL; + } + + mbox->controller.dev = dev; + mbox->controller.chans = &mbox->chan[0]; + mbox->controller.num_chans = mbox->chan_num; + mbox->controller.ops = &hi6220_mbox_chan_ops; + + if (mbox->tx_irq_mode) + mbox->controller.txdone_irq = true; + else { + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + } + + err = mbox_controller_register(&mbox->controller); + if (err) { + dev_err(dev, "Failed to register mailbox %d\n", err); + return err; + } + + platform_set_drvdata(pdev, mbox); + dev_info(dev, "Mailbox enabled\n"); + return 0; +} + +static int hi6220_mbox_remove(struct platform_device *pdev) +{ + struct hi6220_mbox *mbox = platform_get_drvdata(pdev); + + mbox_controller_unregister(&mbox->controller); + return 0; +} + +static struct platform_driver hi6220_mbox_driver = { + .driver = { + .name = "hi6220-mbox", + .owner = THIS_MODULE, + .of_match_table = hi6220_mbox_of_match, + }, + .probe = hi6220_mbox_probe, + .remove = hi6220_mbox_remove, +}; + +static int __init hi6220_mbox_init(void) +{ + return platform_driver_register(&hi6220_mbox_driver); +} +core_initcall(hi6220_mbox_init); + +static void __exit hi6220_mbox_exit(void) +{ + platform_driver_unregister(&hi6220_mbox_driver); +} +module_exit(hi6220_mbox_exit); + +MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>"); +MODULE_DESCRIPTION("Hi6220 mailbox driver"); +MODULE_LICENSE("GPL v2"); |