diff options
Diffstat (limited to 'arch/arm/mach-imx/mx6_ir.c')
-rw-r--r-- | arch/arm/mach-imx/mx6_ir.c | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/mx6_ir.c b/arch/arm/mach-imx/mx6_ir.c new file mode 100644 index 000000000000..21aabd531c99 --- /dev/null +++ b/arch/arm/mach-imx/mx6_ir.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2005-2014 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/fsl_devices.h> +#include <linux/ir.h> +#include "mxc_ir.h" + +#ifdef IR_HRTIMER_USED +#include <linux/hrtimer.h> +#include <linux/ktime.h> +#else +#include "epit.h" +#endif + +#define IR_MAX_CARRIER_FREQ (11000000) +#define IR_MIN_CARRIER_FREQ (3000) + +struct mx6_ir_device { + struct pwm_device *pwm; +#ifdef IR_HRTIMER_USED + struct hrtimer hr_timer; +#else + struct epit_device *epit; +#endif + int pattern_len; + int *pattern; + int transmit_idx; + int transmit_level; + struct device *dev; + wait_queue_head_t queue; + bool cond; + unsigned int carry_freq; +}; + +struct carrier_freq { + int min; + int max; +}; + +static const struct carrier_freq mx6_ir_carrier_freq[] = { + {IR_MIN_CARRIER_FREQ, IR_MAX_CARRIER_FREQ}, }; + +int ir_config(void *dev, int carry_freq) +{ + unsigned int period_ns; + struct mx6_ir_device *ir_dev = (struct mx6_ir_device *)dev; + + if ((!ir_dev) || (!carry_freq)) + return -EINVAL; + + if (!ir_dev->pwm) + return -EINVAL; + + period_ns = 1000000000; + do_div(period_ns, carry_freq); + pwm_config(ir_dev->pwm, (period_ns >> 1), period_ns); + pwm_out_enable(ir_dev->pwm, 0); + return 0; +} +EXPORT_SYMBOL(ir_config); + +int ir_get_carrier_range(int id, int *min, int *max) +{ + if ((!min) || (!max)) { + return -1; + } else { + *min = mx6_ir_carrier_freq[id].min; + *max = mx6_ir_carrier_freq[id].max; + return 0; + } +} +EXPORT_SYMBOL(ir_get_carrier_range); + +int ir_get_num_carrier_freqs(void) +{ + return ARRAY_SIZE(mx6_ir_carrier_freq); +} +EXPORT_SYMBOL(ir_get_num_carrier_freqs); + + +#ifdef IR_HRTIMER_USED +static void *hrtimer_cb_para; + +static inline void ir_hrtimer_start(struct hrtimer *hr_timer, int width_ns, + void *cb, void *para) +{ + ktime_t ktime; + + ktime = ktime_set(0, width_ns); + hrtimer_init(hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hr_timer->function = cb; + hrtimer_cb_para = para; + hrtimer_start(hr_timer, ktime, HRTIMER_MODE_REL); +} + +static inline void ir_hrtimer_stop(struct hrtimer *hr_timer) +{ + hrtimer_cancel(hr_timer); +} + +static int ir_transmit_cb(struct hrtimer *timer) +{ + struct mx6_ir_device *ir_dev = (struct mx6_ir_device *)hrtimer_cb_para; + ktime_t ktime; + + ir_dev->transmit_idx++; + if (ir_dev->pattern_len == ir_dev->transmit_idx) { + /* all bit transmit is done */ + /* stop pwm output */ + pwm_out_enable(ir_dev->pwm, 0); + ir_dev->cond = true; + wake_up(&ir_dev->queue); + return HRTIMER_NORESTART; + } else { + /* start next bit transmit */; + ir_dev->transmit_level = !ir_dev->transmit_level; + ktime = ktime_set(0, ir_dev->pattern[ir_dev->transmit_idx] * 1000); + hrtimer_forward(&ir_dev->hr_timer, + ktime_get(), + ktime); + pwm_out_enable(ir_dev->pwm, ir_dev->transmit_level); + return HRTIMER_RESTART; + } +} + +int ir_transmit(void *dev, int len, int *pattern, unsigned char start_level) +{ + struct mx6_ir_device *ir_dev = (struct mx6_ir_device *)dev; + char level = start_level; + + if ((!ir_dev) || (!pattern)) + return -EINVAL; + + if (!ir_dev->pwm) + return -EINVAL; + + ir_dev->transmit_level = start_level; + ir_dev->transmit_idx = 0; + ir_dev->pattern = pattern; + ir_dev->pattern_len = len; + ir_dev->cond = false; + /* + 1. Enable/disable pwm output per level, start timer with width + 2. time cb will start next bit transmit,wait all bit transmit done + 3. disable pwm output + */ + pwm_enable(ir_dev->pwm); + ir_dev->cond = false; + ir_hrtimer_start(&ir_dev->hr_timer, + (ir_dev->pattern[0] * 1000), + (void *)ir_transmit_cb, + (void *)(ir_dev)); + pwm_out_enable(ir_dev->pwm, ir_dev->transmit_level); + wait_event(ir_dev->queue, ir_dev->cond); + ir_hrtimer_stop(&ir_dev->hr_timer); + pwm_disable(ir_dev->pwm); + return 0; +} +#else +static int ir_transmit_cb(void *dev) +{ + struct mx6_ir_device *ir_dev = (struct mx6_ir_device *)dev; + ir_dev->transmit_idx++; + if (ir_dev->pattern_len == ir_dev->transmit_idx) { + /* all bit transmit is done */ + /* stop pwm output */ + pwm_out_enable(ir_dev->pwm, 0); + ir_dev->cond = true; + wake_up(&ir_dev->queue); + } else { + /* start next bit transmit */; + ir_dev->transmit_level = !ir_dev->transmit_level; + pwm_out_enable(ir_dev->pwm, ir_dev->transmit_level); + epit_start(ir_dev->epit, + (ir_dev->pattern[ir_dev->transmit_idx] * 1000)); + + } + return 0; +} + +int ir_transmit(void *dev, int len, int *pattern, unsigned char start_level) +{ + struct mx6_ir_device *ir_dev = (struct mx6_ir_device *)dev; + + if ((!ir_dev) || (!pattern)) + return -EINVAL; + + if ((!ir_dev->pwm) || (!ir_dev->epit)) + return -EINVAL; + + ir_dev->transmit_level = start_level; + ir_dev->transmit_idx = 0; + ir_dev->pattern = pattern; + ir_dev->pattern_len = len; + ir_dev->cond = false; + /* + 1. Enable/disable pwm output per level, start timer with width + 2. time cb will start next bit transmit,wait all bit transmit done + 3. disable pwm output + */ + pwm_enable(ir_dev->pwm); + epit_config(ir_dev->epit, + EPIT_FREE_RUN_MODE, + (void *)ir_transmit_cb, + (void *)(ir_dev)); + epit_start(ir_dev->epit, + (ir_dev->pattern[0] * 1000)); + pwm_out_enable(ir_dev->pwm, ir_dev->transmit_level); + wait_event(ir_dev->queue, ir_dev->cond); + epit_stop(ir_dev->epit); + pwm_disable(ir_dev->pwm); + return 0; +} +#endif +EXPORT_SYMBOL(ir_transmit); + +#if defined(CONFIG_OF) +static const struct of_device_id mxc_ir_dt_ids[] = { + { .compatible = "fsl,mxc-ir"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxc_ir_dt_ids); +#endif + +static int mx6_ir_probe(struct platform_device *pdev) +{ + struct mx6_ir_device *pb; + int ret = 0; + struct device_node *np = pdev->dev.of_node; +#ifndef IR_HRTIMER_USED + int epit_id = 0; +#endif + + pb = kzalloc(sizeof(*pb), GFP_KERNEL); + if (!pb) { + dev_err(&pdev->dev, "no memory for state\n"); + ret = -ENOMEM; + goto err_alloc; + } + pb->dev = &pdev->dev; + + pb->pwm = devm_pwm_get(&pdev->dev, NULL); + if (IS_ERR(pb->pwm)) { + dev_err(&pdev->dev, "unable to request PWM.\n"); + ret = (PTR_ERR)(pb->pwm); + goto err_pwm; + } + +#ifndef IR_HRTIMER_USED + ret = of_property_read_u32(np, "epit", &epit_id); + if (ret < 0) { + dev_dbg(&pdev->dev, "can not get epit\n"); + goto err_pwm; + } + pb->epit = epit_request(epit_id, "IR Signal"); + if (IS_ERR(pb->epit)) { + dev_err(&pdev->dev, "unable to request EPIT for IR Signal\n"); + ret = PTR_ERR(pb->epit); + goto err_epit; + } else { + dev_dbg(&pdev->dev, "got EPIT for IR Signal\n"); + } +#endif + /* Init queue */ + init_waitqueue_head(&pb->queue); + ir_device_register(dev_name(&pdev->dev), &pdev->dev, pb); + platform_set_drvdata(pdev, pb); + + return 0; + +#ifndef IR_HRTIMER_USED +err_epit: +#endif + devm_pwm_put(&pdev->dev, pb->pwm); +err_pwm: + kfree(pb); +err_alloc: + return ret; +} + +static int mx6_ir_remove(struct platform_device *pdev) +{ + struct mx6_ir_device *pb = platform_get_drvdata(pdev); + ir_device_unregister(); + pwm_disable(pb->pwm); + devm_pwm_put(&pdev->dev, pb->pwm); +#ifndef IR_HRTIMER_USED + epit_stop(pb->epit); + epit_free(pb->epit); +#endif + kfree(pb); + return 0; +} + +static struct platform_driver mx6_ir_driver = { + .probe = mx6_ir_probe, + .remove = mx6_ir_remove, + .driver = { + .name = "mxc_ir", + .owner = THIS_MODULE, + .of_match_table = mxc_ir_dt_ids, + }, +}; + +module_platform_driver(mx6_ir_driver) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IR control interface on MX6 Platform"); + |