mirror of
https://github.com/armbian/build
synced 2025-09-24 19:47:06 +07:00
787 lines
20 KiB
Diff
787 lines
20 KiB
Diff
From 13498feb91614d59ebece61d0c278e31529bb8c8 Mon Sep 17 00:00:00 2001
|
|
From: Paolo Sabatino <paolo.sabatino@gmail.com>
|
|
Date: Tue, 10 Oct 2023 21:54:51 +0200
|
|
Subject: [PATCH] rockchip gpio IR driver
|
|
|
|
---
|
|
drivers/media/rc/Kconfig | 11 +
|
|
drivers/media/rc/Makefile | 1 +
|
|
drivers/media/rc/rockchip-ir.c | 723 +++++++++++++++++++++++++++++++++
|
|
3 files changed, 735 insertions(+)
|
|
create mode 100644 drivers/media/rc/rockchip-ir.c
|
|
|
|
diff --git a/drivers/media/rc/Kconfig b/drivers/media/rc/Kconfig
|
|
index 2afe67ffa285..0fd671f5873c 100644
|
|
--- a/drivers/media/rc/Kconfig
|
|
+++ b/drivers/media/rc/Kconfig
|
|
@@ -338,6 +338,16 @@ config IR_REDRAT3
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called redrat3.
|
|
|
|
+config IR_ROCKCHIP_CIR
|
|
+ tristate "Rockchip GPIO IR receiver"
|
|
+ depends on (OF && GPIOLIB) || COMPILE_TEST
|
|
+ help
|
|
+ Say Y here if you want to use the Rockchip IR receiver with
|
|
+ virtual poweroff features provided by rockchip Trust OS
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called rockchip-ir
|
|
+
|
|
config IR_SERIAL
|
|
tristate "Homebrew Serial Port Receiver"
|
|
depends on HAS_IOPORT
|
|
diff --git a/drivers/media/rc/Makefile b/drivers/media/rc/Makefile
|
|
index 2bca6f7f07bc..2ec037f8b939 100644
|
|
--- a/drivers/media/rc/Makefile
|
|
+++ b/drivers/media/rc/Makefile
|
|
@@ -43,6 +43,7 @@ obj-$(CONFIG_IR_MTK) += mtk-cir.o
|
|
obj-$(CONFIG_IR_NUVOTON) += nuvoton-cir.o
|
|
obj-$(CONFIG_IR_PWM_TX) += pwm-ir-tx.o
|
|
obj-$(CONFIG_IR_REDRAT3) += redrat3.o
|
|
+obj-$(CONFIG_IR_ROCKCHIP_CIR) += rockchip-ir.o
|
|
obj-$(CONFIG_IR_SERIAL) += serial_ir.o
|
|
obj-$(CONFIG_IR_SPI) += ir-spi.o
|
|
obj-$(CONFIG_IR_STREAMZAP) += streamzap.o
|
|
diff --git a/drivers/media/rc/rockchip-ir.c b/drivers/media/rc/rockchip-ir.c
|
|
new file mode 100644
|
|
index 000000000000..43ade8c4adce
|
|
--- /dev/null
|
|
+++ b/drivers/media/rc/rockchip-ir.c
|
|
@@ -0,0 +1,733 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
|
+*/
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
+#include <linux/pm_qos.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/arm-smccc.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reboot.h>
|
|
+#include <uapi/linux/psci.h>
|
|
+#include <media/rc-core.h>
|
|
+#include <soc/rockchip/rockchip_sip.h>
|
|
+
|
|
+#define ROCKCHIP_IR_DEVICE_NAME "rockchip_ir_recv"
|
|
+
|
|
+#ifdef CONFIG_64BIT
|
|
+#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name
|
|
+#else
|
|
+#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name
|
|
+#endif
|
|
+
|
|
+/*
|
|
+* SIP/TEE constants for remote calls
|
|
+*/
|
|
+#define SIP_REMOTECTL_CFG 0x8200000b
|
|
+#define SIP_SUSPEND_MODE 0x82000003
|
|
+#define SIP_REMOTECTL_CFG 0x8200000b
|
|
+#define SUSPEND_MODE_CONFIG 0x01
|
|
+#define WKUP_SOURCE_CONFIG 0x02
|
|
+#define PWM_REGULATOR_CONFIG 0x03
|
|
+#define GPIO_POWER_CONFIG 0x04
|
|
+#define SUSPEND_DEBUG_ENABLE 0x05
|
|
+#define APIOS_SUSPEND_CONFIG 0x06
|
|
+#define VIRTUAL_POWEROFF 0x07
|
|
+
|
|
+#define REMOTECTL_SET_IRQ 0xf0
|
|
+#define REMOTECTL_SET_PWM_CH 0xf1
|
|
+#define REMOTECTL_SET_PWRKEY 0xf2
|
|
+#define REMOTECTL_GET_WAKEUP_STATE 0xf3
|
|
+#define REMOTECTL_ENABLE 0xf4
|
|
+#define REMOTECTL_PWRKEY_WAKEUP 0xdeadbeaf /* wakeup state */
|
|
+
|
|
+/*
|
|
+* PWM Registers
|
|
+* Each PWM has its own control registers
|
|
+*/
|
|
+#define PWM_REG_CNTR 0x00 /* Counter Register */
|
|
+#define PWM_REG_HPR 0x04 /* Period Register */
|
|
+#define PWM_REG_LPR 0x08 /* Duty Cycle Register */
|
|
+#define PWM_REG_CTRL 0x0c /* Control Register */
|
|
+
|
|
+/*
|
|
+* PWM General registers
|
|
+* Registers shared among PWMs
|
|
+*/
|
|
+#define PWM_REG_INT_EN 0x44
|
|
+
|
|
+/*REG_CTRL bits definitions*/
|
|
+#define PWM_ENABLE (1 << 0)
|
|
+#define PWM_DISABLE (0 << 0)
|
|
+
|
|
+/*operation mode*/
|
|
+#define PWM_MODE_ONESHOT (0x00 << 1)
|
|
+#define PWM_MODE_CONTINUMOUS (0x01 << 1)
|
|
+#define PWM_MODE_CAPTURE (0x02 << 1)
|
|
+
|
|
+/* Channel interrupt enable bit */
|
|
+#define PWM_CH_INT_ENABLE(n) BIT(n)
|
|
+
|
|
+enum pwm_div {
|
|
+ PWM_DIV1 = (0x0 << 12),
|
|
+ PWM_DIV2 = (0x1 << 12),
|
|
+ PWM_DIV4 = (0x2 << 12),
|
|
+ PWM_DIV8 = (0x3 << 12),
|
|
+ PWM_DIV16 = (0x4 << 12),
|
|
+ PWM_DIV32 = (0x5 << 12),
|
|
+ PWM_DIV64 = (0x6 << 12),
|
|
+ PWM_DIV128 = (0x7 << 12),
|
|
+};
|
|
+
|
|
+#define PWM_INT_ENABLE 1
|
|
+#define PWM_INT_DISABLE 0
|
|
+
|
|
+struct rockchip_rc_dev {
|
|
+ struct rc_dev *rcdev;
|
|
+ struct gpio_desc *gpiod;
|
|
+ int irq;
|
|
+ struct device *pmdev;
|
|
+ struct pm_qos_request qos;
|
|
+ void __iomem *pwm_base;
|
|
+ int pwm_wake_irq;
|
|
+ int pwm_id;
|
|
+ bool use_shutdown_handler; // if true, installs a shutdown handler and triggers virtual poweroff
|
|
+ bool use_suspend_handler; // if true, virtual poweroff is used as suspend mode otherwise use as regular suspend
|
|
+ struct pinctrl *pinctrl;
|
|
+ struct pinctrl_state *pinctrl_state_default;
|
|
+ struct pinctrl_state *pinctrl_state_suspend;
|
|
+};
|
|
+
|
|
+static struct arm_smccc_res __invoke_sip_fn_smc(unsigned long function_id,
|
|
+ unsigned long arg0,
|
|
+ unsigned long arg1,
|
|
+ unsigned long arg2)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+int sip_smc_remotectl_config(u32 func, u32 data)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ res = __invoke_sip_fn_smc(SIP_REMOTECTL_CFG, func, data, 0);
|
|
+
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, ctrl, config1, config2);
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+int sip_smc_virtual_poweroff(void)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ res = __invoke_sip_fn_smc(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), 0, 0, 0);
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+static irqreturn_t rockchip_ir_recv_irq(int irq, void *dev_id)
|
|
+{
|
|
+ int val;
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_id;
|
|
+ struct device *pmdev = gpio_dev->pmdev;
|
|
+
|
|
+ /*
|
|
+ * For some cpuidle systems, not all:
|
|
+ * Respond to interrupt taking more latency when cpu in idle.
|
|
+ * Invoke asynchronous pm runtime get from interrupt context,
|
|
+ * this may introduce a millisecond delay to call resume callback,
|
|
+ * where to disable cpuilde.
|
|
+ *
|
|
+ * Two issues lead to fail to decode first frame, one is latency to
|
|
+ * respond to interrupt, another is delay introduced by async api.
|
|
+ */
|
|
+ if (pmdev)
|
|
+ pm_runtime_get(pmdev);
|
|
+
|
|
+ val = gpiod_get_value(gpio_dev->gpiod);
|
|
+ if (val >= 0)
|
|
+ ir_raw_event_store_edge(gpio_dev->rcdev, val == 1);
|
|
+
|
|
+ if (pmdev) {
|
|
+ pm_runtime_mark_last_busy(pmdev);
|
|
+ pm_runtime_put_autosuspend(pmdev);
|
|
+ }
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void rockchip_pwm_int_ctrl(struct rockchip_rc_dev *gpio_dev, bool enable)
|
|
+{
|
|
+
|
|
+ void __iomem *pwm_base = gpio_dev->pwm_base;
|
|
+ struct device *dev = &gpio_dev->rcdev->dev;
|
|
+ int pwm_id = gpio_dev->pwm_id;
|
|
+
|
|
+ void __iomem *reg_int_ctrl;
|
|
+ int val;
|
|
+
|
|
+ reg_int_ctrl= pwm_base - (0x10 * pwm_id) + PWM_REG_INT_EN;
|
|
+
|
|
+ val = readl_relaxed(reg_int_ctrl);
|
|
+
|
|
+ if (enable) {
|
|
+ val |= PWM_CH_INT_ENABLE(pwm_id);
|
|
+ dev_info(dev, "PWM interrupt enabled, register value %x\n", val);
|
|
+ } else {
|
|
+ val &= ~PWM_CH_INT_ENABLE(pwm_id);
|
|
+ dev_info(dev, "PWM interrupt disabled, register value %x\n", val);
|
|
+ }
|
|
+
|
|
+ writel_relaxed(val, reg_int_ctrl);
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_pwm_hw_init(struct rockchip_rc_dev *gpio_dev)
|
|
+{
|
|
+
|
|
+ void __iomem *pwm_base = gpio_dev->pwm_base;
|
|
+ int val;
|
|
+
|
|
+ //1. disabled pwm
|
|
+ val = readl_relaxed(pwm_base + PWM_REG_CTRL);
|
|
+ val = (val & 0xFFFFFFFE) | PWM_DISABLE;
|
|
+ writel_relaxed(val, pwm_base + PWM_REG_CTRL);
|
|
+
|
|
+ //2. capture mode
|
|
+ val = readl_relaxed(pwm_base + PWM_REG_CTRL);
|
|
+ val = (val & 0xFFFFFFF9) | PWM_MODE_CAPTURE;
|
|
+ writel_relaxed(val, pwm_base + PWM_REG_CTRL);
|
|
+
|
|
+ //set clk div, clk div to 64
|
|
+ val = readl_relaxed(pwm_base + PWM_REG_CTRL);
|
|
+ val = (val & 0xFF0001FF) | PWM_DIV64;
|
|
+ writel_relaxed(val, pwm_base + PWM_REG_CTRL);
|
|
+
|
|
+ //4. enabled pwm int
|
|
+ rockchip_pwm_int_ctrl(gpio_dev, true);
|
|
+
|
|
+ //5. enabled pwm
|
|
+ val = readl_relaxed(pwm_base + PWM_REG_CTRL);
|
|
+ val = (val & 0xFFFFFFFE) | PWM_ENABLE;
|
|
+ writel_relaxed(val, pwm_base + PWM_REG_CTRL);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_pwm_hw_stop(struct rockchip_rc_dev *gpio_dev)
|
|
+{
|
|
+
|
|
+ void __iomem *pwm_base = gpio_dev->pwm_base;
|
|
+ int val;
|
|
+
|
|
+ //disable pwm interrupt
|
|
+ rockchip_pwm_int_ctrl(gpio_dev, false);
|
|
+
|
|
+ //disable pwm
|
|
+ val = readl_relaxed(pwm_base + PWM_REG_CTRL);
|
|
+ val = (val & 0xFFFFFFFE) | PWM_DISABLE;
|
|
+ writel_relaxed(val, pwm_base + PWM_REG_CTRL);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_pwm_sip_wakeup_init(struct rockchip_rc_dev *gpio_dev)
|
|
+{
|
|
+
|
|
+ struct device *dev = &gpio_dev->rcdev->dev;
|
|
+
|
|
+ struct irq_data *irq_data;
|
|
+ long hwirq;
|
|
+ int ret;
|
|
+
|
|
+ irq_data = irq_get_irq_data(gpio_dev->pwm_wake_irq);
|
|
+ if (!irq_data) {
|
|
+ dev_err(dev, "could not get irq data\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ hwirq = irq_data->hwirq;
|
|
+ dev_info(dev, "use hwirq %ld, pwm chip id %d for PWM SIP wakeup\n", hwirq, gpio_dev->pwm_id);
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+ ret |= sip_smc_remotectl_config(REMOTECTL_SET_IRQ, (int)hwirq);
|
|
+ ret |= sip_smc_remotectl_config(REMOTECTL_SET_PWM_CH, gpio_dev->pwm_id);
|
|
+ ret |= sip_smc_remotectl_config(REMOTECTL_ENABLE, 1);
|
|
+
|
|
+ if (ret) {
|
|
+ dev_err(dev, "SIP remote controller mode, TEE does not support feature\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ sip_smc_set_suspend_mode(SUSPEND_MODE_CONFIG, 0x10042, 0);
|
|
+ sip_smc_set_suspend_mode(WKUP_SOURCE_CONFIG, 0x0, 0);
|
|
+ sip_smc_set_suspend_mode(PWM_REGULATOR_CONFIG, 0x0, 0);
|
|
+ //sip_smc_set_suspend_mode(GPIO_POWER_CONFIG, i, gpio_temp[i]);
|
|
+ sip_smc_set_suspend_mode(SUSPEND_DEBUG_ENABLE, 0x1, 0);
|
|
+ sip_smc_set_suspend_mode(APIOS_SUSPEND_CONFIG, 0x0, 0);
|
|
+ sip_smc_set_suspend_mode(VIRTUAL_POWEROFF, 0, 1);
|
|
+
|
|
+ dev_info(dev, "TEE remote controller wakeup installed\n");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct rockchip_rc_dev *gpio_dev = platform_get_drvdata(pdev);
|
|
+ struct device *pmdev = gpio_dev->pmdev;
|
|
+
|
|
+ if (pmdev) {
|
|
+ pm_runtime_get_sync(pmdev);
|
|
+ cpu_latency_qos_remove_request(&gpio_dev->qos);
|
|
+
|
|
+ pm_runtime_disable(pmdev);
|
|
+ pm_runtime_put_noidle(pmdev);
|
|
+ pm_runtime_set_suspended(pmdev);
|
|
+ }
|
|
+
|
|
+ // Disable the remote controller handling of the Trust OS
|
|
+ sip_smc_remotectl_config(REMOTECTL_ENABLE, 0);
|
|
+
|
|
+ // Disable the virtual poweroff of the Trust OS
|
|
+ sip_smc_set_suspend_mode(VIRTUAL_POWEROFF, 0, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rockchip_ir_register_power_key(struct device *dev)
|
|
+{
|
|
+
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ struct rc_map *key_map;
|
|
+ struct rc_map_table *key;
|
|
+ int idx, key_scancode, rev_scancode;
|
|
+ int tee_scancode;
|
|
+
|
|
+ key_map = &gpio_dev->rcdev->rc_map;
|
|
+
|
|
+ dev_info(dev, "remote key table %s, key map of %d items\n", key_map->name, key_map->len);
|
|
+
|
|
+ for (idx = 0; idx < key_map->len; idx++) {
|
|
+
|
|
+ key = &key_map->scan[idx];
|
|
+
|
|
+ if (key->keycode != KEY_POWER)
|
|
+ continue;
|
|
+
|
|
+ key_scancode = key->scancode;
|
|
+ rev_scancode = ~key_scancode;
|
|
+
|
|
+ // If key_scancode has higher 16 bits set to 0, then the scancode is NEC protocol, otherwise it is NECX/NEC32
|
|
+ if ((key_scancode & 0xffff) == key_scancode)
|
|
+ tee_scancode = (key_scancode & 0xff00) | ((rev_scancode & 0xff00) << 8); // NEC protocol
|
|
+ else
|
|
+ tee_scancode = ((key_scancode & 0xff0000) >> 8) | ((key_scancode & 0xff00) << 8); // NECX/NEC32 protocol
|
|
+
|
|
+ tee_scancode |= rev_scancode & 0xff;
|
|
+ tee_scancode <<= 8;
|
|
+
|
|
+ sip_smc_remotectl_config(REMOTECTL_SET_PWRKEY, tee_scancode);
|
|
+
|
|
+ dev_info(dev, "registered scancode %08x (SIP: %8x)\n", key_scancode, tee_scancode);
|
|
+
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_suspend_prepare(struct device *dev)
|
|
+{
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ dev_info(dev, "initialize rockchip SIP virtual poweroff\n");
|
|
+ ret = rockchip_pwm_sip_wakeup_init(gpio_dev);
|
|
+
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ rockchip_ir_register_power_key(dev);
|
|
+
|
|
+ disable_irq(gpio_dev->irq);
|
|
+ dev_info(dev, "GPIO IRQ disabled\n");
|
|
+
|
|
+ ret = pinctrl_select_state(gpio_dev->pinctrl, gpio_dev->pinctrl_state_suspend);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "unable to set pin in PWM mode\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_info(dev, "set pin configuration to PWM mode\n");
|
|
+
|
|
+ rockchip_pwm_hw_init(gpio_dev);
|
|
+ dev_info(dev, "started pin PWM mode\n");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int rockchip_ir_recv_suspend(struct device *dev)
|
|
+{
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ /*
|
|
+ * if property suspend-is-virtual-poweroff is set, we can disable
|
|
+ * the regular gpio wakeup and enable the PWM mode for the Trust OS
|
|
+ * to take control and react to remote control.
|
|
+ * If the property is not set, we instead enable the wake up for the
|
|
+ * regular gpio.
|
|
+ */
|
|
+ if (gpio_dev->use_suspend_handler) {
|
|
+
|
|
+ rockchip_ir_recv_suspend_prepare(dev);
|
|
+
|
|
+ } else {
|
|
+
|
|
+ if (device_may_wakeup(dev))
|
|
+ enable_irq_wake(gpio_dev->irq);
|
|
+ else
|
|
+ disable_irq(gpio_dev->irq);
|
|
+
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_resume(struct device *dev)
|
|
+{
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * In case suspend-is-virtual-poweroff property is set,
|
|
+ * restore the pin from PWM mode to regular GPIO configuration
|
|
+ * and stop the PWM function.
|
|
+ * Otherwise, just enable the regular GPIO irq
|
|
+ */
|
|
+ if (gpio_dev->use_suspend_handler) {
|
|
+
|
|
+ rockchip_pwm_hw_stop(gpio_dev);
|
|
+ dev_info(dev, "stopped pin PWM mode\n");
|
|
+
|
|
+ ret = pinctrl_select_state(gpio_dev->pinctrl, gpio_dev->pinctrl_state_default);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "unable to restore pin in GPIO mode\n");
|
|
+ return ret;
|
|
+ }
|
|
+ dev_info(dev, "restored pin configuration di GPIO\n");
|
|
+
|
|
+ enable_irq(gpio_dev->irq);
|
|
+ dev_info(dev, "restored GPIO IRQ\n");
|
|
+
|
|
+ } else {
|
|
+
|
|
+ if (device_may_wakeup(dev))
|
|
+ disable_irq_wake(gpio_dev->irq);
|
|
+ else
|
|
+ enable_irq(gpio_dev->irq);
|
|
+
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void rockchip_ir_recv_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct rockchip_rc_dev *gpio_dev = dev_get_drvdata(dev);
|
|
+
|
|
+ if (gpio_dev->use_shutdown_handler)
|
|
+ rockchip_ir_recv_suspend_prepare(dev);
|
|
+
|
|
+ return;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_sys_off(struct sys_off_data *data)
|
|
+{
|
|
+
|
|
+ sip_smc_virtual_poweroff();
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_init_sip(void)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_SIP_VERSION, ROCKCHIP_SIP_IMPLEMENT_V2, SECURE_REG_WR, 0, 0, 0, 0, 0, &res);
|
|
+
|
|
+ if (res.a0)
|
|
+ return 0;
|
|
+
|
|
+ return res.a1;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ir_recv_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = dev->of_node;
|
|
+ struct rockchip_rc_dev *gpio_dev;
|
|
+ struct rc_dev *rcdev;
|
|
+ struct clk *clk;
|
|
+ struct clk *p_clk;
|
|
+ struct resource *res;
|
|
+ u32 period = 0;
|
|
+ int rc;
|
|
+ int ret;
|
|
+ int pwm_wake_irq;
|
|
+ int clocks;
|
|
+
|
|
+ if (!np)
|
|
+ return -ENODEV;
|
|
+
|
|
+ gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev), GFP_KERNEL);
|
|
+ if (!gpio_dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ gpio_dev->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN);
|
|
+ if (IS_ERR(gpio_dev->gpiod)) {
|
|
+ rc = PTR_ERR(gpio_dev->gpiod);
|
|
+ /* Just try again if this happens */
|
|
+ if (rc != -EPROBE_DEFER)
|
|
+ dev_err(dev, "error getting gpio (%d)\n", rc);
|
|
+ return rc;
|
|
+ }
|
|
+ gpio_dev->irq = gpiod_to_irq(gpio_dev->gpiod);
|
|
+ if (gpio_dev->irq < 0)
|
|
+ return gpio_dev->irq;
|
|
+
|
|
+ rcdev = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW);
|
|
+ if (!rcdev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ rcdev->priv = gpio_dev;
|
|
+ rcdev->device_name = ROCKCHIP_IR_DEVICE_NAME;
|
|
+ rcdev->input_phys = ROCKCHIP_IR_DEVICE_NAME "/input0";
|
|
+ rcdev->input_id.bustype = BUS_HOST;
|
|
+ rcdev->input_id.vendor = 0x0001;
|
|
+ rcdev->input_id.product = 0x0001;
|
|
+ rcdev->input_id.version = 0x0100;
|
|
+ rcdev->dev.parent = dev;
|
|
+ rcdev->driver_name = KBUILD_MODNAME;
|
|
+ rcdev->min_timeout = 1;
|
|
+ rcdev->timeout = IR_DEFAULT_TIMEOUT;
|
|
+ rcdev->max_timeout = 10 * IR_DEFAULT_TIMEOUT;
|
|
+ rcdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
|
|
+ rcdev->map_name = of_get_property(np, "linux,rc-map-name", NULL);
|
|
+ if (!rcdev->map_name)
|
|
+ rcdev->map_name = RC_MAP_EMPTY;
|
|
+
|
|
+ gpio_dev->rcdev = rcdev;
|
|
+ if (of_property_read_bool(np, "wakeup-source")) {
|
|
+
|
|
+ ret = device_init_wakeup(dev, true);
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(dev, "could not init wakeup device\n");
|
|
+
|
|
+ }
|
|
+
|
|
+ rc = devm_rc_register_device(dev, rcdev);
|
|
+ if (rc < 0) {
|
|
+ dev_err(dev, "failed to register rc device (%d)\n", rc);
|
|
+ return rc;
|
|
+ }
|
|
+
|
|
+ of_property_read_u32(np, "linux,autosuspend-period", &period);
|
|
+ if (period) {
|
|
+ gpio_dev->pmdev = dev;
|
|
+ pm_runtime_set_autosuspend_delay(dev, period);
|
|
+ pm_runtime_use_autosuspend(dev);
|
|
+ pm_runtime_set_suspended(dev);
|
|
+ pm_runtime_enable(dev);
|
|
+ }
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res) {
|
|
+ dev_err(dev, "no memory resources defined\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ gpio_dev->pwm_base = devm_ioremap_resource(dev, res);
|
|
+ if (IS_ERR(gpio_dev->pwm_base))
|
|
+ return PTR_ERR(gpio_dev->pwm_base);
|
|
+
|
|
+ clocks = of_property_count_strings(np, "clock-names");
|
|
+ if (clocks == 2) {
|
|
+ clk = devm_clk_get(dev, "pwm");
|
|
+ p_clk = devm_clk_get(dev, "pclk");
|
|
+ } else {
|
|
+ clk = devm_clk_get(dev, NULL);
|
|
+ p_clk = clk;
|
|
+ }
|
|
+
|
|
+ if (IS_ERR(clk)) {
|
|
+ ret = PTR_ERR(clk);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(dev, "Can't get bus clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (IS_ERR(p_clk)) {
|
|
+ ret = PTR_ERR(p_clk);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(dev, "Can't get peripheral clock: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(clk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Can't enable bus clk: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(p_clk);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Can't enable peripheral clk: %d\n", ret);
|
|
+ goto error_clk;
|
|
+ }
|
|
+
|
|
+ pwm_wake_irq = platform_get_irq(pdev, 0);
|
|
+ if (pwm_wake_irq < 0) {
|
|
+ dev_err(&pdev->dev, "cannot find PWM wake interrupt\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ gpio_dev->pwm_wake_irq = pwm_wake_irq;
|
|
+ ret = enable_irq_wake(pwm_wake_irq);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "could not enable IRQ wakeup\n");
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(np, "pwm-id", &gpio_dev->pwm_id);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "missing pwm-id property\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ if (gpio_dev->pwm_id > 3) {
|
|
+ dev_err(dev, "invalid pwm-id property\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ gpio_dev->use_shutdown_handler = of_property_read_bool(np, "shutdown-is-virtual-poweroff");
|
|
+ gpio_dev->use_suspend_handler = of_property_read_bool(np, "suspend-is-virtual-poweroff");
|
|
+
|
|
+ gpio_dev->pinctrl = devm_pinctrl_get(dev);
|
|
+ if (IS_ERR(gpio_dev->pinctrl)) {
|
|
+ dev_err(dev, "Unable to get pinctrl\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ gpio_dev->pinctrl_state_default = pinctrl_lookup_state(gpio_dev->pinctrl, "default");
|
|
+ if (IS_ERR(gpio_dev->pinctrl_state_default)) {
|
|
+ dev_err(dev, "Unable to get default pinctrl state\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ gpio_dev->pinctrl_state_suspend = pinctrl_lookup_state(gpio_dev->pinctrl, "suspend");
|
|
+ if (IS_ERR(gpio_dev->pinctrl_state_suspend)) {
|
|
+ dev_err(dev, "Unable to get suspend pinctrl state\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, gpio_dev);
|
|
+
|
|
+ ret = devm_request_irq(dev, gpio_dev->irq, rockchip_ir_recv_irq,
|
|
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
|
|
+ "gpio-ir-recv-irq", gpio_dev);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "Can't request GPIO interrupt\n");
|
|
+ goto error_pclk;
|
|
+ }
|
|
+
|
|
+ if (gpio_dev->use_shutdown_handler) {
|
|
+
|
|
+ ret = devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
|
|
+ SYS_OFF_PRIO_FIRMWARE, rockchip_ir_recv_sys_off, NULL);
|
|
+
|
|
+ if (ret)
|
|
+ dev_err(dev, "could not register sys_off handler\n");
|
|
+
|
|
+ }
|
|
+
|
|
+ ret = rockchip_ir_recv_init_sip();
|
|
+ if (!ret) {
|
|
+ dev_err(dev, "Unable to initialize Rockchip SIP v2, virtual poweroff unavailable\n");
|
|
+ gpio_dev->use_shutdown_handler = false;
|
|
+ gpio_dev->use_suspend_handler = false;
|
|
+ } else {
|
|
+ dev_info(dev, "rockchip SIP initialized, version 0x%x\n", ret);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_pclk:
|
|
+ clk_unprepare(p_clk);
|
|
+error_clk:
|
|
+ clk_unprepare(clk);
|
|
+
|
|
+ return -ENODEV;
|
|
+
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops rockchip_ir_recv_pm_ops = {
|
|
+ .suspend = rockchip_ir_recv_suspend,
|
|
+ .resume = rockchip_ir_recv_resume,
|
|
+};
|
|
+#endif
|
|
+
|
|
+static const struct of_device_id rockchip_ir_recv_of_match[] = {
|
|
+ { .compatible = "rockchip-ir-receiver", },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, rockchip_ir_recv_of_match);
|
|
+
|
|
+static struct platform_driver rockchip_ir_recv_driver = {
|
|
+ .probe = rockchip_ir_recv_probe,
|
|
+ .remove = rockchip_ir_recv_remove,
|
|
+ .shutdown = rockchip_ir_recv_shutdown,
|
|
+ .driver = {
|
|
+ .name = KBUILD_MODNAME,
|
|
+ .of_match_table = of_match_ptr(rockchip_ir_recv_of_match),
|
|
+#ifdef CONFIG_PM
|
|
+ .pm = &rockchip_ir_recv_pm_ops,
|
|
+#endif
|
|
+ },
|
|
+};
|
|
+module_platform_driver(rockchip_ir_recv_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Rockchip IR Receiver driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
|
|
|