root/drivers/media/rc/st_rc.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2013 STMicroelectronics Limited
 * Author: Srinivas Kandagatla <srinivas.kandagatla@st.com>
 */
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <media/rc-core.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_wakeirq.h>

struct st_rc_device {
        struct device                   *dev;
        int                             irq;
        int                             irq_wake;
        struct clk                      *sys_clock;
        void __iomem                    *base;  /* Register base address */
        void __iomem                    *rx_base;/* RX Register base address */
        struct rc_dev                   *rdev;
        bool                            overclocking;
        int                             sample_mult;
        int                             sample_div;
        bool                            rxuhfmode;
        struct  reset_control           *rstc;
};

/* Registers */
#define IRB_SAMPLE_RATE_COMM    0x64    /* sample freq divisor*/
#define IRB_CLOCK_SEL           0x70    /* clock select       */
#define IRB_CLOCK_SEL_STATUS    0x74    /* clock status       */
/* IRB IR/UHF receiver registers */
#define IRB_RX_ON               0x40    /* pulse time capture */
#define IRB_RX_SYS              0X44    /* sym period capture */
#define IRB_RX_INT_EN           0x48    /* IRQ enable (R/W)   */
#define IRB_RX_INT_STATUS       0x4c    /* IRQ status (R/W)   */
#define IRB_RX_EN               0x50    /* Receive enable     */
#define IRB_MAX_SYM_PERIOD      0x54    /* max sym value      */
#define IRB_RX_INT_CLEAR        0x58    /* overrun status     */
#define IRB_RX_STATUS           0x6c    /* receive status     */
#define IRB_RX_NOISE_SUPPR      0x5c    /* noise suppression  */
#define IRB_RX_POLARITY_INV     0x68    /* polarity inverter  */

/*
 * IRQ set: Enable full FIFO                 1  -> bit  3;
 *          Enable overrun IRQ               1  -> bit  2;
 *          Enable last symbol IRQ           1  -> bit  1:
 *          Enable RX interrupt              1  -> bit  0;
 */
#define IRB_RX_INTS             0x0f
#define IRB_RX_OVERRUN_INT      0x04
 /* maximum symbol period (microsecs),timeout to detect end of symbol train */
#define MAX_SYMB_TIME           0x5000
#define IRB_SAMPLE_FREQ         10000000
#define IRB_FIFO_NOT_EMPTY      0xff00
#define IRB_OVERFLOW            0x4
#define IRB_TIMEOUT             0xffff
#define IR_ST_NAME "st-rc"

static void st_rc_send_lirc_timeout(struct rc_dev *rdev)
{
        struct ir_raw_event ev = { .timeout = true, .duration = rdev->timeout };
        ir_raw_event_store(rdev, &ev);
}

/*
 * RX graphical example to better understand the difference between ST IR block
 * output and standard definition used by LIRC (and most of the world!)
 *
 *           mark                                     mark
 *      |-IRB_RX_ON-|                            |-IRB_RX_ON-|
 *      ___  ___  ___                            ___  ___  ___             _
 *      | |  | |  | |                            | |  | |  | |             |
 *      | |  | |  | |         space 0            | |  | |  | |   space 1   |
 * _____| |__| |__| |____________________________| |__| |__| |_____________|
 *
 *      |--------------- IRB_RX_SYS -------------|------ IRB_RX_SYS -------|
 *
 *      |------------- encoding bit 0 -----------|---- encoding bit 1 -----|
 *
 * ST hardware returns mark (IRB_RX_ON) and total symbol time (IRB_RX_SYS), so
 * convert to standard mark/space we have to calculate space=(IRB_RX_SYS-mark)
 * The mark time represents the amount of time the carrier (usually 36-40kHz)
 * is detected.The above examples shows Pulse Width Modulation encoding where
 * bit 0 is represented by space>mark.
 */

static irqreturn_t st_rc_rx_interrupt(int irq, void *data)
{
        unsigned long timeout;
        unsigned int symbol, mark = 0;
        struct st_rc_device *dev = data;
        int last_symbol = 0;
        u32 status, int_status;
        struct ir_raw_event ev = {};

        if (dev->irq_wake)
                pm_wakeup_event(dev->dev, 0);

        /* FIXME: is 10ms good enough ? */
        timeout = jiffies +  msecs_to_jiffies(10);
        do {
                status  = readl(dev->rx_base + IRB_RX_STATUS);
                if (!(status & (IRB_FIFO_NOT_EMPTY | IRB_OVERFLOW)))
                        break;

                int_status = readl(dev->rx_base + IRB_RX_INT_STATUS);
                if (unlikely(int_status & IRB_RX_OVERRUN_INT)) {
                        /* discard the entire collection in case of errors!  */
                        ir_raw_event_overflow(dev->rdev);
                        dev_info(dev->dev, "IR RX overrun\n");
                        writel(IRB_RX_OVERRUN_INT,
                                        dev->rx_base + IRB_RX_INT_CLEAR);
                        continue;
                }

                symbol = readl(dev->rx_base + IRB_RX_SYS);
                mark = readl(dev->rx_base + IRB_RX_ON);

                if (symbol == IRB_TIMEOUT)
                        last_symbol = 1;

                 /* Ignore any noise */
                if ((mark > 2) && (symbol > 1)) {
                        symbol -= mark;
                        if (dev->overclocking) { /* adjustments to timings */
                                symbol *= dev->sample_mult;
                                symbol /= dev->sample_div;
                                mark *= dev->sample_mult;
                                mark /= dev->sample_div;
                        }

                        ev.duration = mark;
                        ev.pulse = true;
                        ir_raw_event_store(dev->rdev, &ev);

                        if (!last_symbol) {
                                ev.duration = symbol;
                                ev.pulse = false;
                                ir_raw_event_store(dev->rdev, &ev);
                        } else  {
                                st_rc_send_lirc_timeout(dev->rdev);
                        }

                }
                last_symbol = 0;
        } while (time_is_after_jiffies(timeout));

        writel(IRB_RX_INTS, dev->rx_base + IRB_RX_INT_CLEAR);

        /* Empty software fifo */
        ir_raw_event_handle(dev->rdev);
        return IRQ_HANDLED;
}

static int st_rc_hardware_init(struct st_rc_device *dev)
{
        int ret;
        int baseclock, freqdiff;
        unsigned int rx_max_symbol_per = MAX_SYMB_TIME;
        unsigned int rx_sampling_freq_div;

        /* Enable the IP */
        reset_control_deassert(dev->rstc);

        ret = clk_prepare_enable(dev->sys_clock);
        if (ret) {
                dev_err(dev->dev, "Failed to prepare/enable system clock\n");
                return ret;
        }

        baseclock = clk_get_rate(dev->sys_clock);

        /* IRB input pins are inverted internally from high to low. */
        writel(1, dev->rx_base + IRB_RX_POLARITY_INV);

        rx_sampling_freq_div = baseclock / IRB_SAMPLE_FREQ;
        writel(rx_sampling_freq_div, dev->base + IRB_SAMPLE_RATE_COMM);

        freqdiff = baseclock - (rx_sampling_freq_div * IRB_SAMPLE_FREQ);
        if (freqdiff) { /* over clocking, workout the adjustment factors */
                dev->overclocking = true;
                dev->sample_mult = 1000;
                dev->sample_div = baseclock / (10000 * rx_sampling_freq_div);
                rx_max_symbol_per = (rx_max_symbol_per * 1000)/dev->sample_div;
        }

        writel(rx_max_symbol_per, dev->rx_base + IRB_MAX_SYM_PERIOD);

        return 0;
}

static void st_rc_remove(struct platform_device *pdev)
{
        struct st_rc_device *rc_dev = platform_get_drvdata(pdev);

        dev_pm_clear_wake_irq(&pdev->dev);
        device_init_wakeup(&pdev->dev, false);
        clk_disable_unprepare(rc_dev->sys_clock);
        rc_unregister_device(rc_dev->rdev);
        rc_free_device(rc_dev->rdev);
}

static int st_rc_open(struct rc_dev *rdev)
{
        struct st_rc_device *dev = rdev->priv;
        unsigned long flags;
        local_irq_save(flags);
        /* enable interrupts and receiver */
        writel(IRB_RX_INTS, dev->rx_base + IRB_RX_INT_EN);
        writel(0x01, dev->rx_base + IRB_RX_EN);
        local_irq_restore(flags);

        return 0;
}

static void st_rc_close(struct rc_dev *rdev)
{
        struct st_rc_device *dev = rdev->priv;
        /* disable interrupts and receiver */
        writel(0x00, dev->rx_base + IRB_RX_EN);
        writel(0x00, dev->rx_base + IRB_RX_INT_EN);
}

static int st_rc_probe(struct platform_device *pdev)
{
        int ret = -EINVAL;
        struct rc_dev *rdev;
        struct device *dev = &pdev->dev;
        struct st_rc_device *rc_dev;
        struct device_node *np = pdev->dev.of_node;
        const char *rx_mode;

        rc_dev = devm_kzalloc(dev, sizeof(struct st_rc_device), GFP_KERNEL);

        if (!rc_dev)
                return -ENOMEM;

        rdev = rc_allocate_device(RC_DRIVER_IR_RAW);

        if (!rdev)
                return -ENOMEM;

        if (np && !of_property_read_string(np, "rx-mode", &rx_mode)) {

                if (!strcmp(rx_mode, "uhf")) {
                        rc_dev->rxuhfmode = true;
                } else if (!strcmp(rx_mode, "infrared")) {
                        rc_dev->rxuhfmode = false;
                } else {
                        dev_err(dev, "Unsupported rx mode [%s]\n", rx_mode);
                        goto err;
                }

        } else {
                goto err;
        }

        rc_dev->sys_clock = devm_clk_get(dev, NULL);
        if (IS_ERR(rc_dev->sys_clock)) {
                dev_err(dev, "System clock not found\n");
                ret = PTR_ERR(rc_dev->sys_clock);
                goto err;
        }

        rc_dev->irq = platform_get_irq(pdev, 0);
        if (rc_dev->irq < 0) {
                ret = rc_dev->irq;
                goto err;
        }

        rc_dev->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(rc_dev->base)) {
                ret = PTR_ERR(rc_dev->base);
                goto err;
        }

        if (rc_dev->rxuhfmode)
                rc_dev->rx_base = rc_dev->base + 0x40;
        else
                rc_dev->rx_base = rc_dev->base;

        rc_dev->rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
        if (IS_ERR(rc_dev->rstc)) {
                ret = PTR_ERR(rc_dev->rstc);
                goto err;
        }

        rc_dev->dev = dev;
        platform_set_drvdata(pdev, rc_dev);
        ret = st_rc_hardware_init(rc_dev);
        if (ret)
                goto err;

        rdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
        /* rx sampling rate is 10Mhz */
        rdev->rx_resolution = 100;
        rdev->timeout = MAX_SYMB_TIME;
        rdev->priv = rc_dev;
        rdev->open = st_rc_open;
        rdev->close = st_rc_close;
        rdev->driver_name = IR_ST_NAME;
        rdev->map_name = RC_MAP_EMPTY;
        rdev->device_name = "ST Remote Control Receiver";

        ret = rc_register_device(rdev);
        if (ret < 0)
                goto clkerr;

        rc_dev->rdev = rdev;
        if (devm_request_irq(dev, rc_dev->irq, st_rc_rx_interrupt,
                             0, IR_ST_NAME, rc_dev) < 0) {
                dev_err(dev, "IRQ %d register failed\n", rc_dev->irq);
                ret = -EINVAL;
                goto rcerr;
        }

        /* enable wake via this device */
        device_init_wakeup(dev, true);
        dev_pm_set_wake_irq(dev, rc_dev->irq);

        /*
         * for LIRC_MODE_MODE2 or LIRC_MODE_PULSE or LIRC_MODE_RAW
         * lircd expects a long space first before a signal train to sync.
         */
        st_rc_send_lirc_timeout(rdev);

        dev_info(dev, "setup in %s mode\n", rc_dev->rxuhfmode ? "UHF" : "IR");

        return ret;
rcerr:
        rc_unregister_device(rdev);
clkerr:
        clk_disable_unprepare(rc_dev->sys_clock);
err:
        rc_free_device(rdev);
        dev_err(dev, "Unable to register device (%d)\n", ret);
        return ret;
}

#ifdef CONFIG_PM_SLEEP
static int st_rc_suspend(struct device *dev)
{
        struct st_rc_device *rc_dev = dev_get_drvdata(dev);

        if (device_may_wakeup(dev)) {
                if (!enable_irq_wake(rc_dev->irq))
                        rc_dev->irq_wake = 1;
                else
                        return -EINVAL;
        } else {
                pinctrl_pm_select_sleep_state(dev);
                writel(0x00, rc_dev->rx_base + IRB_RX_EN);
                writel(0x00, rc_dev->rx_base + IRB_RX_INT_EN);
                clk_disable_unprepare(rc_dev->sys_clock);
                reset_control_assert(rc_dev->rstc);
        }

        return 0;
}

static int st_rc_resume(struct device *dev)
{
        int ret;
        struct st_rc_device *rc_dev = dev_get_drvdata(dev);
        struct rc_dev   *rdev = rc_dev->rdev;

        if (rc_dev->irq_wake) {
                disable_irq_wake(rc_dev->irq);
                rc_dev->irq_wake = 0;
        } else {
                pinctrl_pm_select_default_state(dev);
                ret = st_rc_hardware_init(rc_dev);
                if (ret)
                        return ret;

                if (rdev->users) {
                        writel(IRB_RX_INTS, rc_dev->rx_base + IRB_RX_INT_EN);
                        writel(0x01, rc_dev->rx_base + IRB_RX_EN);
                }
        }

        return 0;
}

#endif

static SIMPLE_DEV_PM_OPS(st_rc_pm_ops, st_rc_suspend, st_rc_resume);

#ifdef CONFIG_OF
static const struct of_device_id st_rc_match[] = {
        { .compatible = "st,comms-irb", },
        {},
};

MODULE_DEVICE_TABLE(of, st_rc_match);
#endif

static struct platform_driver st_rc_driver = {
        .driver = {
                .name = IR_ST_NAME,
                .of_match_table = of_match_ptr(st_rc_match),
                .pm     = &st_rc_pm_ops,
        },
        .probe = st_rc_probe,
        .remove = st_rc_remove,
};

module_platform_driver(st_rc_driver);

MODULE_DESCRIPTION("RC Transceiver driver for STMicroelectronics platforms");
MODULE_AUTHOR("STMicroelectronics (R&D) Ltd");
MODULE_LICENSE("GPL");