blob: 2627bd1924122f8d9aee11e624ee46dee68f28c9 [file] [log] [blame]
/*
* drivers/amlogic/irblaster/irblaster.c
*
* Copyright (C) 2015 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include "meson-irblaster.h"
#undef pr_fmt
#define pr_fmt(fmt) "irblaster: " fmt
#define BLASTER_NAME "meson-irblaster"
static int debug_enable;
static DEFINE_MUTEX(blaster_mutex);
#define irblaster_dbg(format, arg...) \
do { \
if (debug_enable) \
pr_info(format, ##arg); \
} while (0)
static int write_to_fifo(struct aml_irblaster_dev *dev,
unsigned int hightime, unsigned int lowtime)
{
unsigned int count_delay;
unsigned int high_ct, low_ct;
unsigned int cycle = 1000 / (dev->carrier_freqs / 1000);
uint32_t val;
int n = 0;
int tb[3] = {
1, 10, 100
};
/*
*1. set mod_high_count = 13
*2. set mod_low_count = 13
*3. 60khz-8us, 38k-13us
*/
high_ct = cycle * dev->duty_cycle / 100;
low_ct = cycle - high_ct;
writel((BLASTER_MODULATION_LOW_COUNT(low_ct - 1) |
BLASTER_MODULATION_HIGH_COUNT(high_ct - 1)),
dev->reg_base + AO_IR_BLASTER_ADDR1);
/*
hightime: modulator signal.
MODULATOR_TB:
00: system clock
01: mpeg_xtal3_tick
10: mpeg_1uS_tick
11: mpeg_10uS_tick
AO_IR_BLASTER_ADDR2
bit12: output level(or modulation enable/disable:1=enable)
bit[11:10]: Timebase :
00=1us
01=10us
10=100us
11=Modulator clock
bit[9:0]: Count of timebase units to delay
*/
count_delay = (((hightime + cycle/2) / cycle) - 1) & COUNT_DELAY_MASK;
val = (BLASTER_WRITE_FIFO | BLASTER_MODULATION_ENABLE |
BLASTER_TIMEBASE_MODULATION_CLOCK | (count_delay << 0));
writel(val, dev->reg_base + AO_IR_BLASTER_ADDR2);
/*
lowtime<1024,n=0,timebase=1us
1024<=lowtime<10240,n=1,timebase=10us
10240<=lowtime,n=2,timebase=100us
*/
n = lowtime >> 10;
if (n > 0 && n < 10)
n = 1;
else if (n >= 10)
n = 2;
lowtime = (lowtime + (tb[n] >> 1))/tb[n];
count_delay = (lowtime-1) & COUNT_DELAY_MASK;
val = (BLASTER_WRITE_FIFO & (~BLASTER_MODULATION_ENABLE)) |
(n << TIMEBASE_SHIFT) | (count_delay << 0);
writel(val, dev->reg_base + AO_IR_BLASTER_ADDR2);
return 0;
}
static void send_all_data(struct aml_irblaster_dev *dev)
{
int i;
unsigned int *pdata = NULL;
irblaster_dbg("dev->count = %d\n", dev->count);
pdata = &dev->buffer[dev->count];
for (i = 0; (i < 120) &&
(dev->count < dev->buffer_size);) {
write_to_fifo(dev, *pdata, *(pdata + 1));
pdata += 2;
dev->count += 2;
i += 2;
}
if (dev->count >= dev->buffer_size) {
irblaster_dbg("The all datas finished!\n");
//complete(&dev->blaster_completion);
}
}
static irqreturn_t meson_blaster_interrupt(int irq, void *dev_id)
{
struct aml_irblaster_dev *dev = dev_id;
irblaster_dbg("meson_blaster_interrupt !!\n");
complete(&dev->blaster_completion);
/*clear pending bit*/
writel(readl(dev->reg_base + AO_IR_BLASTER_ADDR3) &
(~BLASTER_FIFO_THD_PENDING),
dev->reg_base + AO_IR_BLASTER_ADDR3);
schedule_work(&dev->blaster_work);
return IRQ_HANDLED;
}
int set_carrier_freqs(struct aml_irblaster_dev *dev, int carrier_freqs)
{
if (carrier_freqs > MAX_FREQ || carrier_freqs < LIMIT_FREQ) {
pr_info("freqs[%d,%d]\n", LIMIT_FREQ, MAX_FREQ);
return -1;
}
dev->carrier_freqs = carrier_freqs;
return 0;
}
static int get_carrier_freqs(struct aml_irblaster_dev *dev)
{
return dev->carrier_freqs;
}
static int set_duty_cycle(struct aml_irblaster_dev *dev, int duty_cycle)
{
if (duty_cycle > MAX_DUTY || duty_cycle < LIMIT_DUTY) {
pr_info("duty_cycle[%d,%d]\n", LIMIT_DUTY, MAX_DUTY);
return -1;
}
dev->duty_cycle = duty_cycle;
return 0;
}
static int aml_irblaster_open(struct inode *inode, struct file *file)
{
struct aml_irblaster_dev *dev = NULL;
dev = container_of(inode->i_cdev,
struct aml_irblaster_dev, blaster_cdev);
file->private_data = dev;
return 0;
}
static int send(struct aml_irblaster_dev *dev, const char *buf, int len)
{
int i = 0, j = 0, m = 0, ret = 0;
int val;
char tone[PS_SIZE];
unsigned int sum_time = 0;
for (i = 0; i < len; i++) {
if (buf[i] == '\0') {
break;
} else if (buf[i] == 's') {
tone[j] = '\0';
ret = kstrtoint(tone, 10, &val);
if (ret) {
pr_err("Invalid tone\n");
return ret;
}
dev->buffer[m] = val*10;
sum_time = sum_time + val*10;
j = 0;
m++;
if (m >= MAX_PLUSE)
break;
continue;
}
tone[j] = buf[i];
j++;
if (j >= PS_SIZE) {
pr_err("send timing value is out of range\n");
return -ENOMEM;
}
}
dev->buffer_size = m;
dev->count = 0;
send_all_data(dev);
ret = wait_for_completion_interruptible_timeout(
&dev->blaster_completion, msecs_to_jiffies(sum_time / 1000));
if (!ret)
pr_err("failed to send all data\n");
return ret;
}
static long aml_irblaster_ioctl(struct file *filp, unsigned int cmd,
unsigned long args)
{
int carrier_freqs = 0, duty_cycle = 0;
static int psize;
s32 r = 0;
void __user *argp = (void __user *)args;
char *sendcode = NULL;
struct aml_irblaster_dev *dev = filp->private_data;
irblaster_dbg("aml_irblaster_ioctl() 0x%4x\n ", cmd);
switch (cmd) {
case CONSUMERIR_TRANSMIT:
psize = psize ? psize:MAX_PLUSE;
sendcode = kcalloc(psize, sizeof(char), GFP_KERNEL);
if (sendcode == NULL) {
pr_err("can't get sendcode memory\n");
return -ENOMEM;
}
if (copy_from_user(sendcode, (char *)argp,
psize))
return -EFAULT;
pr_info("send code is %s\n", sendcode);
mutex_lock(&blaster_mutex);
r = send(dev, sendcode, psize);
mutex_unlock(&blaster_mutex);
kfree(sendcode);
break;
case GET_CARRIER:
pr_info("in get freq\n");
carrier_freqs = get_carrier_freqs(dev);
put_user(carrier_freqs, (int *)argp);
return carrier_freqs;
case SET_CARRIER:
pr_info("in set freq\n");
if (get_user(carrier_freqs, (int *)argp) < 0)
return -EFAULT;
mutex_lock(&blaster_mutex);
r = set_carrier_freqs(dev, carrier_freqs);
mutex_unlock(&blaster_mutex);
break;
case SET_DUTYCYCLE:
pr_info("in set duty_cycle\n");
if (copy_from_user(&duty_cycle, argp, sizeof(int)))
return -EFAULT;
get_user(duty_cycle, (int *)argp);
mutex_lock(&blaster_mutex);
r = set_duty_cycle(dev, duty_cycle);
mutex_unlock(&blaster_mutex);
break;
default:
r = -ENOIOCTLCMD;
break;
}
return r;
}
static int aml_irblaster_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t show_debug(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (debug_enable)
sprintf(buf, "debug=enable\n");
else
sprintf(buf, "debug=disable\n");
return strlen(buf);
}
static ssize_t store_debug(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (!strncmp(buf, "enable", 1)) {
debug_enable = 1;
pr_info("enable debug\n");
} else if (!strncmp(buf, "disable", 1)) {
debug_enable = 0;
pr_info("disable debug\n");
}
return strlen(buf);
}
static ssize_t show_carrier_freq(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct aml_irblaster_dev *irblaster_dev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", irblaster_dev->carrier_freqs);
}
static ssize_t store_carrier_freq(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret = 0, val;
struct aml_irblaster_dev *irblaster_dev = dev_get_drvdata(dev);
mutex_lock(&blaster_mutex);
ret = kstrtoint(buf, 10, &val);
if (ret) {
pr_err("Invalid input for carrier_freq\n");
mutex_unlock(&blaster_mutex);
return ret;
}
ret = set_carrier_freqs(irblaster_dev, val);
mutex_unlock(&blaster_mutex);
if (ret)
return ret;
pr_info("carrier_freq is %d\n", irblaster_dev->carrier_freqs);
return count;
}
static ssize_t show_duty_cycle(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct aml_irblaster_dev *irblaster_dev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", irblaster_dev->duty_cycle);
}
static ssize_t store_duty_cycle(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret = 0, val;
struct aml_irblaster_dev *irblaster_dev = dev_get_drvdata(dev);
mutex_lock(&blaster_mutex);
ret = kstrtoint(buf, 10, &val);
if (ret) {
pr_err("Invalid input for duty_cycle\n");
mutex_unlock(&blaster_mutex);
return ret;
}
ret = set_duty_cycle(irblaster_dev, val);
mutex_unlock(&blaster_mutex);
if (ret)
return ret;
pr_info("duty_cycle is %d\n", irblaster_dev->duty_cycle);
return count;
}
static ssize_t store_send(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct aml_irblaster_dev *irblaster_dev = dev_get_drvdata(dev);
int i = 0, j = 0, m = 0, ret = 0;
int val;
char tone[PS_SIZE];
unsigned int sum_time = 0;
mutex_lock(&blaster_mutex);
while (buf[i] != '\0') {
if (buf[i] == 's') {
tone[j] = '\0';
ret = kstrtoint(tone, 10, &val);
if (ret) {
pr_err("Invalid tone\n");
mutex_unlock(&blaster_mutex);
return ret;
}
irblaster_dev->buffer[m] = val*10;
sum_time = sum_time + val*10;
j = 0;
i++;
m++;
if (m >= MAX_PLUSE)
break;
continue;
}
tone[j] = buf[i];
i++;
j++;
if (j >= PS_SIZE) {
pr_err("send timing value is out of range\n");
mutex_unlock(&blaster_mutex);
return -ENOMEM;
}
}
irblaster_dev->buffer_size = m;
irblaster_dbg("irblaster_dev->buffer_size = %d\n",
irblaster_dev->buffer_size);
irblaster_dbg("irblaster_dev->buffer =\n ");
for (i = 0; i < irblaster_dev->buffer_size; i++)
irblaster_dbg("%d\n", irblaster_dev->buffer[i]);
irblaster_dbg("sum_time = %d\n", sum_time);
irblaster_dev->count = 0;
init_completion(&irblaster_dev->blaster_completion);
send_all_data(irblaster_dev);
ret = wait_for_completion_interruptible_timeout(
&irblaster_dev->blaster_completion,
msecs_to_jiffies(sum_time / 1000));
if (ret)
pr_err("failed to send all data\n");
mutex_unlock(&blaster_mutex);
return count;
}
static DEVICE_ATTR(debug, 0644, show_debug, store_debug);
static DEVICE_ATTR(send, 0200, NULL, store_send);
static DEVICE_ATTR(carrier_freq, 0644,
show_carrier_freq, store_carrier_freq);
static DEVICE_ATTR(duty_cycle, 0644,
show_duty_cycle, store_duty_cycle);
static struct attribute *irblaster_attributes[] = {
&dev_attr_debug.attr,
&dev_attr_send.attr,
&dev_attr_carrier_freq.attr,
&dev_attr_duty_cycle.attr,
NULL,
};
static struct attribute_group irblaster_attribute_group = {
.attrs = irblaster_attributes,
};
const struct attribute_group *irblaster_attribute_groups[] = {
&irblaster_attribute_group,
NULL,
};
static const struct file_operations blaster_fops = {
.owner = THIS_MODULE,
.open = aml_irblaster_open,
.compat_ioctl = aml_irblaster_ioctl,
.unlocked_ioctl = aml_irblaster_ioctl,
.release = aml_irblaster_release,
};
static void do_blaster_send(struct work_struct *work)
{
struct aml_irblaster_dev *dev = container_of(work,
struct aml_irblaster_dev, blaster_work);
if (dev->count < dev->buffer_size)
send_all_data(dev);
}
static void blaster_initialize(struct aml_irblaster_dev *dev)
{
unsigned int carrier_cycle = 1000 / (dev->carrier_freqs / 1000);
unsigned int high_ct, low_ct;
/*
*1. disable ir blaster
*2. set the modulator_tb = 2'10; mpeg_1uS_tick 1us
*3. set initializes the output to be high
*/
writel((~BLASTER_ENABLE) & (BLASTER_MODULATOR_TB_1US_TICK |
BLASTER_INIT_HIGH), dev->reg_base + AO_IR_BLASTER_ADDR0);
/*
*1. set mod_high_count = 13
*2. set mod_low_count = 13
*3. 60khz-8us, 38k-13us
*/
high_ct = carrier_cycle * dev->duty_cycle / 100;
low_ct = carrier_cycle - high_ct;
writel((BLASTER_MODULATION_LOW_COUNT(low_ct - 1) |
BLASTER_MODULATION_HIGH_COUNT(high_ct - 1)),
dev->reg_base + AO_IR_BLASTER_ADDR1);
/*mask initialize output to be high*/
writel(readl(dev->reg_base + AO_IR_BLASTER_ADDR0) &
~BLASTER_INIT_HIGH,
dev->reg_base + AO_IR_BLASTER_ADDR0);
/*
*1. set fifo irq enable
*2. set fifo irq threshold
*/
writel(BLASTER_FIFO_IRQ_ENABLE |
BLASTER_FIFO_IRQ_THRESHOLD(8),
dev->reg_base + AO_IR_BLASTER_ADDR3);
/*enable irblaster*/
writel(readl(dev->reg_base + AO_IR_BLASTER_ADDR0) |
BLASTER_ENABLE,
dev->reg_base + AO_IR_BLASTER_ADDR0);
}
static int aml_irblaster_probe(struct platform_device *pdev)
{
int ret = 0;
struct aml_irblaster_dev *irblaster_dev = NULL;
struct pinctrl *irblaster_pinctrl = NULL;
struct resource *reg_mem = NULL;
struct resource *res_irq = NULL;
struct resource *reset_mem = NULL;
void __iomem *reg_base = NULL;
void __iomem *reset_base = NULL;
pr_info("probe\n");
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "pdev->dev.of_node == NULL!\n");
return -1;
}
irblaster_pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(irblaster_pinctrl)) {
dev_err(&pdev->dev, "pinctrl error.\n");
return PTR_ERR(irblaster_pinctrl);
}
irblaster_dev = devm_kzalloc(&pdev->dev,
sizeof(struct aml_irblaster_dev), GFP_KERNEL);
if (!irblaster_dev)
return -ENOMEM;
platform_set_drvdata(pdev, irblaster_dev);
irblaster_dev->dev = &pdev->dev;
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res_irq)
return -ENODEV;
reg_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!IS_ERR_OR_NULL(reg_mem)) {
reg_base = devm_ioremap_resource(&pdev->dev, reg_mem);
if (IS_ERR(reg_base)) {
dev_err(&pdev->dev, "reg0: cannot obtain I/O memory region.\n");
return PTR_ERR(reg_base);
}
} else {
dev_err(&pdev->dev, "get IORESOURCE_MEM error.\n");
return PTR_ERR(reg_base);
}
reset_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!IS_ERR_OR_NULL(reset_mem)) {
reset_base = devm_ioremap_resource(&pdev->dev,
reset_mem);
if (IS_ERR(reset_base)) {
dev_err(&pdev->dev, "reg1: cannot obtain I/O memory region.\n");
return PTR_ERR(reset_base);
}
} else {
dev_err(&pdev->dev, "get IORESOURCE_MEM error.\n");
return PTR_ERR(reset_mem);
}
irblaster_dev->carrier_freqs = DEFAULT_CARRIER_FREQ;
irblaster_dev->duty_cycle = DEFAULT_DUTY_CYCLE;
irblaster_dev->reg_base = reg_base;
irblaster_dev->reset_base = reset_base;
irblaster_dev->irq = res_irq->start;
ret = alloc_chrdev_region(&irblaster_dev->blaster_cdev.dev, 0,
BLASTER_DEVICE_COUNT,
BLASTER_NAME);
if (ret < 0) {
pr_err("Failed to alloc cdev major device\n");
return ret;
}
cdev_init(&irblaster_dev->blaster_cdev, &blaster_fops);
irblaster_dev->blaster_cdev.owner = THIS_MODULE;
ret = cdev_add(&irblaster_dev->blaster_cdev,
irblaster_dev->blaster_cdev.dev,
BLASTER_DEVICE_COUNT);
if (ret)
goto err0;
irblaster_dev->blaster_class = class_create(THIS_MODULE,
BLASTER_NAME);
if (IS_ERR(irblaster_dev->blaster_class)) {
pr_err("Failed to class create\n");
ret = PTR_ERR(irblaster_dev->blaster_class);
goto err1;
}
irblaster_dev->dev = device_create_with_groups(
irblaster_dev->blaster_class,
NULL,
irblaster_dev->blaster_cdev.dev,
irblaster_dev,
irblaster_attribute_groups,
"irblaster%d", 1);
if (IS_ERR(irblaster_dev->dev)) {
pr_err("Failed to device create\n");
ret = PTR_ERR(irblaster_dev->dev);
goto err2;
}
init_completion(&irblaster_dev->blaster_completion);
INIT_WORK(&irblaster_dev->blaster_work, do_blaster_send);
ret = devm_request_irq(&pdev->dev, irblaster_dev->irq,
meson_blaster_interrupt,
IRQF_TRIGGER_RISING,
dev_name(&pdev->dev),
irblaster_dev);
if (ret) {
pr_err("Failed to request irq number\n");
goto err2;
}
/*initial blaster*/
blaster_initialize(irblaster_dev);
return 0;
err2:
class_destroy(irblaster_dev->blaster_class);
err1:
cdev_del(&irblaster_dev->blaster_cdev);
err0:
unregister_chrdev_region(irblaster_dev->blaster_cdev.dev,
BLASTER_DEVICE_COUNT);
return ret;
}
static int aml_irblaster_remove(struct platform_device *pdev)
{
struct aml_irblaster_dev *irblaster_dev = platform_get_drvdata(pdev);
device_destroy(irblaster_dev->blaster_class,
irblaster_dev->blaster_cdev.dev);
class_destroy(irblaster_dev->blaster_class);
cdev_del(&irblaster_dev->blaster_cdev);
unregister_chrdev_region(irblaster_dev->blaster_cdev.dev,
BLASTER_DEVICE_COUNT);
return 0;
}
static const struct of_device_id irblaster_dt_match[] = {
{
.compatible = "amlogic, meson_irblaster",
},
{},
};
MODULE_DEVICE_TABLE(of, irblaster_dt_match);
static struct platform_driver aml_irblaster_driver = {
.probe = aml_irblaster_probe,
.remove = aml_irblaster_remove,
.driver = {
.name = "meson_irblaster",
.owner = THIS_MODULE,
.of_match_table = irblaster_dt_match,
},
};
static int __init aml_irblaster_init(void)
{
return platform_driver_register(&aml_irblaster_driver);
}
static void __exit aml_irblaster_exit(void)
{
platform_driver_unregister(&aml_irblaster_driver);
}
module_init(aml_irblaster_init);
module_exit(aml_irblaster_exit);
MODULE_AUTHOR("Amlogic, Inc.");
MODULE_DESCRIPTION("Amlogic Meson ir blaster driver");
MODULE_LICENSE("GPL");