mirror of https://github.com/OpenIPC/firmware.git
699 lines
19 KiB
Diff
699 lines
19 KiB
Diff
diff -drupN a/drivers/input/keyboard/sunxi-ir-tx.c b/drivers/input/keyboard/sunxi-ir-tx.c
|
|
--- a/drivers/input/keyboard/sunxi-ir-tx.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ b/drivers/input/keyboard/sunxi-ir-tx.c 2022-06-12 05:28:14.000000000 +0300
|
|
@@ -0,0 +1,694 @@
|
|
+/*
|
|
+ * drivers/input/keyboard/sunxi-ir-tx.c
|
|
+ *
|
|
+ * Copyright (c) 2013-2018 Allwinnertech Co., Ltd.
|
|
+ *
|
|
+ * This software is licensed under the terms of the GNU General Public
|
|
+ * License version 2, as published by the Free Software Foundation, and
|
|
+ * may be copied, distributed, and modified under those terms.
|
|
+ *
|
|
+ * 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/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/cdev.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/of_irq.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/pm.h>
|
|
+#include <asm/io.h>
|
|
+#include "sunxi-ir-tx.h"
|
|
+
|
|
+static u32 debug_mask = 1;
|
|
+#define dprintk(level_mask, fmt, arg...) \
|
|
+do { \
|
|
+ if (unlikely(debug_mask & level_mask)) \
|
|
+ pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg); \
|
|
+} while (0)
|
|
+
|
|
+#define IRTX_ERR(fmt, arg...) pr_err("%s()%d - "fmt, __func__, __LINE__, ##arg)
|
|
+
|
|
+struct ir_tx_dev {
|
|
+ struct device *dev;
|
|
+ struct cdev cdev;
|
|
+ dev_t chrdev;
|
|
+};
|
|
+
|
|
+struct ir_raw_buffer {
|
|
+ unsigned long tx_dcnt;
|
|
+ unsigned char tx_buf[IR_TX_RAW_BUF_SIZE];
|
|
+};
|
|
+
|
|
+struct sunxi_ir_tx_data {
|
|
+ void __iomem *reg_base;
|
|
+ struct platform_device *pdev;
|
|
+ struct clk *mclk;
|
|
+ struct clk *pclk;
|
|
+ struct regulator *suply;
|
|
+ struct pinctrl *pctrl;
|
|
+ u32 suply_vol;
|
|
+ int irq_num;
|
|
+};
|
|
+
|
|
+static struct class *ir_tx_class;
|
|
+static struct ir_tx_dev *ir_tx_dev;
|
|
+static struct sunxi_ir_tx_data *ir_tx_data;
|
|
+static struct ir_raw_buffer ir_rawbuf;
|
|
+
|
|
+__inline int ir_tx_fifo_empty(void)
|
|
+{
|
|
+ return(readl(ir_tx_data->reg_base + IR_TX_TACR) == IR_TX_FIFO_SIZE);
|
|
+}
|
|
+
|
|
+ __inline int ir_tx_fifo_full(void)
|
|
+{
|
|
+ unsigned int reg_val;
|
|
+
|
|
+ reg_val = readl(ir_tx_data->reg_base + IR_TX_TACR);
|
|
+ dprintk(DEBUG_INFO, "%3u bytes fifo available\n", reg_val);
|
|
+ if (reg_val == 0)
|
|
+ return 1;
|
|
+ else
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+__inline void ir_tx_reset_rawbuffer(void)
|
|
+{
|
|
+ ir_rawbuf.tx_dcnt = 0;
|
|
+}
|
|
+
|
|
+void ir_tx_packet_handler(unsigned char address, unsigned char command)
|
|
+{
|
|
+ unsigned int i, j;
|
|
+ unsigned int count = 0;
|
|
+ unsigned char buffer[256];
|
|
+ unsigned char tx_code[4];
|
|
+
|
|
+ ir_tx_reset_rawbuffer();
|
|
+
|
|
+ tx_code[0] = address;
|
|
+ tx_code[1] = ~address;
|
|
+ tx_code[2] = command;
|
|
+ tx_code[3] = ~command;
|
|
+
|
|
+ dprintk(DEBUG_INFO, "addr: 0x%x addr': 0x%x cmd: 0x%x cmd': 0x%x\n",
|
|
+ tx_code[0], tx_code[1], tx_code[2], tx_code[3]);
|
|
+
|
|
+ /* go encoding */
|
|
+ if (IR_TX_CLK_Ts == 1) {
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xa5;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x54;
|
|
+ for (j = 0; j < 4; j++) {
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ if (tx_code[j] & 0x01) {
|
|
+ buffer[count++] = 0x99;
|
|
+ buffer[count++] = 0x4d;
|
|
+ } else {
|
|
+ buffer[count++] = 0x99;
|
|
+ buffer[count++] = 0x19;
|
|
+ }
|
|
+ tx_code[j] = tx_code[j] >> 1;
|
|
+ }
|
|
+ }
|
|
+ buffer[count++] = 0x99;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x7f;
|
|
+ } else {
|
|
+ for (j = 0; j < 4; j++) {
|
|
+ if (IR_TX_CYCLE_TYPE == 0 && j % 4 == 0) {
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xff;
|
|
+ buffer[count++] = 0xab;
|
|
+ buffer[count++] = 0x7f;
|
|
+ buffer[count++] = 0x55;
|
|
+ }
|
|
+ for (i = 0; i < 8; i++) {
|
|
+ if (tx_code[j] & 0x01) {
|
|
+ buffer[count++] = 0x9a;
|
|
+ buffer[count++] = 0x4e;
|
|
+ } else {
|
|
+ buffer[count++] = 0x9a;
|
|
+ buffer[count++] = 0x1a;
|
|
+ }
|
|
+ tx_code[j] = tx_code[j] >> 1;
|
|
+ }
|
|
+ }
|
|
+ if (IR_TX_CYCLE_TYPE == 0)
|
|
+ buffer[count++] = 0x9a;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < count; i++)
|
|
+ ir_rawbuf.tx_buf[ir_rawbuf.tx_dcnt++] = buffer[i];
|
|
+
|
|
+ dprintk(DEBUG_INFO, "tx_dcnt = %ld\n", ir_rawbuf.tx_dcnt);
|
|
+}
|
|
+
|
|
+static int send_ir_code(unsigned char address, unsigned char command)
|
|
+{
|
|
+ unsigned int i = 0, idle_threshold;
|
|
+ u32 tmp;
|
|
+
|
|
+ writel(IR_TX_GL_VALUE, ir_tx_data->reg_base + IR_TX_GLR);
|
|
+ idle_threshold = (readl(ir_tx_data->reg_base + IR_TX_IDC_H) << 8)
|
|
+ | readl(ir_tx_data->reg_base + IR_TX_IDC_L);
|
|
+ dprintk(DEBUG_INFO, "enter\n");
|
|
+ dprintk(DEBUG_INFO, "idle_threshold = %d\n", idle_threshold);
|
|
+ ir_tx_packet_handler(address, command);
|
|
+ writel((ir_rawbuf.tx_dcnt - 1), ir_tx_data->reg_base + IR_TX_TR);
|
|
+
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_GLR = 0x%2x\n", IR_TX_GLR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_GLR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_MCR = 0x%2x\n", IR_TX_MCR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_MCR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_CR = 0x%2x\n", IR_TX_CR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_CR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_IDC_H = 0x%2x\n", IR_TX_IDC_H,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_IDC_H));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_IDC_L = 0x%2x\n", IR_TX_IDC_L,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_IDC_L));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_ICR_H = 0x%2x\n", IR_TX_ICR_H,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_ICR_H));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_ICR_L = 0x%2x\n", IR_TX_ICR_L,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_ICR_L));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TELR = 0x%2x\n", IR_TX_TELR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TELR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_INTC = 0x%2x\n", IR_TX_INTC,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_INTC));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TACR = 0x%2x\n", IR_TX_TACR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TACR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_STAR = 0x%2x\n", IR_TX_STAR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_STAR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TR = 0x%2x\n", IR_TX_TR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_DMAC = 0x%2x\n", IR_TX_DMAC,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_DMAC));
|
|
+
|
|
+ if (ir_rawbuf.tx_dcnt <= IR_TX_FIFO_SIZE)
|
|
+ for (i = 0; i < ir_rawbuf.tx_dcnt; i++) {
|
|
+ writeb(ir_rawbuf.tx_buf[i],
|
|
+ ir_tx_data->reg_base + IR_TX_FIFO_DR);
|
|
+ }
|
|
+ else {
|
|
+ dprintk(DEBUG_INFO, "invalid packet\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ dprintk(DEBUG_INFO, "%3u bytes fifo available\n",
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TACR));
|
|
+
|
|
+ if (IR_TX_CYCLE_TYPE) {
|
|
+ for (i = 0; i < ir_rawbuf.tx_dcnt; i++)
|
|
+ dprintk(DEBUG_INFO, "%d, ir txbuffer code = 0x%x!\n",
|
|
+ i, ir_rawbuf.tx_buf[i]);
|
|
+ tmp = readl(ir_tx_data->reg_base + IR_TX_CR);
|
|
+ tmp |= (0x01<<7);
|
|
+ writel(tmp, ir_tx_data->reg_base + IR_TX_CR);
|
|
+ } else
|
|
+ while (!ir_tx_fifo_empty())
|
|
+ dprintk(DEBUG_INFO,
|
|
+ "fifo under run. %3u bytes fifo available\n",
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TACR));
|
|
+
|
|
+ //wait idle finish
|
|
+ while ((readl(ir_tx_data->reg_base + IR_TX_ICR_H) << 8
|
|
+ | readl(ir_tx_data->reg_base + IR_TX_ICR_L))
|
|
+ < idle_threshold)
|
|
+ dprintk(DEBUG_INFO, "wait idle\n");
|
|
+
|
|
+ dprintk(DEBUG_INFO, "finish\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline unsigned long ir_tx_get_intsta(void)
|
|
+{
|
|
+ return readl(ir_tx_data->reg_base + IR_TX_STAR);
|
|
+}
|
|
+
|
|
+static inline void ir_tx_clr_intsta(unsigned long bitmap)
|
|
+{
|
|
+ unsigned long tmp = readl(ir_tx_data->reg_base + IR_TX_STAR);
|
|
+
|
|
+ tmp &= ~0xff;
|
|
+ tmp |= bitmap&0xff;
|
|
+ writel(tmp, ir_tx_data->reg_base + IR_TX_STAR);
|
|
+}
|
|
+
|
|
+static irqreturn_t ir_tx_irq_service(int irqno, void *dev_id)
|
|
+{
|
|
+ unsigned long intsta = ir_tx_get_intsta();
|
|
+
|
|
+ dprintk(DEBUG_INFO, "IR TX IRQ Serve 0x%lx\n", intsta);
|
|
+
|
|
+ ir_tx_clr_intsta(intsta);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void ir_tx_reg_clear(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+ writel(0, ir_tx_data->reg_base + IR_TX_GLR);
|
|
+}
|
|
+
|
|
+static void ir_tx_reg_cfg(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+ writel(IR_TX_MC_VALUE, ir_tx_data->reg_base + IR_TX_MCR);
|
|
+ writel(IR_TX_CLK_VALUE, ir_tx_data->reg_base + IR_TX_CR);
|
|
+ writel(IR_TX_IDC_H_VALUE, ir_tx_data->reg_base + IR_TX_IDC_H);
|
|
+ writel(IR_TX_IDC_L_VALUE, ir_tx_data->reg_base + IR_TX_IDC_L);
|
|
+ writel(IR_TX_STA_VALUE, ir_tx_data->reg_base + IR_TX_STAR);
|
|
+ writel(IR_TX_INT_C_VALUE, ir_tx_data->reg_base + IR_TX_INTC);
|
|
+ writel(IR_TX_GL_VALUE, ir_tx_data->reg_base + IR_TX_GLR);
|
|
+
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_GLR = 0x%2x\n", IR_TX_GLR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_GLR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_MCR = 0x%2x\n", IR_TX_MCR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_MCR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_CR = 0x%2x\n", IR_TX_CR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_CR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_IDC_H = 0x%2x\n", IR_TX_IDC_H,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_IDC_H));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_IDC_L = 0x%2x\n", IR_TX_IDC_L,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_IDC_L));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_ICR_H = 0x%2x\n", IR_TX_ICR_H,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_ICR_H));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_ICR_L = 0x%2x\n", IR_TX_ICR_L,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_ICR_L));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TELR = 0x%2x\n", IR_TX_TELR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TELR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_INTC = 0x%2x\n", IR_TX_INTC,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_INTC));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TACR = 0x%2x\n", IR_TX_TACR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TACR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_STAR = 0x%2x\n", IR_TX_STAR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_STAR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_TR = 0x%2x\n", IR_TX_TR,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_TR));
|
|
+ dprintk(DEBUG_INFO, "Offset: 0x%2x IR_TX_DMAC = 0x%2x\n", IR_TX_DMAC,
|
|
+ readl(ir_tx_data->reg_base + IR_TX_DMAC));
|
|
+
|
|
+}
|
|
+
|
|
+static void ir_tx_clk_cfg(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+
|
|
+ unsigned long rate = 0;
|
|
+
|
|
+ rate = clk_get_rate(ir_tx_data->pclk);
|
|
+ dprintk(DEBUG_INIT, "get ir parent rate %dHZ\n", (__u32)rate);
|
|
+
|
|
+ if (clk_set_parent(ir_tx_data->mclk, ir_tx_data->pclk))
|
|
+ IRTX_ERR("set ir_clk parent failed!\n");
|
|
+
|
|
+ if (clk_set_rate(ir_tx_data->mclk, IR_TX_CLK))
|
|
+ IRTX_ERR("set ir clock freq to %d failed!\n", IR_TX_CLK);
|
|
+
|
|
+ rate = clk_get_rate(ir_tx_data->mclk);
|
|
+ dprintk(DEBUG_INIT, "get ir_clk rate %dHZ\n", (__u32)rate);
|
|
+
|
|
+ if (clk_prepare_enable(ir_tx_data->mclk))
|
|
+ IRTX_ERR("try to enable ir_clk failed!\n");
|
|
+}
|
|
+
|
|
+static void ir_tx_clk_uncfg(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+
|
|
+ if (IS_ERR(ir_tx_data->mclk) || ir_tx_data->mclk == NULL)
|
|
+ IRTX_ERR("ir_clk handle is invalid, just return!\n");
|
|
+ else {
|
|
+ clk_disable_unprepare(ir_tx_data->mclk);
|
|
+ clk_put(ir_tx_data->mclk);
|
|
+ ir_tx_data->mclk = NULL;
|
|
+ }
|
|
+
|
|
+ if (IS_ERR(ir_tx_data->pclk) || ir_tx_data->pclk == NULL)
|
|
+ IRTX_ERR("ir_clk_source handle is invalid, just return!\n");
|
|
+ else {
|
|
+ clk_put(ir_tx_data->pclk);
|
|
+ ir_tx_data->pclk = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ir_tx_select_gpio_state(struct pinctrl *pctrl, char *name)
|
|
+{
|
|
+ int ret = 0;
|
|
+ struct pinctrl_state *pctrl_state = NULL;
|
|
+
|
|
+ pctrl_state = pinctrl_lookup_state(pctrl, name);
|
|
+ if (IS_ERR(pctrl_state)) {
|
|
+ IRTX_ERR("IR_TX pinctrl_lookup_state(%s) failed! return %p \n",
|
|
+ name, pctrl_state);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ ret = pinctrl_select_state(pctrl, pctrl_state);
|
|
+ if (ret < 0)
|
|
+ IRTX_ERR("IR_TX pinctrl_select_state(%s) failed! return %d \n",
|
|
+ name, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ir_tx_request_gpio(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+ ir_tx_data->pctrl = devm_pinctrl_get(&ir_tx_data->pdev->dev);
|
|
+ if (IS_ERR(ir_tx_data->pctrl)) {
|
|
+ IRTX_ERR("devm_pinctrl_get() failed!\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return ir_tx_select_gpio_state(ir_tx_data->pctrl,
|
|
+ PINCTRL_STATE_DEFAULT);
|
|
+}
|
|
+
|
|
+static int ir_tx_setup(struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = ir_tx_request_gpio(ir_tx_data);
|
|
+ if (ret < 0) {
|
|
+ IRTX_ERR("request gpio failed!\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ ir_tx_clk_cfg(ir_tx_data);
|
|
+ ir_tx_reg_cfg(ir_tx_data);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ir_tx_startup(struct platform_device *pdev,
|
|
+ struct sunxi_ir_tx_data *ir_tx_data)
|
|
+{
|
|
+ struct device_node *np = NULL;
|
|
+ int ret = 0;
|
|
+
|
|
+ np = pdev->dev.of_node;
|
|
+
|
|
+ ir_tx_data->reg_base = of_iomap(np, 0);
|
|
+ if (ir_tx_data->reg_base == NULL) {
|
|
+ IRTX_ERR("Failed to ioremap() io memory region.\n");
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ dprintk(DEBUG_INIT, "base: %p !\n", ir_tx_data->reg_base);
|
|
+
|
|
+ ir_tx_data->irq_num = irq_of_parse_and_map(np, 0);
|
|
+ if (ir_tx_data->irq_num == 0) {
|
|
+ IRTX_ERR("Failed to map irq.\n");
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ dprintk(DEBUG_INIT, "irq num: %d !\n", ir_tx_data->irq_num);
|
|
+
|
|
+ ir_tx_data->pclk = of_clk_get(np, 0);
|
|
+ ir_tx_data->mclk = of_clk_get(np, 1);
|
|
+ if (IS_ERR_OR_NULL(ir_tx_data->pclk) ||
|
|
+ IS_ERR_OR_NULL(ir_tx_data->mclk)) {
|
|
+ IRTX_ERR("Failed to get clk.\n");
|
|
+ ret = -EBUSY;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_ir_tx_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ dprintk(DEBUG_INIT, "start\n");
|
|
+
|
|
+ ir_tx_data = kzalloc(sizeof(*ir_tx_data), GFP_KERNEL);
|
|
+ if (IS_ERR_OR_NULL(ir_tx_data)) {
|
|
+ IRTX_ERR("not enough memory for ir tx data\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ if (pdev->dev.of_node)
|
|
+ /* get dt and sysconfig */
|
|
+ ret = sunxi_ir_tx_startup(pdev, ir_tx_data);
|
|
+ else {
|
|
+ IRTX_ERR("device tree err!\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ if (ret < 0)
|
|
+ goto err_startup;
|
|
+
|
|
+ ir_tx_data->pdev = pdev;
|
|
+
|
|
+ if (ir_tx_setup(ir_tx_data)) {
|
|
+ IRTX_ERR("ir_tx_setup failed.\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, ir_tx_data);
|
|
+ if (request_irq(ir_tx_data->irq_num, ir_tx_irq_service, 0,
|
|
+ "RemoteIR_TX", ir_tx_data)) {
|
|
+ IRTX_ERR(" request irq fail.\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_request_irq;
|
|
+ }
|
|
+
|
|
+ if (ret) {
|
|
+ IRTX_ERR("cteate sysfs node failed\n");
|
|
+ goto err_create_grp;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_create_grp:
|
|
+ free_irq(ir_tx_data->irq_num, &pdev->dev);
|
|
+
|
|
+err_request_irq:
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ ir_tx_clk_uncfg(ir_tx_data);
|
|
+ ir_tx_reg_clear(ir_tx_data);
|
|
+
|
|
+err_startup:
|
|
+ kfree(ir_tx_data);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_ir_tx_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct sunxi_ir_tx_data *ir_tx_data = platform_get_drvdata(pdev);
|
|
+
|
|
+ free_irq(ir_tx_data->irq_num, &pdev->dev);
|
|
+ devm_pinctrl_put(ir_tx_data->pctrl);
|
|
+
|
|
+ ir_tx_reg_clear(ir_tx_data);
|
|
+ ir_tx_clk_uncfg(ir_tx_data);
|
|
+
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ kfree(ir_tx_data);
|
|
+
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id sunxi_ir_tx_match[] = {
|
|
+ { .compatible = "allwinner,ir_tx", },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sunxi_ir_recv_of_match);
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int sunxi_ir_tx_suspend(struct device *dev)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct sunxi_ir_tx_data *ir_tx_data = platform_get_drvdata(pdev);
|
|
+
|
|
+ dprintk(DEBUG_SUSPEND, "enter\n");
|
|
+ ir_tx_reg_clear(ir_tx_data);
|
|
+
|
|
+ disable_irq_nosync(ir_tx_data->irq_num);
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ir_tx_data->mclk)) {
|
|
+ IRTX_ERR("clk handle is invalid, just return!\n");
|
|
+ return -1;
|
|
+ }
|
|
+ clk_disable_unprepare(ir_tx_data->mclk);
|
|
+
|
|
+ ir_tx_select_gpio_state(ir_tx_data->pctrl, PINCTRL_STATE_SLEEP);
|
|
+
|
|
+
|
|
+ return 0;
|
|
+};
|
|
+
|
|
+static int sunxi_ir_tx_resume(struct device *dev)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct sunxi_ir_tx_data *ir_tx_data = platform_get_drvdata(pdev);
|
|
+
|
|
+ dprintk(DEBUG_SUSPEND, "enter\n");
|
|
+
|
|
+ clk_prepare_enable(ir_tx_data->mclk);
|
|
+ enable_irq(ir_tx_data->irq_num);
|
|
+
|
|
+ if (ir_tx_setup(ir_tx_data))
|
|
+ return -1;
|
|
+
|
|
+ return 0;
|
|
+};
|
|
+
|
|
+static const struct dev_pm_ops sunxi_ir_tx_pm_ops = {
|
|
+ .suspend = sunxi_ir_tx_suspend,
|
|
+ .resume = sunxi_ir_tx_resume,
|
|
+};
|
|
+#endif
|
|
+
|
|
+static struct platform_driver sunxi_ir_tx_driver = {
|
|
+ .probe = sunxi_ir_tx_probe,
|
|
+ .remove = sunxi_ir_tx_remove,
|
|
+ .driver = {
|
|
+ .name = SUNXI_IR_TX_DRIVER_NAME,
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = sunxi_ir_tx_match,
|
|
+#ifdef CONFIG_PM
|
|
+ .pm = &sunxi_ir_tx_pm_ops,
|
|
+#endif
|
|
+ },
|
|
+};
|
|
+
|
|
+static int ir_tx_open(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ filp->private_data = ir_tx_data;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ir_tx_release(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long ir_tx_unlocked_ioctl(struct file *filp, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ unsigned int size;
|
|
+ struct cmd *code;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case IR_TX_IOCSEND:
|
|
+ size = _IOC_SIZE(cmd);
|
|
+ code = (struct cmd *)kzalloc(size, GFP_KERNEL);
|
|
+ if (IS_ERR_OR_NULL(code)) {
|
|
+ IRTX_ERR("not enough memory for ir code\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ if (copy_from_user(code, (void __user *)arg, size)) {
|
|
+ IRTX_ERR("copy buffer err\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ send_ir_code(code->address, code->command);
|
|
+ kfree(code);
|
|
+ break;
|
|
+ default:
|
|
+ IRTX_ERR("give us a err cmd\n");
|
|
+ return -ENOTTY;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct file_operations ir_tx_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .unlocked_ioctl = ir_tx_unlocked_ioctl,
|
|
+ .open = ir_tx_open,
|
|
+ .release = ir_tx_release,
|
|
+};
|
|
+
|
|
+static int __init ir_tx_init(void)
|
|
+{
|
|
+ int err = 0;
|
|
+ struct device *dev;
|
|
+
|
|
+ dprintk(DEBUG_INIT, "start\n");
|
|
+
|
|
+ ir_tx_dev = kzalloc(sizeof(struct ir_tx_dev), GFP_KERNEL);
|
|
+ if (ir_tx_dev == NULL) {
|
|
+ IRTX_ERR("kzalloc failed!\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ err = alloc_chrdev_region(&ir_tx_dev->chrdev, 0, 1, "ir_tx-dev");
|
|
+
|
|
+ if (err) {
|
|
+ IRTX_ERR("alloc_chrdev_region failed!\n");
|
|
+ goto alloc_chrdev_err;
|
|
+ }
|
|
+
|
|
+ cdev_init(&(ir_tx_dev->cdev), &ir_tx_fops);
|
|
+ ir_tx_dev->cdev.owner = THIS_MODULE;
|
|
+ err = cdev_add(&(ir_tx_dev->cdev), ir_tx_dev->chrdev, 1);
|
|
+ if (err) {
|
|
+ IRTX_ERR("cdev_add failed!\n");
|
|
+ goto cdev_add_err;
|
|
+ }
|
|
+
|
|
+ ir_tx_class = class_create(THIS_MODULE, "ir_tx_char_class");
|
|
+ if (IS_ERR(ir_tx_class)) {
|
|
+ err = PTR_ERR(ir_tx_class);
|
|
+ IRTX_ERR("class_create failed!\n");
|
|
+ goto class_err;
|
|
+ }
|
|
+
|
|
+ dev = device_create(ir_tx_class, NULL, ir_tx_dev->chrdev, NULL,
|
|
+ "ir_tx%d", 0);
|
|
+ if (IS_ERR(dev)) {
|
|
+ err = PTR_ERR(dev);
|
|
+ IRTX_ERR("device_create failed!\n");
|
|
+ goto device_err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+device_err:
|
|
+ device_destroy(ir_tx_class, ir_tx_dev->chrdev);
|
|
+class_err:
|
|
+ cdev_del(&(ir_tx_dev->cdev));
|
|
+cdev_add_err:
|
|
+ unregister_chrdev_region(ir_tx_dev->chrdev, 1);
|
|
+alloc_chrdev_err:
|
|
+ kfree(ir_tx_dev);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void __exit ir_tx_exit(void)
|
|
+{
|
|
+ cdev_del(&(ir_tx_dev->cdev));
|
|
+ unregister_chrdev_region(ir_tx_dev->chrdev, 1);
|
|
+ device_destroy(ir_tx_class, ir_tx_dev->chrdev);
|
|
+ class_destroy(ir_tx_class);
|
|
+ kfree(ir_tx_dev);
|
|
+}
|
|
+
|
|
+module_init(ir_tx_init);
|
|
+module_exit(ir_tx_exit);
|
|
+module_param_named(debug, debug_mask, int, 0664);
|
|
+module_platform_driver(sunxi_ir_tx_driver);
|
|
+MODULE_AUTHOR("Li Ming");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("Remote IR TX driver");
|