831 lines
23 KiB
C
831 lines
23 KiB
C
|
/*
|
||
|
* Copyright (c) 2013-2020 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/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 <media/rc-core.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_raw_buffer {
|
||
|
unsigned int 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 rc_dev *rcdev;
|
||
|
struct clk *mclk;
|
||
|
struct clk *pclk;
|
||
|
struct regulator *suply;
|
||
|
struct pinctrl *pctrl;
|
||
|
u32 suply_vol;
|
||
|
int irq_num;
|
||
|
};
|
||
|
|
||
|
static struct sunxi_ir_tx_data *ir_tx_data;
|
||
|
static struct ir_raw_buffer ir_rawbuf;
|
||
|
|
||
|
/*
|
||
|
* one pulse cycle(=10.666666us) is not an integer, so in order to
|
||
|
* improve the accuracy, define the value of three pulse cycle here.
|
||
|
*/
|
||
|
static unsigned int three_pulse_cycle = 32;
|
||
|
|
||
|
static inline int ir_tx_fifo_empty(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);
|
||
|
|
||
|
return (reg_val == IR_TX_FIFO_SIZE);
|
||
|
}
|
||
|
|
||
|
static 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);
|
||
|
|
||
|
return (reg_val == 0);
|
||
|
}
|
||
|
|
||
|
static inline void ir_tx_reset_rawbuffer(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
ir_rawbuf.tx_dcnt = 0;
|
||
|
for (i = 0; i < IR_TX_RAW_BUF_SIZE; i++)
|
||
|
ir_rawbuf.tx_buf[i] = 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 = %d\n", ir_rawbuf.tx_dcnt);
|
||
|
}
|
||
|
|
||
|
static int send_ir_code(void)
|
||
|
{
|
||
|
unsigned int i, idle_threshold;
|
||
|
unsigned int reg_val;
|
||
|
u32 tmp;
|
||
|
|
||
|
dprintk(DEBUG_INFO, "enter\n");
|
||
|
|
||
|
/* reset transmit and flush fifo */
|
||
|
reg_val = readl(ir_tx_data->reg_base + IR_TX_GLR);
|
||
|
reg_val |= 0x02;
|
||
|
writel(reg_val, 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, "idle_threshold = %d\n", idle_threshold);
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
tmp = readl(ir_tx_data->reg_base + IR_TX_TACR);
|
||
|
dprintk(DEBUG_INFO, "%3u bytes fifo available\n", tmp);
|
||
|
|
||
|
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()) {
|
||
|
tmp = readl(ir_tx_data->reg_base + IR_TX_TACR);
|
||
|
dprintk(DEBUG_INFO,
|
||
|
"fifo under run. %3u bytes fifo available\n",
|
||
|
tmp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* 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 int ir_tx_get_intsta(void)
|
||
|
{
|
||
|
return readl(ir_tx_data->reg_base + IR_TX_STAR);
|
||
|
}
|
||
|
|
||
|
static inline void ir_tx_clr_intsta(unsigned int bitmap)
|
||
|
{
|
||
|
unsigned int 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 int intsta = ir_tx_get_intsta();
|
||
|
|
||
|
dprintk(DEBUG_INFO, "IR TX IRQ Serve %#x\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 ret;
|
||
|
}
|
||
|
|
||
|
static int ir_tx_resume_setup(struct sunxi_ir_tx_data *ir_tx_data)
|
||
|
{
|
||
|
if (IS_ERR_OR_NULL(ir_tx_data->pctrl)) {
|
||
|
IRTX_ERR("pinctrl handler error!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ir_tx_select_gpio_state(ir_tx_data->pctrl, PINCTRL_STATE_DEFAULT);
|
||
|
|
||
|
ir_tx_clk_cfg(ir_tx_data);
|
||
|
ir_tx_reg_cfg(ir_tx_data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The relation of carrier frequency and IR TX CLK as follows:
|
||
|
* RFMC = FCLK / ((N + 1) * (DRMC + 2))
|
||
|
*
|
||
|
* @RFMC: reference frequency of modulated carrier(=carrier_freq).
|
||
|
* @FCLK: IR TX CLK(=12000000).
|
||
|
* @N: the low 8 bit value of IR_TX_MCR.
|
||
|
* @DRMC: duty cycle of modulated carrier, its value can be 0,1 or 2
|
||
|
* which is decided by the bit6 and bit5 of TX MCR.
|
||
|
*/
|
||
|
static int sunxi_ir_tx_set_carrier(struct rc_dev *dev, u32 carrier_freq)
|
||
|
{
|
||
|
unsigned int reg_val;
|
||
|
unsigned int drmc = 1;
|
||
|
|
||
|
if ((carrier_freq > 6000000) || (carrier_freq < 15000)) {
|
||
|
IRTX_ERR("invalid frequency of carrier: %d\n", carrier_freq);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* First, get the duty cycle of modulated carrier */
|
||
|
reg_val = readl(ir_tx_data->reg_base + IR_TX_GLR);
|
||
|
drmc = (reg_val >> 5) & 0x3;
|
||
|
dprintk(DEBUG_INFO, "DRMC is %d\n", drmc);
|
||
|
dprintk(DEBUG_INFO, "0: duty cycle 50%%\n"
|
||
|
"1: duty cycle 33%%\n"
|
||
|
"2: duty cycle 25%%\n");
|
||
|
|
||
|
/* Then, calculate the value of N */
|
||
|
reg_val = IR_TX_CLK / ((2 + drmc) * carrier_freq) - 1;
|
||
|
reg_val &= 0xff;
|
||
|
dprintk(DEBUG_INFO, "RFMC is %2x\n", reg_val);
|
||
|
writel(reg_val, ir_tx_data->reg_base + IR_TX_MCR);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sunxi_ir_tx_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle)
|
||
|
{
|
||
|
unsigned int reg_val;
|
||
|
|
||
|
if (duty_cycle > 100) {
|
||
|
IRTX_ERR("invalid duty_cycle: %d\n", duty_cycle);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dprintk(DEBUG_INFO, "set duty cycle to %d\n", duty_cycle);
|
||
|
reg_val = readl(ir_tx_data->reg_base + IR_TX_GLR);
|
||
|
|
||
|
/* clear bit5 and bit6 */
|
||
|
reg_val &= 0x9f;
|
||
|
if (duty_cycle < 30) {
|
||
|
reg_val |= 0x40; /* set bit6=1 */
|
||
|
dprintk(DEBUG_INFO, "set duty cycle to 25%%\n");
|
||
|
} else if (duty_cycle < 40) {
|
||
|
reg_val |= 0x20; /* set bit5=1 */
|
||
|
dprintk(DEBUG_INFO, "set duty cycle to 33%%\n");
|
||
|
} else {
|
||
|
/* do nothing, bit5 and bit6 are already cleared to 0 */
|
||
|
dprintk(DEBUG_INFO, "set duty cycle to 50%%\n");
|
||
|
}
|
||
|
|
||
|
dprintk(DEBUG_INFO, "reg_val of IR_TX_GLR: %2x\n", reg_val);
|
||
|
writel(reg_val, ir_tx_data->reg_base + IR_TX_GLR);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* run_length_encode - encode raw data as run_length_encode format
|
||
|
* @raw_data: raw data are from userspace, such as 0x01002328,
|
||
|
* bit24=1 indicates the output wave is high level and
|
||
|
* otherwise low level; the low 24 bits of raw data
|
||
|
* indicate the number of pulse cycle.
|
||
|
* @buf: store run_length_encode data and will be written to tx fifo
|
||
|
* @count: the current index of buf
|
||
|
*/
|
||
|
static unsigned int run_length_encode(unsigned int *raw_data,
|
||
|
unsigned char *buf,
|
||
|
unsigned int count)
|
||
|
{
|
||
|
unsigned int is_high_level = 0;
|
||
|
unsigned int num = 0; /* the number of pulse cycle */
|
||
|
|
||
|
/* output high level or low level */
|
||
|
is_high_level = (*raw_data >> 24) & 0x01;
|
||
|
dprintk(DEBUG_INFO, "is high level: %d\n", is_high_level);
|
||
|
|
||
|
/* calculate the number of pulse cycle */
|
||
|
num = ((*raw_data & 0x00FFFFFF) * 3) / three_pulse_cycle;
|
||
|
/* check number is over 127 or not */
|
||
|
while (num > 0x7f) {
|
||
|
buf[count] = (is_high_level << 7) | 0x7f;
|
||
|
dprintk(DEBUG_INFO, "current buffer data is %2x\n", buf[count]);
|
||
|
count++;
|
||
|
num -= 0x7f;
|
||
|
}
|
||
|
|
||
|
buf[count] = (is_high_level << 7) | num;
|
||
|
dprintk(DEBUG_INFO, "current buffer data is %2x\n", buf[count]);
|
||
|
count++;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static int sunxi_ir_tx_xmit(struct rc_dev *dev, unsigned int *txbuf,
|
||
|
unsigned int count)
|
||
|
{
|
||
|
int i, ret, num;
|
||
|
unsigned int index = 0;
|
||
|
unsigned int *head_p, *data_p, *stop_p;
|
||
|
|
||
|
if (unlikely(!dev)) {
|
||
|
dprintk(DEBUG_INFO, "device is null\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (unlikely(count > IR_TX_RAW_BUF_SIZE)) {
|
||
|
dprintk(DEBUG_INFO, "too many raw data\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dprintk(DEBUG_INFO, "transmit %d raw data\n", count);
|
||
|
|
||
|
ir_tx_reset_rawbuffer();
|
||
|
|
||
|
/* encode the guide code */
|
||
|
head_p = txbuf;
|
||
|
dprintk(DEBUG_INFO, "head pulse: '%#x', head space: '%#x'\n",
|
||
|
*head_p, *(head_p + 1));
|
||
|
index = run_length_encode(head_p, ir_rawbuf.tx_buf, index);
|
||
|
head_p++;
|
||
|
index = run_length_encode(head_p, ir_rawbuf.tx_buf, index);
|
||
|
|
||
|
/* encode the payload: addr, ~addr, cmd, ~cmd */
|
||
|
data_p = (++head_p);
|
||
|
dprintk(DEBUG_INFO, "valid raw data is:\n");
|
||
|
/* count = head(2) + payload(num) + end(2) */
|
||
|
num = count - 4;
|
||
|
for (i = 0; i < num; i++) {
|
||
|
dprintk(DEBUG_INFO, "%#x ", *data_p);
|
||
|
index = run_length_encode(data_p, ir_rawbuf.tx_buf, index);
|
||
|
data_p++;
|
||
|
if ((i + 1) % 8 == 0)
|
||
|
dprintk(DEBUG_INFO, "\n");
|
||
|
}
|
||
|
dprintk(DEBUG_INFO, "\n");
|
||
|
|
||
|
/* encode the end code */
|
||
|
stop_p = data_p;
|
||
|
dprintk(DEBUG_INFO, "stop pulse: '%#x', stop space: '%#x'\n",
|
||
|
*stop_p, *(stop_p + 1));
|
||
|
index = run_length_encode(stop_p, ir_rawbuf.tx_buf, index);
|
||
|
stop_p++;
|
||
|
index = run_length_encode(stop_p, ir_rawbuf.tx_buf, index);
|
||
|
|
||
|
/* update ir_rawbuf.tx_dcnt */
|
||
|
ir_rawbuf.tx_dcnt = index;
|
||
|
dprintk(DEBUG_INFO, "ir_rawbuf total count is %d\n", ir_rawbuf.tx_dcnt);
|
||
|
|
||
|
/* send ir code to tx fifo */
|
||
|
ret = send_ir_code();
|
||
|
if (unlikely(ret)) {
|
||
|
dprintk(DEBUG_INFO, "send ir code fail\n");
|
||
|
ret = -EINVAL;
|
||
|
} else {
|
||
|
ret = count;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
goto out_iounmap;
|
||
|
} 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;
|
||
|
goto out_dispose_mapping;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
out_dispose_mapping:
|
||
|
irq_dispose_mapping(ir_tx_data->irq_num);
|
||
|
out_iounmap:
|
||
|
iounmap(ir_tx_data->reg_base);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sunxi_ir_tx_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct rc_dev *ir_tx_rcdev;
|
||
|
int ret = 0;
|
||
|
static char ir_tx_dev_name[] = "ir_tx";
|
||
|
|
||
|
dprintk(DEBUG_INIT, "%s %s\n", SUNXI_IR_TX_DRIVER_NAME,
|
||
|
SUNXI_IR_TX_VERSION);
|
||
|
|
||
|
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) {
|
||
|
/* initialize hardware resource */
|
||
|
ret = sunxi_ir_tx_startup(pdev, ir_tx_data);
|
||
|
if (ret) {
|
||
|
IRTX_ERR("initialize hardware resource fail\n");
|
||
|
goto err_startup;
|
||
|
}
|
||
|
} else {
|
||
|
IRTX_ERR("device tree err!\n");
|
||
|
ret = -EBUSY;
|
||
|
goto err_startup;
|
||
|
}
|
||
|
|
||
|
ir_tx_rcdev = rc_allocate_device();
|
||
|
if (!ir_tx_rcdev) {
|
||
|
IRTX_ERR("rc dev allocate fail!\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err_startup;
|
||
|
}
|
||
|
|
||
|
/* initialize ir_tx_rcdev*/
|
||
|
ir_tx_rcdev->input_name = SUNXI_IR_TX_DEVICE_NAME;
|
||
|
ir_tx_rcdev->input_phys = SUNXI_IR_TX_DEVICE_NAME "/input0";
|
||
|
ir_tx_rcdev->input_id.bustype = BUS_HOST;
|
||
|
ir_tx_rcdev->input_id.vendor = 0x0001;
|
||
|
ir_tx_rcdev->input_id.product = 0x0001;
|
||
|
ir_tx_rcdev->input_id.version = 0x0100;
|
||
|
ir_tx_rcdev->input_dev->dev.init_name = &ir_tx_dev_name[0];
|
||
|
|
||
|
ir_tx_rcdev->dev.parent = &pdev->dev;
|
||
|
ir_tx_rcdev->driver_type = RC_DRIVER_IR_RAW;
|
||
|
ir_tx_rcdev->driver_name = SUNXI_IR_TX_DRIVER_NAME;
|
||
|
ir_tx_rcdev->map_name = RC_MAP_SUNXI;
|
||
|
|
||
|
ir_tx_rcdev->s_tx_carrier = sunxi_ir_tx_set_carrier;
|
||
|
ir_tx_rcdev->s_tx_duty_cycle = sunxi_ir_tx_set_duty_cycle;
|
||
|
ir_tx_rcdev->tx_ir = sunxi_ir_tx_xmit;
|
||
|
|
||
|
ret = rc_register_device(ir_tx_rcdev);
|
||
|
if (ret) {
|
||
|
IRTX_ERR("failed to register rc device\n");
|
||
|
goto err_register_rcdev;
|
||
|
}
|
||
|
dprintk(DEBUG_INIT, "register rc device success\n");
|
||
|
|
||
|
ir_tx_data->rcdev = ir_tx_rcdev;
|
||
|
ir_tx_data->pdev = pdev;
|
||
|
|
||
|
if (ir_tx_setup(ir_tx_data)) {
|
||
|
IRTX_ERR("ir_tx_setup failed.\n");
|
||
|
ret = -EIO;
|
||
|
goto err_register_rcdev;
|
||
|
}
|
||
|
dprintk(DEBUG_INIT, "ir_tx_setup success\n");
|
||
|
|
||
|
platform_set_drvdata(pdev, ir_tx_data);
|
||
|
ret = request_irq(ir_tx_data->irq_num, ir_tx_irq_service, 0,
|
||
|
"RemoteIR_TX", ir_tx_data);
|
||
|
if (ret) {
|
||
|
IRTX_ERR(" request irq fail.\n");
|
||
|
ret = -EBUSY;
|
||
|
goto err_request_irq;
|
||
|
}
|
||
|
dprintk(DEBUG_INIT, "request irq success\n");
|
||
|
dprintk(DEBUG_INIT, "probe success\n");
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_request_irq:
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
rc_unregister_device(ir_tx_rcdev);
|
||
|
ir_tx_rcdev = NULL;
|
||
|
ir_tx_clk_uncfg(ir_tx_data);
|
||
|
ir_tx_reg_clear(ir_tx_data);
|
||
|
|
||
|
err_register_rcdev:
|
||
|
rc_free_device(ir_tx_rcdev);
|
||
|
|
||
|
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);
|
||
|
rc_unregister_device(ir_tx_data->rcdev);
|
||
|
kfree(ir_tx_data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id sunxi_ir_tx_of_match[] = {
|
||
|
{ .compatible = "allwinner,ir_tx", },
|
||
|
{ },
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, sunxi_ir_tx_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_resume_setup(ir_tx_data)) {
|
||
|
IRTX_ERR("ir_tx_setup failed!\n");
|
||
|
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_of_match,
|
||
|
#ifdef CONFIG_PM
|
||
|
.pm = &sunxi_ir_tx_pm_ops,
|
||
|
#endif
|
||
|
},
|
||
|
};
|
||
|
|
||
|
module_platform_driver(sunxi_ir_tx_driver);
|
||
|
MODULE_AUTHOR("libohui <libohui@allwinnertech.com>");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_DESCRIPTION("Remote IR TX driver");
|