linux/drivers/media/rc/ir-hix5hd2.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2014 Linaro Ltd.
   4 * Copyright (c) 2014 HiSilicon Limited.
   5 */
   6
   7#include <linux/clk.h>
   8#include <linux/delay.h>
   9#include <linux/interrupt.h>
  10#include <linux/mfd/syscon.h>
  11#include <linux/module.h>
  12#include <linux/of_device.h>
  13#include <linux/regmap.h>
  14#include <media/rc-core.h>
  15
  16#define IR_ENABLE               0x00
  17#define IR_CONFIG               0x04
  18#define CNT_LEADS               0x08
  19#define CNT_LEADE               0x0c
  20#define CNT_SLEADE              0x10
  21#define CNT0_B                  0x14
  22#define CNT1_B                  0x18
  23#define IR_BUSY                 0x1c
  24#define IR_DATAH                0x20
  25#define IR_DATAL                0x24
  26#define IR_INTM                 0x28
  27#define IR_INTS                 0x2c
  28#define IR_INTC                 0x30
  29#define IR_START                0x34
  30
  31/* interrupt mask */
  32#define INTMS_SYMBRCV           (BIT(24) | BIT(8))
  33#define INTMS_TIMEOUT           (BIT(25) | BIT(9))
  34#define INTMS_OVERFLOW          (BIT(26) | BIT(10))
  35#define INT_CLR_OVERFLOW        BIT(18)
  36#define INT_CLR_TIMEOUT         BIT(17)
  37#define INT_CLR_RCV             BIT(16)
  38#define INT_CLR_RCVTIMEOUT      (BIT(16) | BIT(17))
  39
  40#define IR_CLK_ENABLE           BIT(4)
  41#define IR_CLK_RESET            BIT(5)
  42
  43/* IR_ENABLE register bits */
  44#define IR_ENABLE_EN            BIT(0)
  45#define IR_ENABLE_EN_EXTRA      BIT(8)
  46
  47#define IR_CFG_WIDTH_MASK       0xffff
  48#define IR_CFG_WIDTH_SHIFT      16
  49#define IR_CFG_FORMAT_MASK      0x3
  50#define IR_CFG_FORMAT_SHIFT     14
  51#define IR_CFG_INT_LEVEL_MASK   0x3f
  52#define IR_CFG_INT_LEVEL_SHIFT  8
  53/* only support raw mode */
  54#define IR_CFG_MODE_RAW         BIT(7)
  55#define IR_CFG_FREQ_MASK        0x7f
  56#define IR_CFG_FREQ_SHIFT       0
  57#define IR_CFG_INT_THRESHOLD    1
  58/* symbol start from low to high, symbol stream end at high*/
  59#define IR_CFG_SYMBOL_FMT       0
  60#define IR_CFG_SYMBOL_MAXWIDTH  0x3e80
  61
  62#define IR_HIX5HD2_NAME         "hix5hd2-ir"
  63
  64/* Need to set extra bit for enabling IR */
  65#define HIX5HD2_FLAG_EXTRA_ENABLE       BIT(0)
  66
  67struct hix5hd2_soc_data {
  68        u32 clk_reg;
  69        u32 flags;
  70};
  71
  72static const struct hix5hd2_soc_data hix5hd2_data = {
  73        .clk_reg = 0x48,
  74};
  75
  76static const struct hix5hd2_soc_data hi3796cv300_data = {
  77        .clk_reg = 0x60,
  78        .flags = HIX5HD2_FLAG_EXTRA_ENABLE,
  79};
  80
  81struct hix5hd2_ir_priv {
  82        int                     irq;
  83        void __iomem            *base;
  84        struct device           *dev;
  85        struct rc_dev           *rdev;
  86        struct regmap           *regmap;
  87        struct clk              *clock;
  88        unsigned long           rate;
  89        const struct hix5hd2_soc_data *socdata;
  90};
  91
  92static int hix5hd2_ir_clk_enable(struct hix5hd2_ir_priv *dev, bool on)
  93{
  94        u32 clk_reg = dev->socdata->clk_reg;
  95        u32 val;
  96        int ret = 0;
  97
  98        if (dev->regmap) {
  99                regmap_read(dev->regmap, clk_reg, &val);
 100                if (on) {
 101                        val &= ~IR_CLK_RESET;
 102                        val |= IR_CLK_ENABLE;
 103                } else {
 104                        val &= ~IR_CLK_ENABLE;
 105                        val |= IR_CLK_RESET;
 106                }
 107                regmap_write(dev->regmap, clk_reg, val);
 108        } else {
 109                if (on)
 110                        ret = clk_prepare_enable(dev->clock);
 111                else
 112                        clk_disable_unprepare(dev->clock);
 113        }
 114        return ret;
 115}
 116
 117static inline void hix5hd2_ir_enable(struct hix5hd2_ir_priv *priv)
 118{
 119        u32 val = IR_ENABLE_EN;
 120
 121        if (priv->socdata->flags & HIX5HD2_FLAG_EXTRA_ENABLE)
 122                val |= IR_ENABLE_EN_EXTRA;
 123
 124        writel_relaxed(val, priv->base + IR_ENABLE);
 125}
 126
 127static int hix5hd2_ir_config(struct hix5hd2_ir_priv *priv)
 128{
 129        int timeout = 10000;
 130        u32 val, rate;
 131
 132        hix5hd2_ir_enable(priv);
 133
 134        while (readl_relaxed(priv->base + IR_BUSY)) {
 135                if (timeout--) {
 136                        udelay(1);
 137                } else {
 138                        dev_err(priv->dev, "IR_BUSY timeout\n");
 139                        return -ETIMEDOUT;
 140                }
 141        }
 142
 143        /* Now only support raw mode, with symbol start from low to high */
 144        rate = DIV_ROUND_CLOSEST(priv->rate, 1000000);
 145        val = IR_CFG_SYMBOL_MAXWIDTH & IR_CFG_WIDTH_MASK << IR_CFG_WIDTH_SHIFT;
 146        val |= IR_CFG_SYMBOL_FMT & IR_CFG_FORMAT_MASK << IR_CFG_FORMAT_SHIFT;
 147        val |= (IR_CFG_INT_THRESHOLD - 1) & IR_CFG_INT_LEVEL_MASK
 148               << IR_CFG_INT_LEVEL_SHIFT;
 149        val |= IR_CFG_MODE_RAW;
 150        val |= (rate - 1) & IR_CFG_FREQ_MASK << IR_CFG_FREQ_SHIFT;
 151        writel_relaxed(val, priv->base + IR_CONFIG);
 152
 153        writel_relaxed(0x00, priv->base + IR_INTM);
 154        /* write arbitrary value to start  */
 155        writel_relaxed(0x01, priv->base + IR_START);
 156        return 0;
 157}
 158
 159static int hix5hd2_ir_open(struct rc_dev *rdev)
 160{
 161        struct hix5hd2_ir_priv *priv = rdev->priv;
 162        int ret;
 163
 164        ret = hix5hd2_ir_clk_enable(priv, true);
 165        if (ret)
 166                return ret;
 167
 168        ret = hix5hd2_ir_config(priv);
 169        if (ret) {
 170                hix5hd2_ir_clk_enable(priv, false);
 171                return ret;
 172        }
 173        return 0;
 174}
 175
 176static void hix5hd2_ir_close(struct rc_dev *rdev)
 177{
 178        struct hix5hd2_ir_priv *priv = rdev->priv;
 179
 180        hix5hd2_ir_clk_enable(priv, false);
 181}
 182
 183static irqreturn_t hix5hd2_ir_rx_interrupt(int irq, void *data)
 184{
 185        u32 symb_num, symb_val, symb_time;
 186        u32 data_l, data_h;
 187        u32 irq_sr, i;
 188        struct hix5hd2_ir_priv *priv = data;
 189
 190        irq_sr = readl_relaxed(priv->base + IR_INTS);
 191        if (irq_sr & INTMS_OVERFLOW) {
 192                /*
 193                 * we must read IR_DATAL first, then we can clean up
 194                 * IR_INTS availably since logic would not clear
 195                 * fifo when overflow, drv do the job
 196                 */
 197                ir_raw_event_reset(priv->rdev);
 198                symb_num = readl_relaxed(priv->base + IR_DATAH);
 199                for (i = 0; i < symb_num; i++)
 200                        readl_relaxed(priv->base + IR_DATAL);
 201
 202                writel_relaxed(INT_CLR_OVERFLOW, priv->base + IR_INTC);
 203                dev_info(priv->dev, "overflow, level=%d\n",
 204                         IR_CFG_INT_THRESHOLD);
 205        }
 206
 207        if ((irq_sr & INTMS_SYMBRCV) || (irq_sr & INTMS_TIMEOUT)) {
 208                struct ir_raw_event ev = {};
 209
 210                symb_num = readl_relaxed(priv->base + IR_DATAH);
 211                for (i = 0; i < symb_num; i++) {
 212                        symb_val = readl_relaxed(priv->base + IR_DATAL);
 213                        data_l = ((symb_val & 0xffff) * 10);
 214                        data_h =  ((symb_val >> 16) & 0xffff) * 10;
 215                        symb_time = (data_l + data_h) / 10;
 216
 217                        ev.duration = data_l;
 218                        ev.pulse = true;
 219                        ir_raw_event_store(priv->rdev, &ev);
 220
 221                        if (symb_time < IR_CFG_SYMBOL_MAXWIDTH) {
 222                                ev.duration = data_h;
 223                                ev.pulse = false;
 224                                ir_raw_event_store(priv->rdev, &ev);
 225                        } else {
 226                                ir_raw_event_set_idle(priv->rdev, true);
 227                        }
 228                }
 229
 230                if (irq_sr & INTMS_SYMBRCV)
 231                        writel_relaxed(INT_CLR_RCV, priv->base + IR_INTC);
 232                if (irq_sr & INTMS_TIMEOUT)
 233                        writel_relaxed(INT_CLR_TIMEOUT, priv->base + IR_INTC);
 234        }
 235
 236        /* Empty software fifo */
 237        ir_raw_event_handle(priv->rdev);
 238        return IRQ_HANDLED;
 239}
 240
 241static const struct of_device_id hix5hd2_ir_table[] = {
 242        { .compatible = "hisilicon,hix5hd2-ir", &hix5hd2_data, },
 243        { .compatible = "hisilicon,hi3796cv300-ir", &hi3796cv300_data, },
 244        {},
 245};
 246MODULE_DEVICE_TABLE(of, hix5hd2_ir_table);
 247
 248static int hix5hd2_ir_probe(struct platform_device *pdev)
 249{
 250        struct rc_dev *rdev;
 251        struct device *dev = &pdev->dev;
 252        struct resource *res;
 253        struct hix5hd2_ir_priv *priv;
 254        struct device_node *node = pdev->dev.of_node;
 255        const struct of_device_id *of_id;
 256        const char *map_name;
 257        int ret;
 258
 259        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 260        if (!priv)
 261                return -ENOMEM;
 262
 263        of_id = of_match_device(hix5hd2_ir_table, dev);
 264        if (!of_id) {
 265                dev_err(dev, "Unable to initialize IR data\n");
 266                return -ENODEV;
 267        }
 268        priv->socdata = of_id->data;
 269
 270        priv->regmap = syscon_regmap_lookup_by_phandle(node,
 271                                                       "hisilicon,power-syscon");
 272        if (IS_ERR(priv->regmap)) {
 273                dev_info(dev, "no power-reg\n");
 274                priv->regmap = NULL;
 275        }
 276
 277        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 278        priv->base = devm_ioremap_resource(dev, res);
 279        if (IS_ERR(priv->base))
 280                return PTR_ERR(priv->base);
 281
 282        priv->irq = platform_get_irq(pdev, 0);
 283        if (priv->irq < 0)
 284                return priv->irq;
 285
 286        rdev = rc_allocate_device(RC_DRIVER_IR_RAW);
 287        if (!rdev)
 288                return -ENOMEM;
 289
 290        priv->clock = devm_clk_get(dev, NULL);
 291        if (IS_ERR(priv->clock)) {
 292                dev_err(dev, "clock not found\n");
 293                ret = PTR_ERR(priv->clock);
 294                goto err;
 295        }
 296        ret = clk_prepare_enable(priv->clock);
 297        if (ret)
 298                goto err;
 299        priv->rate = clk_get_rate(priv->clock);
 300
 301        rdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
 302        rdev->priv = priv;
 303        rdev->open = hix5hd2_ir_open;
 304        rdev->close = hix5hd2_ir_close;
 305        rdev->driver_name = IR_HIX5HD2_NAME;
 306        map_name = of_get_property(node, "linux,rc-map-name", NULL);
 307        rdev->map_name = map_name ?: RC_MAP_EMPTY;
 308        rdev->device_name = IR_HIX5HD2_NAME;
 309        rdev->input_phys = IR_HIX5HD2_NAME "/input0";
 310        rdev->input_id.bustype = BUS_HOST;
 311        rdev->input_id.vendor = 0x0001;
 312        rdev->input_id.product = 0x0001;
 313        rdev->input_id.version = 0x0100;
 314        rdev->rx_resolution = 10;
 315        rdev->timeout = IR_CFG_SYMBOL_MAXWIDTH * 10;
 316
 317        ret = rc_register_device(rdev);
 318        if (ret < 0)
 319                goto clkerr;
 320
 321        if (devm_request_irq(dev, priv->irq, hix5hd2_ir_rx_interrupt,
 322                             0, pdev->name, priv) < 0) {
 323                dev_err(dev, "IRQ %d register failed\n", priv->irq);
 324                ret = -EINVAL;
 325                goto regerr;
 326        }
 327
 328        priv->rdev = rdev;
 329        priv->dev = dev;
 330        platform_set_drvdata(pdev, priv);
 331
 332        return ret;
 333
 334regerr:
 335        rc_unregister_device(rdev);
 336        rdev = NULL;
 337clkerr:
 338        clk_disable_unprepare(priv->clock);
 339err:
 340        rc_free_device(rdev);
 341        dev_err(dev, "Unable to register device (%d)\n", ret);
 342        return ret;
 343}
 344
 345static int hix5hd2_ir_remove(struct platform_device *pdev)
 346{
 347        struct hix5hd2_ir_priv *priv = platform_get_drvdata(pdev);
 348
 349        clk_disable_unprepare(priv->clock);
 350        rc_unregister_device(priv->rdev);
 351        return 0;
 352}
 353
 354#ifdef CONFIG_PM_SLEEP
 355static int hix5hd2_ir_suspend(struct device *dev)
 356{
 357        struct hix5hd2_ir_priv *priv = dev_get_drvdata(dev);
 358
 359        clk_disable_unprepare(priv->clock);
 360        hix5hd2_ir_clk_enable(priv, false);
 361
 362        return 0;
 363}
 364
 365static int hix5hd2_ir_resume(struct device *dev)
 366{
 367        struct hix5hd2_ir_priv *priv = dev_get_drvdata(dev);
 368        int ret;
 369
 370        ret = hix5hd2_ir_clk_enable(priv, true);
 371        if (ret)
 372                return ret;
 373
 374        ret = clk_prepare_enable(priv->clock);
 375        if (ret) {
 376                hix5hd2_ir_clk_enable(priv, false);
 377                return ret;
 378        }
 379
 380        hix5hd2_ir_enable(priv);
 381
 382        writel_relaxed(0x00, priv->base + IR_INTM);
 383        writel_relaxed(0xff, priv->base + IR_INTC);
 384        writel_relaxed(0x01, priv->base + IR_START);
 385
 386        return 0;
 387}
 388#endif
 389
 390static SIMPLE_DEV_PM_OPS(hix5hd2_ir_pm_ops, hix5hd2_ir_suspend,
 391                         hix5hd2_ir_resume);
 392
 393static struct platform_driver hix5hd2_ir_driver = {
 394        .driver = {
 395                .name = IR_HIX5HD2_NAME,
 396                .of_match_table = hix5hd2_ir_table,
 397                .pm     = &hix5hd2_ir_pm_ops,
 398        },
 399        .probe = hix5hd2_ir_probe,
 400        .remove = hix5hd2_ir_remove,
 401};
 402
 403module_platform_driver(hix5hd2_ir_driver);
 404
 405MODULE_DESCRIPTION("IR controller driver for hix5hd2 platforms");
 406MODULE_AUTHOR("Guoxiong Yan <yanguoxiong@huawei.com>");
 407MODULE_LICENSE("GPL v2");
 408MODULE_ALIAS("platform:hix5hd2-ir");
 409