| /* |
| * ImgTec IR Raw Decoder found in PowerDown Controller. |
| * |
| * Copyright 2010-2014 Imagination Technologies Ltd. |
| * |
| * 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 ties into the input subsystem using the RC-core in raw mode. Raw IR |
| * signal edges are reported and decoded by generic software decoders. |
| */ |
| |
| #include <linux/spinlock.h> |
| #include <media/rc-core.h> |
| #include "img-ir.h" |
| |
| #define ECHO_TIMEOUT_MS 150 /* ms between echos */ |
| |
| /* must be called with priv->lock held */ |
| static void img_ir_refresh_raw(struct img_ir_priv *priv, u32 irq_status) |
| { |
| struct img_ir_priv_raw *raw = &priv->raw; |
| struct rc_dev *rc_dev = priv->raw.rdev; |
| int multiple; |
| u32 ir_status; |
| |
| /* find whether both rise and fall was detected */ |
| multiple = ((irq_status & IMG_IR_IRQ_EDGE) == IMG_IR_IRQ_EDGE); |
| /* |
| * If so, we need to see if the level has actually changed. |
| * If it's just noise that we didn't have time to process, |
| * there's no point reporting it. |
| */ |
| ir_status = img_ir_read(priv, IMG_IR_STATUS) & IMG_IR_IRRXD; |
| if (multiple && ir_status == raw->last_status) |
| return; |
| raw->last_status = ir_status; |
| |
| /* report the edge to the IR raw decoders */ |
| if (ir_status) /* low */ |
| ir_raw_event_store_edge(rc_dev, false); |
| else /* high */ |
| ir_raw_event_store_edge(rc_dev, true); |
| ir_raw_event_handle(rc_dev); |
| } |
| |
| /* called with priv->lock held */ |
| void img_ir_isr_raw(struct img_ir_priv *priv, u32 irq_status) |
| { |
| struct img_ir_priv_raw *raw = &priv->raw; |
| |
| /* check not removing */ |
| if (!raw->rdev) |
| return; |
| |
| img_ir_refresh_raw(priv, irq_status); |
| |
| /* start / push back the echo timer */ |
| mod_timer(&raw->timer, jiffies + msecs_to_jiffies(ECHO_TIMEOUT_MS)); |
| } |
| |
| /* |
| * Echo timer callback function. |
| * The raw decoders expect to get a final sample even if there are no edges, in |
| * order to be assured of the final space. If there are no edges for a certain |
| * time we use this timer to emit a final sample to satisfy them. |
| */ |
| static void img_ir_echo_timer(unsigned long arg) |
| { |
| struct img_ir_priv *priv = (struct img_ir_priv *)arg; |
| |
| spin_lock_irq(&priv->lock); |
| |
| /* check not removing */ |
| if (priv->raw.rdev) |
| /* |
| * It's safe to pass irq_status=0 since it's only used to check |
| * for double edges. |
| */ |
| img_ir_refresh_raw(priv, 0); |
| |
| spin_unlock_irq(&priv->lock); |
| } |
| |
| void img_ir_setup_raw(struct img_ir_priv *priv) |
| { |
| u32 irq_en; |
| |
| if (!priv->raw.rdev) |
| return; |
| |
| /* clear and enable edge interrupts */ |
| spin_lock_irq(&priv->lock); |
| irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE); |
| irq_en |= IMG_IR_IRQ_EDGE; |
| img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE); |
| img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en); |
| spin_unlock_irq(&priv->lock); |
| } |
| |
| int img_ir_probe_raw(struct img_ir_priv *priv) |
| { |
| struct img_ir_priv_raw *raw = &priv->raw; |
| struct rc_dev *rdev; |
| int error; |
| |
| /* Set up the echo timer */ |
| setup_timer(&raw->timer, img_ir_echo_timer, (unsigned long)priv); |
| |
| /* Allocate raw decoder */ |
| raw->rdev = rdev = rc_allocate_device(RC_DRIVER_IR_RAW); |
| if (!rdev) { |
| dev_err(priv->dev, "cannot allocate raw input device\n"); |
| return -ENOMEM; |
| } |
| rdev->priv = priv; |
| rdev->map_name = RC_MAP_EMPTY; |
| rdev->device_name = "IMG Infrared Decoder Raw"; |
| |
| /* Register raw decoder */ |
| error = rc_register_device(rdev); |
| if (error) { |
| dev_err(priv->dev, "failed to register raw IR input device\n"); |
| rc_free_device(rdev); |
| raw->rdev = NULL; |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| void img_ir_remove_raw(struct img_ir_priv *priv) |
| { |
| struct img_ir_priv_raw *raw = &priv->raw; |
| struct rc_dev *rdev = raw->rdev; |
| u32 irq_en; |
| |
| if (!rdev) |
| return; |
| |
| /* switch off and disable raw (edge) interrupts */ |
| spin_lock_irq(&priv->lock); |
| raw->rdev = NULL; |
| irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE); |
| irq_en &= ~IMG_IR_IRQ_EDGE; |
| img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en); |
| img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE); |
| spin_unlock_irq(&priv->lock); |
| |
| rc_unregister_device(rdev); |
| |
| del_timer_sync(&raw->timer); |
| } |