| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/of_device.h> |
| #include <linux/err.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/spinlock.h> |
| #include <linux/of_address.h> |
| #include <linux/interrupt.h> |
| #include <linux/of_irq.h> |
| #include <linux/amlogic/irblaster.h> |
| #include <linux/amlogic/irblaster_consumer.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/pm_domain.h> |
| |
| /* Amlogic AO_IR_BLASTER_ADDR0 bits */ |
| #define BLASTER_BUSY BIT(26) |
| #define BLASTER_FIFO_FULL BIT(25) |
| #define BLASTER_FIFO_EMPTY BIT(24) |
| #define BLASTER_FIFO_LEVEL (0xff << 16) |
| #define BLASTER_MODULATOR_TB_SYSTEM_CLOCK (0x0 << 12) |
| #define BLASTER_MODULATOR_TB_XTAL3_TICK (0x1 << 12) |
| #define BLASTER_MODULATOR_TB_1US_TICK (0x2 << 12) |
| #define BLASTER_MODULATOR_TB_10US_TICK (0x3 << 12) |
| #define BLASTER_SLOW_CLOCK_DIV (0xff << 4) |
| #define BLASTER_SLOW_CLOCK_MODE BIT(3) |
| #define BLASTER_INIT_HIGH BIT(2) |
| #define BLASTER_INIT_LOW BIT(1) |
| #define BLASTER_ENABLE BIT(0) |
| |
| /* Amlogic AO_IR_BLASTER_ADDR1 bits */ |
| #define BLASTER_MODULATION_LOW_COUNT(c) ((c) << 16) |
| #define BLASTER_MODULATION_HIGH_COUNT(c) ((c) << 0) |
| |
| /* Amlogic AO_IR_BLASTER_ADDR2 bits */ |
| #define BLASTER_WRITE_FIFO BIT(16) |
| #define BLASTER_MODULATION_ENABLE BIT(12) |
| #define BLASTER_TIMEBASE_1US (0x0 << 10) |
| #define BLASTER_TIMEBASE_10US (0x1 << 10) |
| #define BLASTER_TIMEBASE_100US (0x2 << 10) |
| #define BLASTER_TIMEBASE_MODULATION_CLOCK (0x3 << 10) |
| |
| /* Amlogic AO_IR_BLASTER_ADDR3 bits */ |
| #define BLASTER_FIFO_THD_PENDING BIT(16) |
| #define BLASTER_FIFO_IRQ_ENABLE BIT(8) |
| #define BLASTER_FIFO_IRQ_THRESHOLD(c) (((c) & 0xff) << 0) |
| |
| #define DEFAULT_CARRIER_FREQ (38000) |
| #define DEFAULT_DUTY_CYCLE (50) |
| #define LIMIT_DUTY (25) |
| #define MAX_DUTY (75) |
| #define LIMIT_FREQ (25000) |
| #define MAX_FREQ (60000) |
| #define COUNT_DELAY_MASK (0X3ff) |
| #define TIMEBASE_SHIFT (10) |
| #define BLASTER_KFIFO_SIZE (4) |
| |
| #define AO_IR_BLASTER_ADDR0 (0x0) |
| #define AO_IR_BLASTER_ADDR1 (0x4) |
| #define AO_IR_BLASTER_ADDR2 (0x8) |
| #define AO_IR_BLASTER_ADDR3 (0xc) |
| #define AO_RTI_GEN_CTNL_REG0 (0x0) |
| |
| #define CONSUMERIR_TRANSMIT 0x5500 |
| #define GET_CARRIER 0x5501 |
| #define SET_CARRIER 0x5502 |
| #define SET_DUTYCYCLE 0x5503 |
| #define BLASTER_TIMEBASE_COUNT (BIT(10) - 1) |
| #define BLASTER_DEBUG_HIGH (2) |
| #define BLASTER_DELAY_MS 25 |
| |
| struct meson_irblaster_dev { |
| struct device *dev; |
| struct work_struct blaster_work; |
| struct irblaster_chip chip; |
| struct completion blaster_completion; |
| unsigned int count; |
| unsigned int irq; |
| unsigned int buffer_size; |
| unsigned int *buffer; |
| spinlock_t irblaster_lock; /* use to send data */ |
| void __iomem *reg_base; |
| void __iomem *reset_base; |
| }; |
| |
| #define IROUTDebug(fmt, x...) |
| |
| static void meson_irblaster_tasklet(unsigned long data); |
| DECLARE_TASKLET_DISABLED(irblaster_tasklet, meson_irblaster_tasklet, 0); |
| |
| static struct meson_irblaster_dev * |
| to_meson_irblaster(struct irblaster_chip *chip) |
| { |
| return container_of(chip, struct meson_irblaster_dev, chip); |
| } |
| |
| static void blaster_initialize(struct meson_irblaster_dev *dev) |
| { |
| unsigned int carrier_cycle = DIV_ROUND_CLOSEST(1000, |
| (dev->chip.state.freq / 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_relaxed((~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->chip.state.duty / 100; |
| low_ct = carrier_cycle - high_ct; |
| writel_relaxed((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_relaxed(readl_relaxed(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_relaxed(BLASTER_FIFO_IRQ_ENABLE | |
| BLASTER_FIFO_IRQ_THRESHOLD(8), |
| dev->reg_base + AO_IR_BLASTER_ADDR3); |
| /*enable irblaster*/ |
| writel_relaxed(readl_relaxed(dev->reg_base + AO_IR_BLASTER_ADDR0) | |
| BLASTER_ENABLE, |
| dev->reg_base + AO_IR_BLASTER_ADDR0); |
| } |
| |
| static int write_to_fifo(struct meson_irblaster_dev *dev, |
| unsigned int hightime, |
| unsigned int lowtime) |
| { |
| unsigned int count_delay; |
| unsigned int cycle = DIV_ROUND_CLOSEST(1000, |
| (dev->chip.state.freq / 1000)); |
| u32 val; |
| int n = 0; |
| int tb[3] = { |
| 1, 10, 100 |
| }; |
| |
| /* |
| * 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; |
| if (count_delay < 1024) { |
| count_delay &= COUNT_DELAY_MASK; |
| val = (BLASTER_WRITE_FIFO | BLASTER_MODULATION_ENABLE | |
| BLASTER_TIMEBASE_MODULATION_CLOCK | (count_delay << 0)); |
| } else { |
| count_delay = (((hightime + 100 / 2) / 100) - 1) |
| & COUNT_DELAY_MASK; |
| val = (BLASTER_WRITE_FIFO | BLASTER_MODULATION_ENABLE | |
| BLASTER_TIMEBASE_100US | (count_delay << 0)); |
| } |
| |
| writel_relaxed(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_relaxed(val, dev->reg_base + AO_IR_BLASTER_ADDR2); |
| |
| return 0; |
| } |
| |
| static int write_to_high_fifo(struct meson_irblaster_dev *dev, |
| unsigned int hightime) |
| { |
| unsigned int count_delay; |
| unsigned int cycle = DIV_ROUND_CLOSEST(1000, |
| (dev->chip.state.freq / 1000)); |
| u32 val; |
| |
| /* |
| * 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_relaxed(val, dev->reg_base + AO_IR_BLASTER_ADDR2); |
| |
| return 0; |
| } |
| |
| static void send_all_data(struct meson_irblaster_dev *dev) |
| { |
| int i, j; |
| unsigned int *pdata = NULL; |
| unsigned long flags; |
| unsigned int cycle, max_time, high_ct, low_ct; |
| |
| cycle = DIV_ROUND_CLOSEST(1000, (dev->chip.state.freq / 1000)); |
| high_ct = cycle * dev->chip.state.duty / 100; |
| low_ct = cycle - high_ct; |
| max_time = (high_ct + low_ct) * BLASTER_TIMEBASE_COUNT; |
| IROUTDebug("send data max_time = %d\n", max_time); |
| |
| pdata = &dev->buffer[dev->count]; |
| spin_lock_irqsave(&dev->irblaster_lock, flags); |
| for (i = 0; (i < 120) && (dev->count < dev->buffer_size);) { |
| if (*pdata > max_time) { |
| for (j = 0; j < (*pdata / max_time); j++) { |
| write_to_high_fifo(dev, max_time); |
| i++; |
| } |
| write_to_fifo(dev, *pdata % max_time, *(pdata + 1)); |
| } else { |
| write_to_fifo(dev, *pdata, *(pdata + 1)); |
| } |
| |
| pdata += 2; |
| dev->count += 2; |
| i += 2; |
| } |
| spin_unlock_irqrestore(&dev->irblaster_lock, flags); |
| } |
| |
| int meson_irblaster_send(struct irblaster_chip *chip, |
| unsigned int *data, |
| unsigned int len) |
| { |
| int ret, i, sum_time = 0; |
| unsigned int high_ct, low_ct, cycle, long_len = 0; |
| struct meson_irblaster_dev *irblaster_dev = to_meson_irblaster(chip); |
| |
| pm_runtime_get_sync(chip->dev); |
| init_completion(&irblaster_dev->blaster_completion); |
| |
| /* |
| * 1. set mod_high_count = 13 |
| * 2. set mod_low_count = 13 |
| * 3. 60khz-8us, 38k-13us |
| */ |
| cycle = DIV_ROUND_CLOSEST(1000, |
| (irblaster_dev->chip.state.freq / 1000)); |
| high_ct = cycle * irblaster_dev->chip.state.duty / 100; |
| low_ct = cycle - high_ct; |
| writel_relaxed((BLASTER_MODULATION_LOW_COUNT(low_ct - 1) | |
| BLASTER_MODULATION_HIGH_COUNT(high_ct - 1)), |
| irblaster_dev->reg_base + AO_IR_BLASTER_ADDR1); |
| |
| for (i = 0; i < len; i++) { |
| sum_time += data[i]; |
| if (irblaster_debug) { |
| if (irblaster_debug >= BLASTER_DEBUG_HIGH) |
| IROUTDebug("ir[%d] = %d\n", i, data[i]); |
| if (data[i] > (high_ct + low_ct) |
| * BLASTER_TIMEBASE_COUNT) |
| long_len += DIV_ROUND_UP(data[i], |
| (high_ct + low_ct) * |
| BLASTER_TIMEBASE_COUNT); |
| } |
| } |
| |
| irblaster_dev->buffer = data; |
| irblaster_dev->buffer_size = len; |
| irblaster_dev->count = 0; |
| |
| send_all_data(irblaster_dev); |
| IROUTDebug("wait time = %dus len = %d fifosize = %d\n", |
| sum_time, irblaster_dev->buffer_size, len + long_len); |
| ret = wait_for_completion_interruptible_timeout |
| (&irblaster_dev->blaster_completion, |
| msecs_to_jiffies(sum_time / 1000 + BLASTER_DELAY_MS)); |
| if (!ret) { |
| pr_err("failed to send all data ret = %d\n", ret); |
| return -ETIMEDOUT; |
| } |
| IROUTDebug("send finish!\n"); |
| pm_runtime_put_sync(chip->dev); |
| |
| return 0; |
| } |
| |
| static struct irblaster_ops meson_irblaster_ops = { |
| .send = meson_irblaster_send, |
| }; |
| |
| static void meson_irblaster_tasklet(unsigned long data) |
| { |
| struct meson_irblaster_dev *dev = (struct meson_irblaster_dev *)data; |
| |
| if (dev->count < dev->buffer_size) |
| send_all_data(dev); |
| } |
| |
| static irqreturn_t meson_blaster_interrupt(int irq, void *dev_id) |
| { |
| struct meson_irblaster_dev *dev = dev_id; |
| |
| if (irblaster_debug >= BLASTER_DEBUG_HIGH) |
| IROUTDebug("enter irq count = %d size = %d\n", |
| dev->count, dev->buffer_size); |
| /*clear pending bit*/ |
| writel_relaxed(readl_relaxed(dev->reg_base + AO_IR_BLASTER_ADDR3) & |
| (~BLASTER_FIFO_THD_PENDING), |
| dev->reg_base + AO_IR_BLASTER_ADDR3); |
| |
| if (dev->count >= dev->buffer_size) { |
| complete(&dev->blaster_completion); |
| return IRQ_HANDLED; |
| } |
| |
| tasklet_schedule(&irblaster_tasklet); |
| return IRQ_HANDLED; |
| } |
| |
| static int meson_irblaster_probe(struct platform_device *pdev) |
| { |
| struct meson_irblaster_dev *irblaster_dev = NULL; |
| struct resource *reg_mem = NULL; |
| void __iomem *reg_base = NULL; |
| int err, ret; |
| |
| if (!pdev->dev.of_node) { |
| dev_err(&pdev->dev, "pdev->dev.of_node == NULL!\n"); |
| return -EINVAL; |
| } |
| |
| irblaster_dev = devm_kzalloc(&pdev->dev, |
| sizeof(struct meson_irblaster_dev), |
| GFP_KERNEL); |
| if (!irblaster_dev) |
| return -ENOMEM; |
| |
| spin_lock_init(&irblaster_dev->irblaster_lock); |
| platform_set_drvdata(pdev, irblaster_dev); |
| irblaster_dev->dev = &pdev->dev; |
| |
| 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); |
| } |
| |
| irblaster_dev->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); |
| if (!irblaster_dev->irq) { |
| dev_err(&pdev->dev, "irq: Failed to request irq number.\n"); |
| return -ENODEV; |
| } |
| |
| init_completion(&irblaster_dev->blaster_completion); |
| 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.\n"); |
| return ret; |
| } |
| /* init irblaster sysfs */ |
| ret = irblaster_sysfs_init(); |
| if (ret) { |
| pr_err("Failed to init irblaster sysfs.\n"); |
| return ret; |
| } |
| irblaster_dev->reg_base = reg_base; |
| irblaster_dev->chip.dev = &pdev->dev; |
| irblaster_dev->chip.ops = &meson_irblaster_ops; |
| irblaster_dev->chip.of_irblaster_n_cells = 2; |
| irblaster_dev->chip.state.freq = DEFAULT_CARRIER_FREQ; |
| irblaster_dev->chip.state.duty = DEFAULT_DUTY_CYCLE; |
| |
| err = irblasterchip_add(&irblaster_dev->chip); |
| if (err < 0) { |
| dev_err(&pdev->dev, "failed to register irblaster chip: %d\n", |
| err); |
| return err; |
| } |
| |
| irblaster_tasklet.data = (unsigned long)irblaster_dev; |
| tasklet_enable(&irblaster_tasklet); |
| /*initial blaster*/ |
| blaster_initialize(irblaster_dev); |
| pm_runtime_enable(&pdev->dev); |
| |
| return 0; |
| } |
| |
| static int meson_irblaster_remove(struct platform_device *pdev) |
| { |
| struct meson_irblaster_dev *irblaster_dev = platform_get_drvdata(pdev); |
| |
| tasklet_disable(&irblaster_tasklet); |
| tasklet_kill(&irblaster_tasklet); |
| irblasterchip_remove(&irblaster_dev->chip); |
| |
| return 0; |
| } |
| |
| static int meson_irblaster_runtime_suspend(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int meson_irblaster_runtime_resume(struct device *dev) |
| { |
| struct meson_irblaster_dev *irblaster_dev = dev_get_drvdata(dev); |
| |
| /*initial blaster*/ |
| blaster_initialize(irblaster_dev); |
| return 0; |
| } |
| |
| static const struct of_device_id irblaster_dt_match[] = { |
| { |
| .compatible = "amlogic, meson_irblaster", |
| }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, irblaster_dt_match); |
| |
| static const struct dev_pm_ops meson_irblaster_pm_ops = { |
| SET_RUNTIME_PM_OPS(meson_irblaster_runtime_suspend, |
| meson_irblaster_runtime_resume, NULL) |
| }; |
| |
| static struct platform_driver meson_irblaster_driver = { |
| .probe = meson_irblaster_probe, |
| .remove = meson_irblaster_remove, |
| .driver = { |
| .name = "meson_irblaster", |
| .owner = THIS_MODULE, |
| .of_match_table = irblaster_dt_match, |
| .pm = &meson_irblaster_pm_ops, |
| }, |
| }; |
| |
| static int __init meson_irblaster_init(void) |
| { |
| return platform_driver_register(&meson_irblaster_driver); |
| } |
| |
| static void __exit meson_irblaster_exit(void) |
| { |
| platform_driver_unregister(&meson_irblaster_driver); |
| } |
| |
| fs_initcall_sync(meson_irblaster_init); |
| module_exit(meson_irblaster_exit); |
| MODULE_AUTHOR("Amlogic, Inc."); |
| MODULE_DESCRIPTION("Amlogic ir blaster driver"); |
| MODULE_LICENSE("GPL"); |