diff --git a/MAINTAINERS b/MAINTAINERS index 173d58012875..a02e95b1dffa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14776,4 +14776,8 @@ AMLOGIC GDC DRIVER M: Pengcheng Chen F: drivers/amlogic/media/gdc/app/gdc_dmabuf.c F: drivers/amlogic/media/gdc/app/gdc_dmabuf.h -F: drivers/amlogic/media/gdc/src/platform/system_log.c \ No newline at end of file +F: drivers/amlogic/media/gdc/src/platform/system_log.c + +AMLOGIC IRCUT DRIVER +M: Dianzhong Huo +F: drivers/amlogic/ircut/ diff --git a/arch/arm64/boot/dts/amlogic/g12b_a311x_w411_buildroot.dts b/arch/arm64/boot/dts/amlogic/g12b_a311x_w411_buildroot.dts index c002d6703ca9..470778b5052f 100644 --- a/arch/arm64/boot/dts/amlogic/g12b_a311x_w411_buildroot.dts +++ b/arch/arm64/boot/dts/amlogic/g12b_a311x_w411_buildroot.dts @@ -810,10 +810,10 @@ ircut: ircut { compatible = "amlogic, ircut"; status = "okay"; - filter1_gpio = <&gpio GPIOH_6 GPIO_ACTIVE_HIGH>; - filter2_gpio = <&gpio GPIOH_7 GPIO_ACTIVE_HIGH>; - ircut_in_gpio = <&gpio_ao GPIOAO_7 GPIO_ACTIVE_HIGH>; - alarm_out_gpio = <&gpio_ao GPIOAO_10 GPIO_ACTIVE_HIGH>; + filter1-gpios = <&gpio GPIOH_6 GPIO_ACTIVE_HIGH>; + filter2-gpios = <&gpio GPIOH_7 GPIO_ACTIVE_HIGH>; + light_in-gpios = <&gpio_ao GPIOAO_7 GPIO_ACTIVE_HIGH>; + alarm-gpios = <&gpio_ao GPIOAO_10 GPIO_ACTIVE_HIGH>; }; vcodec_dec { compatible = "amlogic, vcodec-dec"; diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index 687bf8bb6051..e12f32c74e7c 100644 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -138,5 +138,7 @@ source "drivers/amlogic/spi-nor/Kconfig" source "drivers/amlogic/dolby_fw/Kconfig" +source "drivers/amlogic/ircut/Kconfig" + endmenu endif diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index f040e24857c2..f060cfbba2e4 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -135,3 +135,5 @@ obj-$(CONFIG_AMLOGIC_DEFENDKEY) += defendkey/ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ obj-$(CONFIG_DOLBY_FW) += dolby_fw/ + +obj-$(CONFIG_AMLOGIC_IRCUT) += ircut/ diff --git a/drivers/amlogic/ircut/Kconfig b/drivers/amlogic/ircut/Kconfig new file mode 100644 index 000000000000..c99657d73d90 --- /dev/null +++ b/drivers/amlogic/ircut/Kconfig @@ -0,0 +1,5 @@ +config AMLOGIC_IRCUT + tristate "Amlogic ircut support" + default n + help + select y to support ircut diff --git a/drivers/amlogic/ircut/Makefile b/drivers/amlogic/ircut/Makefile new file mode 100644 index 000000000000..d19f34930f27 --- /dev/null +++ b/drivers/amlogic/ircut/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the IPC ircut drivers +# + +# Each configuration option enables a list of files. +obj-$(CONFIG_AMLOGIC_IRCUT) += ircut.o diff --git a/drivers/amlogic/ircut/ircut.c b/drivers/amlogic/ircut/ircut.c new file mode 100644 index 000000000000..bbc7a6c446c1 --- /dev/null +++ b/drivers/amlogic/ircut/ircut.c @@ -0,0 +1,279 @@ +/* + * drivers/amlogic/ircut/ircut.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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 program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#define OWNER_NAME "ircut" +#undef pr_fmt +#define pr_fmt(fmt) "ircut: " fmt + +static unsigned int mode_s; + +struct ircut_plat_s { + struct gpio_desc *light_in; + struct gpio_desc *filter1; + struct gpio_desc *filter2; + struct gpio_desc *alarm; + struct class ircut_class; + struct timer_list timer; +}; + +enum { + IRCUT_STANDBY = 0,/* filter1:L filter2:L */ + IRCUT_FOREWARD, /* filter1:H filter2:L */ + IRCUT_REVERSAL, /* filter1:L filter2:H */ + IRCUT_LOCK, /* filter1:H filter2:H */ + IRCUT_AUTO, /* auto detect by light sensor */ +}; + +static const struct of_device_id ircut_match[] = { + { + .compatible = "amlogic, ircut", + }, + {}, +}; + +static int ircut_control(unsigned int value, struct ircut_plat_s *plat) +{ + if (mode_s != value) + mode_s = value; + else + return 0; + + pr_debug("%s(%d)\n", __func__, value); + switch (value) { + case IRCUT_STANDBY: + gpiod_set_value(plat->filter1, 0); + gpiod_set_value(plat->filter2, 0); + break; + case IRCUT_FOREWARD: + gpiod_set_value(plat->filter1, 1); + gpiod_set_value(plat->filter2, 0); + break; + case IRCUT_REVERSAL: + gpiod_set_value(plat->filter1, 0); + gpiod_set_value(plat->filter2, 1); + break; + case IRCUT_LOCK: + gpiod_set_value(plat->filter1, 1); + gpiod_set_value(plat->filter2, 1); + break; + default: + gpiod_set_value(plat->filter1, 0); + gpiod_set_value(plat->filter2, 1); + break; + } + return 0; +} + +void ircut_in_detect(struct ircut_plat_s *plat) +{ + int value; + + gpiod_direction_input(plat->light_in); + value = gpiod_get_value(plat->light_in); + pr_debug("value=%d\n", value); + if (value == 0) + ircut_control(IRCUT_REVERSAL, plat); + else + ircut_control(IRCUT_FOREWARD, plat); +} + +static void ircut_timer_handle(unsigned long data) +{ + struct ircut_plat_s *plat = (struct ircut_plat_s *)data; + + ircut_in_detect(plat); + mod_timer(&plat->timer, jiffies + msecs_to_jiffies(2000)); +} + +/*ircut class*/ +static ssize_t set_ircut(struct class *cls, + struct class_attribute *attr, const char *buf, + size_t count) +{ + unsigned int mode = 0; + ssize_t ret = 0; + + struct ircut_plat_s *ircutdev = container_of(cls, + struct ircut_plat_s, ircut_class); + + if (!strcmp(attr->attr.name, "mode")) { + ret = kstrtoint(buf, 0, &mode); + if (ret) + return -EINVAL; + } + if (mode < IRCUT_AUTO) { + del_timer_sync(&ircutdev->timer); + ircut_control(mode, ircutdev); + } else + mod_timer(&ircutdev->timer, jiffies + msecs_to_jiffies(2000)); + + return count; +} + +static ssize_t get_ircut(struct class *cls, + struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", mode_s); +} + +static ssize_t set_alarm(struct class *cls, + struct class_attribute *attr, const char *buf, + size_t count) +{ + unsigned int alarm = 0; + ssize_t ret = 0; + + struct ircut_plat_s *ircutdev = container_of(cls, + struct ircut_plat_s, ircut_class); + + if (!strcmp(attr->attr.name, "alarm")) { + ret = kstrtoint(buf, 0, &alarm); + if (ret) + return -EINVAL; + } + if (IS_ERR_OR_NULL(ircutdev->alarm)) { + pr_err("alarm gpio not config.\n"); + return -EINVAL; + } + if (alarm == 1) + gpiod_set_value(ircutdev->alarm, 1); + else + gpiod_set_value(ircutdev->alarm, 0); + + return count; +} + +static struct class_attribute ircut_class_attrs[] = { + __ATTR(mode, 0644, get_ircut, set_ircut), + __ATTR(alarm, 0644, NULL, set_alarm), + __ATTR_NULL +}; + +static int ircut_setup_dt(struct platform_device *pdev) +{ + struct ircut_plat_s *ircutdev = platform_get_drvdata(pdev); + + ircut_control(IRCUT_STANDBY, ircutdev); + return 0; +} + +static int ircut_dev_probe(struct platform_device *pdev) +{ + int ret; + struct ircut_plat_s *plat = NULL; + + plat = devm_kzalloc(&pdev->dev, + sizeof(struct ircut_plat_s), GFP_KERNEL); + if (!plat) + return -ENOMEM; + + platform_set_drvdata(pdev, plat); + + if (pdev->dev.of_node) { + /* get all gpio desc. */ + plat->filter1 = devm_gpiod_get(&pdev->dev, + "filter1", GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(plat->filter1)) + return PTR_ERR(plat->filter1); + plat->filter2 = devm_gpiod_get(&pdev->dev, "filter2", + GPIOD_OUT_HIGH); + if (IS_ERR_OR_NULL(plat->filter2)) + return PTR_ERR(plat->filter2); + plat->light_in = devm_gpiod_get(&pdev->dev, "light_in", + GPIOD_IN); + if (IS_ERR_OR_NULL(plat->light_in)) + return PTR_ERR(plat->light_in); + plat->alarm = devm_gpiod_get(&pdev->dev, "alarm", + GPIOD_OUT_LOW); + if (IS_ERR_OR_NULL(plat->alarm)) + pr_err("ignore alarm gpio not config.\n"); + } + + /*init class*/ + plat->ircut_class.name = OWNER_NAME; + plat->ircut_class.owner = THIS_MODULE; + plat->ircut_class.class_attrs = ircut_class_attrs; + + ret = class_register(&plat->ircut_class); + if (ret < 0) { + return ret; + pr_err("failed to register ircut class.\n"); + } + + ircut_setup_dt(pdev); + + /* timer init */ + setup_timer(&plat->timer, ircut_timer_handle, (unsigned long)plat); + /* start timer */ + /* HZ / 1000 * timer_expires_ms); */ + mod_timer(&plat->timer, jiffies + msecs_to_jiffies(2000)); + return 0; +} + +static int ircut_dev_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ircut_plat_s *ircutdev = platform_get_drvdata(pdev); + + del_timer_sync(&ircutdev->timer); + pr_info("ircut_dev_suspend ok.\n"); + return 0; +} + +static int ircut_dev_resume(struct platform_device *pdev) +{ + struct ircut_plat_s *ircutdev = platform_get_drvdata(pdev); + + mod_timer(&ircutdev->timer, jiffies + msecs_to_jiffies(2000)); + pr_info("ircut_dev_resume ok.\n"); + return 0; +} + +static int ircut_dev_remove(struct platform_device *pdev) +{ + struct ircut_plat_s *ircutdev = platform_get_drvdata(pdev); + + class_unregister(&ircutdev->ircut_class); + del_timer_sync(&ircutdev->timer); + return 0; +} + +static struct platform_driver ircut_plat_driver = { + .probe = ircut_dev_probe, + .remove = ircut_dev_remove, + .suspend = ircut_dev_suspend, + .resume = ircut_dev_resume, + .driver = { + .name = "meson_ircut", + .owner = THIS_MODULE, + .of_match_table = ircut_match + }, +}; + +module_platform_driver(ircut_plat_driver); + +MODULE_AUTHOR("Amlogic"); +MODULE_DESCRIPTION("IPC ircut driver"); +MODULE_AUTHOR("Dianzhong Huo "); +MODULE_LICENSE("GPL");