blob: 966090cc5592b926ddb707e2889d1ab2eb9ee395 [file] [log] [blame]
/*
* drivers/input/misc/ambarella_input_ir.c
*
* Author: Qiao Wang <qwang@ambarella.com>
* Copyright (C) 2004-2009, Ambarella, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <plat/ir.h>
#define MAX_IR_BUFFER 512
#define HW_FIFO_BUFFER 48
struct ambarella_ir_keymap {
u32 key_code;
u32 key_raw;
};
struct ambarella_ir_frame_info {
u32 frame_head_size;
u32 frame_data_size;
u32 frame_end_size;
u32 frame_repeat_head_size;
};
struct ambarella_ir_info {
struct input_dev *input;
unsigned char __iomem *regbase;
unsigned int irq;
int (*ir_parse)(struct ambarella_ir_info *pirinfo, u32 *uid);
int ir_pread;
int ir_pwrite;
u16 tick_buf[MAX_IR_BUFFER];
struct ambarella_ir_frame_info frame_info;
u32 frame_data_to_received;
struct ambarella_ir_keymap *keymap;
u32 key_num;
u32 protocol;
u32 print_key : 1;
};
/* ========================================================================= */
static u16 ambarella_ir_read_data(
struct ambarella_ir_info *pirinfo, int pointer)
{
BUG_ON(pointer < 0);
BUG_ON(pointer >= MAX_IR_BUFFER);
BUG_ON(pointer == pirinfo->ir_pwrite);
return pirinfo->tick_buf[pointer];
}
static int ambarella_ir_get_tick_size(struct ambarella_ir_info *pirinfo)
{
int size = 0;
if (pirinfo->ir_pread > pirinfo->ir_pwrite)
size = MAX_IR_BUFFER - pirinfo->ir_pread + pirinfo->ir_pwrite;
else
size = pirinfo->ir_pwrite - pirinfo->ir_pread;
return size;
}
void ambarella_ir_inc_read_ptr(struct ambarella_ir_info *pirinfo)
{
BUG_ON(pirinfo->ir_pread == pirinfo->ir_pwrite);
pirinfo->ir_pread++;
if (pirinfo->ir_pread >= MAX_IR_BUFFER)
pirinfo->ir_pread = 0;
}
void ambarella_ir_move_read_ptr(struct ambarella_ir_info *pirinfo, int offset)
{
for (; offset > 0; offset--) {
ambarella_ir_inc_read_ptr(pirinfo);
}
}
/* ========================================================================= */
#include "ambarella_ir_nec.c"
#include "ambarella_ir_sony.c"
#include "ambarella_ir_philips.c"
#include "ambarella_ir_panasonic.c"
/* ========================================================================= */
static int ambarella_input_report_ir(struct ambarella_ir_info *pirinfo, u32 uid)
{
struct ambarella_ir_keymap *keymap = pirinfo->keymap;
struct input_dev *input = pirinfo->input;
int i;
for (i = 0; i < pirinfo->key_num; i++, keymap++) {
if (keymap->key_raw == uid) {
input_report_key(input, keymap->key_code, 1);
input_sync(input);
input_report_key(input, keymap->key_code, 0);
input_sync(input);
if(pirinfo->print_key)
dev_info(&input->dev, "IR_KEY [%d]\n", keymap->key_code);
break;
}
}
return 0;
}
static inline void ambarella_ir_write_data(struct ambarella_ir_info *pirinfo,
u16 val)
{
BUG_ON(pirinfo->ir_pwrite < 0);
BUG_ON(pirinfo->ir_pwrite >= MAX_IR_BUFFER);
pirinfo->tick_buf[pirinfo->ir_pwrite] = val;
pirinfo->ir_pwrite++;
if (pirinfo->ir_pwrite >= MAX_IR_BUFFER)
pirinfo->ir_pwrite = 0;
}
static inline int ambarella_ir_update_buffer(struct ambarella_ir_info *pirinfo)
{
int count;
int size;
count = amba_readl(pirinfo->regbase + IR_STATUS_OFFSET);
dev_dbg(&pirinfo->input->dev, "size we got is [%d]\n", count);
for (; count > 0; count--) {
ambarella_ir_write_data(pirinfo,
amba_readl(pirinfo->regbase + IR_DATA_OFFSET));
}
size = ambarella_ir_get_tick_size(pirinfo);
return size;
}
static irqreturn_t ambarella_ir_irq(int irq, void *devid)
{
struct ambarella_ir_info *pirinfo;
u32 ctrl_val, uid, edges;
int rval;
pirinfo = (struct ambarella_ir_info *)devid;
BUG_ON(pirinfo->ir_pread < 0);
BUG_ON(pirinfo->ir_pread >= MAX_IR_BUFFER);
ctrl_val = amba_readl(pirinfo->regbase + IR_CONTROL_OFFSET);
if (ctrl_val & IR_CONTROL_FIFO_OV) {
while (amba_readl(pirinfo->regbase + IR_STATUS_OFFSET) > 0)
amba_readl(pirinfo->regbase + IR_DATA_OFFSET);
amba_setbitsl(pirinfo->regbase + IR_CONTROL_OFFSET,
IR_CONTROL_FIFO_OV);
dev_err(&pirinfo->input->dev,
"IR_CONTROL_FIFO_OV overflow\n");
goto ambarella_ir_irq_exit;
}
ambarella_ir_update_buffer(pirinfo);
if(!pirinfo->ir_parse)
goto ambarella_ir_irq_exit;
rval = pirinfo->ir_parse(pirinfo, &uid);
if (rval == 0) {
/* yes, we find the key */
if(pirinfo->print_key)
dev_info(&pirinfo->input->dev, "uid = 0x%08x\n", uid);
ambarella_input_report_ir(pirinfo, uid);
}
pirinfo->frame_data_to_received = pirinfo->frame_info.frame_data_size +
pirinfo->frame_info.frame_head_size;
pirinfo->frame_data_to_received -= ambarella_ir_get_tick_size(pirinfo);
if (pirinfo->frame_data_to_received <= HW_FIFO_BUFFER) {
edges = pirinfo->frame_data_to_received;
pirinfo->frame_data_to_received = 0;
} else {// > HW_FIFO_BUFFER
edges = HW_FIFO_BUFFER;
pirinfo->frame_data_to_received -= HW_FIFO_BUFFER;
}
dev_dbg(&pirinfo->input->dev,
"line[%d],frame_data_to_received[%d]\n", __LINE__, edges);
amba_clrbitsl(pirinfo->regbase + IR_CONTROL_OFFSET,
IR_CONTROL_INTLEV(0x3F));
amba_setbitsl(pirinfo->regbase + IR_CONTROL_OFFSET,
IR_CONTROL_INTLEV(edges));
ambarella_ir_irq_exit:
amba_writel(pirinfo->regbase + IR_CONTROL_OFFSET,
(amba_readl(pirinfo->regbase + IR_CONTROL_OFFSET) |
IR_CONTROL_LEVINT));
return IRQ_HANDLED;
}
void ambarella_ir_enable(struct ambarella_ir_info *pirinfo)
{
u32 edges;
pirinfo->frame_data_to_received = pirinfo->frame_info.frame_head_size
+ pirinfo->frame_info.frame_data_size;
BUG_ON(pirinfo->frame_data_to_received > MAX_IR_BUFFER);
if (pirinfo->frame_data_to_received > HW_FIFO_BUFFER) {
edges = HW_FIFO_BUFFER;
pirinfo->frame_data_to_received -= HW_FIFO_BUFFER;
} else {
edges = pirinfo->frame_data_to_received;
pirinfo->frame_data_to_received = 0;
}
amba_writel(pirinfo->regbase + IR_CONTROL_OFFSET, IR_CONTROL_RESET);
amba_setbitsl(pirinfo->regbase + IR_CONTROL_OFFSET,
IR_CONTROL_ENB | IR_CONTROL_INTLEV(edges) | IR_CONTROL_INTENB);
enable_irq(pirinfo->irq);
}
void ambarella_ir_disable(struct ambarella_ir_info *pirinfo)
{
disable_irq(pirinfo->irq);
}
void ambarella_ir_set_protocol(struct ambarella_ir_info *pirinfo,
enum ambarella_ir_protocol protocol_id)
{
memset(pirinfo->tick_buf, 0x0, sizeof(pirinfo->tick_buf));
pirinfo->ir_pread = 0;
pirinfo->ir_pwrite = 0;
switch (protocol_id) {
case AMBA_IR_PROTOCOL_NEC:
dev_notice(&pirinfo->input->dev,
"Protocol NEC[%d]\n", protocol_id);
pirinfo->ir_parse = ambarella_ir_nec_parse;
ambarella_ir_get_nec_info(&pirinfo->frame_info);
break;
case AMBA_IR_PROTOCOL_PANASONIC:
dev_notice(&pirinfo->input->dev,
"Protocol PANASONIC[%d]\n", protocol_id);
pirinfo->ir_parse = ambarella_ir_panasonic_parse;
ambarella_ir_get_panasonic_info(&pirinfo->frame_info);
break;
case AMBA_IR_PROTOCOL_SONY:
dev_notice(&pirinfo->input->dev,
"Protocol SONY[%d]\n", protocol_id);
pirinfo->ir_parse = ambarella_ir_sony_parse;
ambarella_ir_get_sony_info(&pirinfo->frame_info);
break;
case AMBA_IR_PROTOCOL_PHILIPS:
dev_notice(&pirinfo->input->dev,
"Protocol PHILIPS[%d]\n", protocol_id);
pirinfo->ir_parse = ambarella_ir_philips_parse;
ambarella_ir_get_philips_info(&pirinfo->frame_info);
break;
default:
dev_notice(&pirinfo->input->dev,
"Protocol default NEC[%d]\n", protocol_id);
pirinfo->ir_parse = ambarella_ir_nec_parse;
ambarella_ir_get_nec_info(&pirinfo->frame_info);
break;
}
}
static void ambarella_ir_init(struct ambarella_ir_info *pirinfo)
{
ambarella_ir_disable(pirinfo);
clk_set_rate(clk_get(NULL, "gclk_ir"), 13000);
ambarella_ir_set_protocol(pirinfo, pirinfo->protocol);
ambarella_ir_enable(pirinfo);
}
static int ambarella_ir_of_parse(struct platform_device *pdev,
struct ambarella_ir_info *pirinfo)
{
struct device_node *np = pdev->dev.of_node;
struct ambarella_ir_keymap *keymap;
const __be32 *prop;
int rval, i, size;
rval = of_property_read_u32(np, "amb,protocol", &pirinfo->protocol);
if (rval < 0 || pirinfo->protocol >= AMBA_IR_PROTOCOL_END)
pirinfo->protocol = AMBA_IR_PROTOCOL_NEC;
pirinfo->print_key = !!of_find_property(np, "amb,print-key", NULL);
prop = of_get_property(np, "amb,keymap", &size);
if (!prop || size % (sizeof(__be32) * 2)) {
dev_err(&pdev->dev, "Invalid keymap!\n");
return -ENOENT;
}
/* cells is 2 for each keymap */
size /= sizeof(__be32) * 2;
pirinfo->key_num = size;
pirinfo->keymap = devm_kzalloc(&pdev->dev,
sizeof(struct ambarella_ir_keymap) * size, GFP_KERNEL);
if (pirinfo->keymap == NULL){
dev_err(&pdev->dev, "No memory!\n");
return -ENOMEM;
}
keymap = pirinfo->keymap;
for (i = 0; i < size; i++) {
keymap->key_raw = be32_to_cpup(prop + i * 2);
keymap->key_code = be32_to_cpup(prop + i * 2 + 1);
input_set_capability(pirinfo->input, EV_KEY, keymap->key_code);
keymap++;
}
return 0;
}
static int ambarella_ir_probe(struct platform_device *pdev)
{
struct ambarella_ir_info *pirinfo;
struct input_dev *input;
struct resource *mem;
int retval;
pirinfo = devm_kzalloc(&pdev->dev,
sizeof(struct ambarella_ir_info), GFP_KERNEL);
if (!pirinfo) {
dev_err(&pdev->dev, "Failed to allocate pirinfo!\n");
return -ENOMEM;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL) {
dev_err(&pdev->dev, "Get mem resource failed!\n");
return -ENXIO;
}
pirinfo->regbase = devm_ioremap(&pdev->dev,
mem->start, resource_size(mem));
if (!pirinfo->regbase) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
return -ENOMEM;
}
pirinfo->irq = platform_get_irq(pdev, 0);
if (pirinfo->irq < 0) {
dev_err(&pdev->dev, "Get irq failed!\n");
return -ENXIO;
}
input = input_allocate_device();
if (!input) {
dev_err(&pdev->dev, "input_allocate_device fail!\n");
return -ENOMEM;
}
input->name = "AmbIR";
input->phys = "ambir/input0";
input->id.bustype = BUS_HOST;
retval = input_register_device(input);
if (retval) {
dev_err(&pdev->dev, "Register input_dev failed!\n");
goto ir_err0;
}
pirinfo->input = input;
retval = ambarella_ir_of_parse(pdev, pirinfo);
if (retval)
goto ir_err1;
platform_set_drvdata(pdev, pirinfo);
retval = devm_request_irq(&pdev->dev, pirinfo->irq,
ambarella_ir_irq, IRQF_TRIGGER_HIGH,
dev_name(&pdev->dev), pirinfo);
if (retval < 0) {
dev_err(&pdev->dev, "Request IRQ failed!\n");
goto ir_err1;
}
ambarella_ir_init(pirinfo);
dev_notice(&pdev->dev, "IR Host Controller probed!\n");
return 0;
ir_err1:
input_unregister_device(input);
ir_err0:
input_free_device(input);
return retval;
}
static int ambarella_ir_remove(struct platform_device *pdev)
{
struct ambarella_ir_info *pirinfo;
int retval = 0;
pirinfo = platform_get_drvdata(pdev);
input_unregister_device(pirinfo->input);
input_free_device(pirinfo->input);
dev_notice(&pdev->dev, "Remove Ambarella IR Host Controller.\n");
return retval;
}
#if (defined CONFIG_PM)
static int ambarella_ir_suspend(struct platform_device *pdev,
pm_message_t state)
{
int retval = 0;
struct ambarella_ir_info *pirinfo;
pirinfo = platform_get_drvdata(pdev);
disable_irq(pirinfo->irq);
amba_clrbitsl(pirinfo->regbase + IR_CONTROL_OFFSET, IR_CONTROL_INTENB);
dev_dbg(&pdev->dev, "%s exit with %d @ %d\n",
__func__, retval, state.event);
return retval;
}
static int ambarella_ir_resume(struct platform_device *pdev)
{
int retval = 0;
struct ambarella_ir_info *pirinfo;
pirinfo = platform_get_drvdata(pdev);
clk_set_rate(clk_get(NULL, "gclk_ir"), 13000);
ambarella_ir_enable(pirinfo);
dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, retval);
return retval;
}
#endif
static const struct of_device_id ambarella_ir_dt_ids[] = {
{.compatible = "ambarella,ir", },
{},
};
MODULE_DEVICE_TABLE(of, ambarella_ir_dt_ids);
static struct platform_driver ambarella_ir_driver = {
.probe = ambarella_ir_probe,
.remove = ambarella_ir_remove,
#if (defined CONFIG_PM)
.suspend = ambarella_ir_suspend,
.resume = ambarella_ir_resume,
#endif
.driver = {
.name = "ambarella-ir",
.owner = THIS_MODULE,
.of_match_table = ambarella_ir_dt_ids,
},
};
module_platform_driver(ambarella_ir_driver);
MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
MODULE_DESCRIPTION("Ambarella IR Input Driver");
MODULE_LICENSE("GPL");