| /* |
| * drivers/amlogic/media/vin/tvin/hdmirx_ext/hdmirx_ext_drv.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 <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/err.h> |
| #include <linux/fs.h> |
| #include <linux/platform_device.h> |
| #include <linux/of.h> |
| #include <linux/slab.h> |
| /*#include <linux/i2c.h>*/ |
| #include <linux/amlogic/i2c-amlogic.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/amlogic/aml_gpio_consumer.h> |
| #include <linux/of_gpio.h> |
| |
| #include "hdmirx_ext_drv.h" |
| #include "hdmirx_ext_hw_iface.h" |
| #include "hdmirx_ext_attrs.h" |
| #include "vdin_iface.h" |
| |
| int hdmirx_ext_debug_print; |
| |
| static struct hdmirx_ext_drv_s *hdmirx_ext_drv; |
| static struct class *hdmirx_ext_class; |
| static dev_t hdmirx_ext_device_id; |
| static struct device *hdmirx_ext_device; |
| |
| struct hdmirx_ext_drv_s *hdmirx_ext_get_driver(void) |
| { |
| if (hdmirx_ext_drv == NULL) |
| RXEXTERR("%s: hdmirx_ext_drv is null\n", __func__); |
| |
| return hdmirx_ext_drv; |
| } |
| |
| /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ |
| |
| static int hdmirx_ext_start(struct hdmirx_ext_drv_s *hdrv) |
| { |
| int ret = 0; |
| |
| RXEXTDBG("%s: start!\n", __func__); |
| ret = __hw_init(); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to init hw\n", __func__); |
| return -2; |
| } |
| ret = __hw_enable(); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to enable hw\n", __func__); |
| return -2; |
| } |
| |
| #if 0 |
| ret = __hw_setup_timer(); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to setup timer, err = %d!\n", |
| __func__, ret); |
| return -3; |
| } |
| |
| ret = __hw_setup_irq(); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to setup irq, err = %d!\n", |
| __func__, ret); |
| return -4; |
| } |
| |
| ret = __hw_setup_task(); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to setup task, err = %d!\n", |
| __func__, ret); |
| return -5; |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| static void hdmirx_ext_device_probe(struct platform_device *pdev) |
| { |
| RXEXTDBG("%s: start\n", __func__); |
| |
| if (!strncmp(hdmirx_ext_drv->name, "sii9135", 7)) { |
| #if defined(CONFIG_AMLOGIC_MEDIA_TVIN_HDMI_EXT_SII9135) |
| hdmirx_ext_register_hw_sii9135(hdmirx_ext_drv); |
| #endif |
| } else { |
| RXEXTERR("%s: invalid device\n", __func__); |
| } |
| } |
| |
| /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ |
| |
| static void hdmirx_ext_set_default_params(void) |
| { |
| hdmirx_ext_drv->hw.i2c_addr = 0xff; |
| hdmirx_ext_drv->hw.i2c_bus_index = -1; |
| hdmirx_ext_drv->hw.en_gpio.gpio = NULL; |
| hdmirx_ext_drv->hw.en_gpio.flag = 0; |
| hdmirx_ext_drv->hw.en_gpio.val_on = -1; |
| hdmirx_ext_drv->hw.en_gpio.val_off = -1; |
| hdmirx_ext_drv->hw.reset_gpio.gpio = NULL; |
| hdmirx_ext_drv->hw.reset_gpio.flag = 0; |
| hdmirx_ext_drv->hw.reset_gpio.val_on = -1; |
| hdmirx_ext_drv->hw.reset_gpio.val_off = -1; |
| hdmirx_ext_drv->hw.pin = NULL; |
| hdmirx_ext_drv->hw.pinmux_flag = 0; |
| /* hdmirx_ext_drv->hw.irq_gpio = 0; */ |
| /* hdmirx_ext_drv->hw.irq_num = -1; */ |
| hdmirx_ext_drv->hw.get_cable_status = NULL; |
| hdmirx_ext_drv->hw.get_signal_status = NULL; |
| hdmirx_ext_drv->hw.get_input_port = NULL; |
| hdmirx_ext_drv->hw.set_input_port = NULL; |
| hdmirx_ext_drv->hw.get_video_timming = NULL; |
| hdmirx_ext_drv->hw.is_hdmi_mode = NULL; |
| hdmirx_ext_drv->hw.get_audio_sample_rate = NULL; |
| hdmirx_ext_drv->hw.debug = NULL; |
| hdmirx_ext_drv->hw.get_chip_version = NULL; |
| hdmirx_ext_drv->hw.init = NULL; |
| hdmirx_ext_drv->hw.enable = NULL; |
| hdmirx_ext_drv->hw.disable = NULL; |
| |
| hdmirx_ext_drv->vdin.vdin_sel = 0; |
| hdmirx_ext_drv->vdin.bt656_sel = 0; |
| hdmirx_ext_drv->vdin.started = 0; |
| hdmirx_ext_drv->user_cmd = 0; |
| |
| hdmirx_ext_drv->status.cable = 0; |
| hdmirx_ext_drv->status.signal = 0; |
| hdmirx_ext_drv->status.video_mode = 0xff; |
| hdmirx_ext_drv->status.audio_sr = 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static int hdmirx_ext_get_dts_config(struct device *dev) |
| { |
| struct device_node *of_node = dev->of_node; |
| unsigned int i2c_index; |
| const char *str; |
| unsigned int val, para[3]; |
| int ret = 0; |
| |
| /* for vdin selection */ |
| ret = of_property_read_u32(of_node, "vdin_sel", &val); |
| if (ret) { |
| RXEXTERR("%s: failed to get vdin_sel\n", __func__); |
| } else { |
| hdmirx_ext_drv->vdin.vdin_sel = (unsigned char)val; |
| RXEXTPR("vdin_sel = %d\n", hdmirx_ext_drv->vdin.vdin_sel); |
| } |
| ret = of_property_read_u32(of_node, "bt656_sel", &val); |
| if (ret) { |
| RXEXTERR("%s: failed to get bt656_sel\n", __func__); |
| } else { |
| hdmirx_ext_drv->vdin.bt656_sel = (unsigned char)val; |
| RXEXTPR("bt656_sel = %d\n", hdmirx_ext_drv->vdin.bt656_sel); |
| } |
| |
| /* for device name */ |
| ret = of_property_read_string(of_node, "ext_dev_name", &str); |
| if (ret) { |
| RXEXTERR("failed to get ext_dev_name\n"); |
| str = "invalid"; |
| } |
| strcpy(hdmirx_ext_drv->name, str); |
| |
| /* for i2c address */ |
| ret = of_property_read_u32(of_node, "i2c_addr", &val); |
| if (ret) { |
| RXEXTERR("%s: failed to get i2c_addr\n", __func__); |
| } else { |
| hdmirx_ext_drv->hw.i2c_addr = (unsigned char)val; |
| RXEXTPR("i2c_addr = 0x%02x\n", hdmirx_ext_drv->hw.i2c_addr); |
| } |
| |
| /* for i2c bus */ |
| ret = of_property_read_string(of_node, "i2c_bus", &str); |
| if (ret) { |
| RXEXTERR("%s: failed to get i2c_bus\n", __func__); |
| return -1; |
| } |
| if (!strncmp(str, "i2c_bus_ao", 10)) |
| i2c_index = AML_I2C_MASTER_AO; |
| else if (!strncmp(str, "i2c_bus_a", 9)) |
| i2c_index = AML_I2C_MASTER_A; |
| else if (!strncmp(str, "i2c_bus_b", 9)) |
| i2c_index = AML_I2C_MASTER_B; |
| else if (!strncmp(str, "i2c_bus_c", 9)) |
| i2c_index = AML_I2C_MASTER_C; |
| else if (!strncmp(str, "i2c_bus_d", 9)) |
| i2c_index = AML_I2C_MASTER_D; |
| else { |
| RXEXTERR("%s: invalid i2c bus \"%s\" from dts\n", |
| __func__, str); |
| return -1; |
| } |
| RXEXTPR("i2c_bus = %s\n", str); |
| hdmirx_ext_drv->hw.i2c_bus_index = i2c_index; |
| |
| /* for gpio_en */ |
| val = GPIOD_OUT_LOW; |
| ret = of_property_read_u32_array(of_node, "en_gpio_val", ¶[0], 2); |
| if (ret) { |
| RXEXTERR("%s: failed to get en_gpio_val\n", __func__); |
| } else { |
| hdmirx_ext_drv->hw.en_gpio.val_on = para[0]; |
| hdmirx_ext_drv->hw.en_gpio.val_off = para[1]; |
| val = hdmirx_ext_drv->hw.en_gpio.val_off ? |
| GPIOD_OUT_LOW : GPIOD_OUT_HIGH; |
| RXEXTPR("en_gpio val_on = %d, val_off=%d\n", |
| hdmirx_ext_drv->hw.en_gpio.val_on, |
| hdmirx_ext_drv->hw.en_gpio.val_off); |
| } |
| /* request gpio */ |
| hdmirx_ext_drv->hw.en_gpio.gpio = devm_gpiod_get(dev, "en", val); |
| if (IS_ERR(hdmirx_ext_drv->hw.en_gpio.gpio)) { |
| hdmirx_ext_drv->hw.en_gpio.flag = 0; |
| RXEXTERR("register en_gpio err\n"); |
| } else { |
| hdmirx_ext_drv->hw.en_gpio.flag = 1; |
| } |
| if (hdmirx_ext_drv->hw.en_gpio.flag) { |
| ret = of_property_read_string(of_node, "en_gpio_name", &str); |
| if (ret) { |
| RXEXTPR("failed to get en_gpio_name\n"); |
| str = "unknown"; |
| } |
| strcpy(hdmirx_ext_drv->hw.en_gpio.name, str); |
| RXEXTPR("register gpio %s: 0x%p\n", |
| hdmirx_ext_drv->hw.en_gpio.name, |
| hdmirx_ext_drv->hw.en_gpio.gpio); |
| } |
| |
| /* for gpio_reset */ |
| val = GPIOD_OUT_LOW; |
| ret = of_property_read_u32_array(of_node, "reset_gpio_val", |
| ¶[0], 2); |
| if (ret) { |
| RXEXTERR("%s: failed to get reset_gpio_val\n", __func__); |
| } else { |
| hdmirx_ext_drv->hw.reset_gpio.val_on = para[0]; |
| hdmirx_ext_drv->hw.reset_gpio.val_off = para[1]; |
| val = hdmirx_ext_drv->hw.reset_gpio.val_on ? |
| GPIOD_OUT_LOW : GPIOD_OUT_HIGH; |
| RXEXTPR("reset_gpio val_on = %d, val_off=%d\n", |
| hdmirx_ext_drv->hw.reset_gpio.val_on, |
| hdmirx_ext_drv->hw.reset_gpio.val_off); |
| } |
| /* request gpio */ |
| hdmirx_ext_drv->hw.reset_gpio.gpio = devm_gpiod_get(dev, "reset", val); |
| if (IS_ERR(hdmirx_ext_drv->hw.reset_gpio.gpio)) { |
| hdmirx_ext_drv->hw.reset_gpio.flag = 0; |
| RXEXTERR("register reset_gpio err\n"); |
| } else { |
| hdmirx_ext_drv->hw.reset_gpio.flag = 1; |
| } |
| if (hdmirx_ext_drv->hw.reset_gpio.flag) { |
| ret = of_property_read_string(of_node, "reset_gpio_name", &str); |
| if (ret) { |
| RXEXTPR("failed to get reset_gpio_name\n"); |
| str = "unknown"; |
| } |
| strcpy(hdmirx_ext_drv->hw.reset_gpio.name, str); |
| RXEXTPR("register gpio %s: 0x%p\n", |
| hdmirx_ext_drv->hw.reset_gpio.name, |
| hdmirx_ext_drv->hw.reset_gpio.gpio); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int hdmirx_ext_open(struct inode *node, struct file *filp) |
| { |
| struct hdmirx_ext_drv_s *pdev; |
| |
| pdev = container_of(node->i_cdev, struct hdmirx_ext_drv_s, cdev); |
| filp->private_data = pdev; |
| |
| return 0; |
| } |
| |
| static int hdmirx_ext_release(struct inode *node, struct file *filp) |
| { |
| return 0; |
| } |
| |
| static const struct file_operations hdmirx_ext_fops = { |
| .owner = THIS_MODULE, |
| .open = hdmirx_ext_open, |
| .release = hdmirx_ext_release, |
| }; |
| |
| static int hdmirx_ext_probe(struct platform_device *pdev) |
| { |
| int ret = -1; |
| |
| hdmirx_ext_debug_print = 0; |
| RXEXTPR("%s: ver: %s\n", __func__, HDMIRX_EXT_DRV_VER); |
| |
| hdmirx_ext_drv = kmalloc(sizeof(struct hdmirx_ext_drv_s), GFP_KERNEL); |
| if (!hdmirx_ext_drv) { |
| ret = -ENOMEM; |
| RXEXTERR("%s: failed to alloc hdmirx_ext_drv!\n", __func__); |
| goto fail; |
| } |
| hdmirx_ext_drv->dev = &pdev->dev; |
| hdmirx_ext_drv->vops = get_vdin_v4l2_ops(); |
| |
| /* hdmirx_ext_drv default value setting */ |
| hdmirx_ext_set_default_params(); |
| |
| #ifdef CONFIG_OF |
| ret = hdmirx_ext_get_dts_config(&pdev->dev); |
| if (ret) { |
| RXEXTERR("%s: invalid dts config\n", __func__); |
| goto cleanup; |
| } |
| #endif |
| hdmirx_ext_device_probe(pdev); |
| |
| ret = alloc_chrdev_region(&hdmirx_ext_device_id, 0, 1, |
| HDMIRX_EXT_DEV_NAME); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to alloc char dev\n", __func__); |
| goto cleanup; |
| } |
| |
| cdev_init(&(hdmirx_ext_drv->cdev), &hdmirx_ext_fops); |
| hdmirx_ext_drv->cdev.owner = THIS_MODULE; |
| |
| ret = cdev_add(&(hdmirx_ext_drv->cdev), hdmirx_ext_device_id, 1); |
| if (ret) { |
| RXEXTERR("%s: failed to add hdmirx_ext cdev, err = %d\n", |
| __func__, ret); |
| goto unregister; |
| } |
| |
| hdmirx_ext_class = class_create(THIS_MODULE, HDMIRX_EXT_DRV_NAME); |
| if (IS_ERR(hdmirx_ext_class)) { |
| ret = PTR_ERR(hdmirx_ext_class); |
| RXEXTERR("%s: failed to create hdmirx_ext class, err = %d\n", |
| __func__, ret); |
| goto destroy_cdev; |
| } |
| |
| hdmirx_ext_device = device_create(hdmirx_ext_class, NULL, |
| hdmirx_ext_device_id, NULL, "%s", hdmirx_ext_drv->name); |
| if (IS_ERR(hdmirx_ext_device)) { |
| ret = PTR_ERR(hdmirx_ext_device); |
| RXEXTERR("%s: failed to create hdmirx_ext device, err = %d\n", |
| __func__, ret); |
| goto destroy_class; |
| } |
| |
| ret = hdmirx_ext_create_device_attrs(hdmirx_ext_device); |
| if (ret < 0) { |
| RXEXTERR("%s: failed to create device attrs, err = %d\n", |
| __func__, ret); |
| goto destroy_device; |
| } |
| |
| /* set drvdata */ |
| dev_set_drvdata(hdmirx_ext_device, hdmirx_ext_drv); |
| |
| ret = hdmirx_ext_start(hdmirx_ext_drv); |
| if (ret) { |
| RXEXTERR("%s: failed to setup resource, err = %d\n", |
| __func__, ret); |
| goto destroy_attrs; |
| } |
| |
| RXEXTPR("driver probe ok\n"); |
| |
| return 0; |
| |
| destroy_attrs: |
| hdmirx_ext_destroy_device_attrs(hdmirx_ext_device); |
| |
| destroy_device: |
| device_destroy(hdmirx_ext_class, hdmirx_ext_device_id); |
| |
| destroy_class: |
| class_destroy(hdmirx_ext_class); |
| |
| destroy_cdev: |
| cdev_del(&(hdmirx_ext_drv->cdev)); |
| |
| unregister: |
| unregister_chrdev_region(MKDEV(MAJOR(hdmirx_ext_device_id), |
| MINOR(hdmirx_ext_device_id)), 1); |
| |
| cleanup: |
| kfree(hdmirx_ext_drv); |
| hdmirx_ext_drv = NULL; |
| |
| fail: |
| return ret; |
| } |
| |
| static int hdmirx_ext_remove(struct platform_device *pdev) |
| { |
| RXEXTPR("%s\n", __func__); |
| |
| hdmirx_ext_destroy_device_attrs(hdmirx_ext_device); |
| |
| if (hdmirx_ext_class) { |
| device_destroy(hdmirx_ext_class, hdmirx_ext_device_id); |
| class_destroy(hdmirx_ext_class); |
| } |
| |
| if (hdmirx_ext_drv) { |
| cdev_del(&(hdmirx_ext_drv->cdev)); |
| kfree(hdmirx_ext_drv); |
| hdmirx_ext_drv = NULL; |
| } |
| |
| unregister_chrdev_region(hdmirx_ext_device_id, 1); |
| |
| return 0; |
| } |
| |
| static int hdmirx_ext_resume(struct platform_device *pdev) |
| { |
| RXEXTPR("%s\n", __func__); |
| return 0; |
| } |
| |
| static int hdmirx_ext_suspend(struct platform_device *pdev, |
| pm_message_t state) |
| { |
| RXEXTPR("%s\n", __func__); |
| if (hdmirx_ext_drv) { |
| if (hdmirx_ext_drv->state) { |
| hdmirx_ext_stop_vdin(); |
| __hw_disable(); |
| } |
| } |
| return 0; |
| } |
| |
| static void hdmirx_ext_shutdown(struct platform_device *pdev) |
| { |
| RXEXTPR("%s\n", __func__); |
| if (hdmirx_ext_drv) { |
| if (hdmirx_ext_drv->state) { |
| hdmirx_ext_stop_vdin(); |
| __hw_disable(); |
| } |
| } |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id hdmirx_ext_dt_match[] = { |
| { |
| .compatible = "amlogic, hdmirx_ext", |
| }, |
| {}, |
| }; |
| #else |
| #define hdmirx_ext_dt_match NULL |
| #endif |
| |
| |
| static struct platform_driver hdmirx_ext_driver = { |
| .probe = hdmirx_ext_probe, |
| .remove = hdmirx_ext_remove, |
| .suspend = hdmirx_ext_suspend, |
| .resume = hdmirx_ext_resume, |
| .shutdown = hdmirx_ext_shutdown, |
| .driver = { |
| .name = HDMIRX_EXT_DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = hdmirx_ext_dt_match, |
| } |
| }; |
| static int __init hdmirx_ext_drv_init(void) |
| { |
| if (platform_driver_register(&hdmirx_ext_driver)) { |
| RXEXTERR("%s: failed to register driver\n", __func__); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit hdmirx_ext_drv_exit(void) |
| { |
| RXEXT_PFUNC(); |
| platform_driver_unregister(&hdmirx_ext_driver); |
| class_unregister(hdmirx_ext_class); |
| } |
| |
| late_initcall(hdmirx_ext_drv_init); |
| module_exit(hdmirx_ext_drv_exit); |
| |
| MODULE_DESCRIPTION("AML external hdmirx driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Amlogic, Inc."); |