| /* |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * |
| * Description: |
| */ |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| |
| #include "aml_pcmcia.h" |
| #include "aml_ci.h" |
| |
| static int aml_pcmcia_debug = 1; |
| |
| module_param_named(pcmcia_debug, aml_pcmcia_debug, int, 0644); |
| MODULE_PARM_DESC(pcmcia_debug, "enable verbose debug messages"); |
| |
| #define pr_dbg(args...)\ |
| do {\ |
| if (aml_pcmcia_debug)\ |
| printk(args);\ |
| } while (0) |
| #define pr_error(fmt, args...) printk("PCMCIA: " fmt, ## args) |
| |
| |
| static int pcmcia_plugin(struct aml_pcmcia *pc) |
| { |
| if (pc->slot_state == MODULE_XTRACTED) { |
| pr_dbg(" CAM Plugged IN: Adapter(%d) Slot(0)\n", 0); |
| udelay(50); |
| aml_pcmcia_reset(pc); |
| /*wait unplug*/ |
| pc->init_irq(pc, IRQF_TRIGGER_RISING); |
| udelay(500); |
| pc->slot_state = MODULE_INSERTED; |
| } else { |
| pr_error("repeat into pcmcia insert \r\n"); |
| aml_pcmcia_reset(pc); |
| } |
| udelay(100); |
| pc->pcmcia_plugin(pc, 1); |
| |
| return 0; |
| } |
| |
| static int pcmcia_unplug(struct aml_pcmcia *pc) |
| { |
| if (pc->slot_state == MODULE_INSERTED) { |
| pr_dbg(" CAM Unplugged: Adapter(%d) Slot(0)\n", 0); |
| /*udelay(50);*/ |
| /*aml_pcmcia_reset(pc);*/ |
| /*wait plugin*/ |
| pc->init_irq(pc, IRQF_TRIGGER_FALLING); |
| udelay(500); |
| pc->slot_state = MODULE_XTRACTED; |
| } |
| udelay(100); |
| pc->pcmcia_plugin(pc, 0); |
| |
| return 0; |
| } |
| |
| static irqreturn_t pcmcia_irq_handler(int irq, void *dev_id) |
| { |
| struct aml_pcmcia *pc = (struct aml_pcmcia *)dev_id; |
| pr_dbg("pcmcia_irq_handler--into--\r\n"); |
| disable_irq_nosync(pc->irq); |
| schedule_work(&pc->pcmcia_work); |
| enable_irq(pc->irq); |
| return IRQ_HANDLED; |
| } |
| |
| static void aml_pcmcia_work(struct work_struct *work) |
| { |
| int cd1, cd2; |
| struct aml_pcmcia *pc = container_of( |
| work, struct aml_pcmcia, pcmcia_work); |
| |
| cd1 = pc->get_cd1(pc); |
| cd2 = pc->get_cd2(pc); |
| |
| if (cd1 != cd2) |
| pr_error("CAM card not inerted.\n"); |
| else { |
| if (!cd1) { |
| pr_error("Adapter(%d) Slot(0): CAM Plugin\n", 0); |
| pcmcia_plugin(pc); |
| } else { |
| pr_error("Adapter(%d) Slot(0): CAM Unplug\n", 0); |
| pcmcia_unplug(pc); |
| } |
| } |
| } |
| |
| static void aml_pcmcia_detect_cam(struct aml_pcmcia *pc) |
| { |
| int cd1, cd2; |
| |
| if (pc == NULL) { |
| return; |
| } |
| cd1 = pc->get_cd1(pc); |
| cd2 = pc->get_cd2(pc); |
| |
| if (cd1 != cd2) |
| pr_error("CAM card not inerted. check end\n"); |
| else { |
| if (!cd1) { |
| pr_error("Adapter(%d) Slot(0): CAM Plugin\n", 0); |
| pcmcia_plugin(pc); |
| } else { |
| pr_error("Adapter(%d) Slot(0): CAM Unplug\n", 0); |
| pcmcia_unplug(pc); |
| } |
| } |
| } |
| static struct aml_pcmcia *pc_cur; |
| |
| int aml_pcmcia_init(struct aml_pcmcia *pc) |
| { |
| int err = 0; |
| unsigned long mode; |
| pr_dbg("aml_pcmcia_init start pc->irq=%d\r\n", pc->irq); |
| pc->rst(pc, AML_L); |
| /*power on*/ |
| pc->pwr(pc, AML_PWR_OPEN);/*hi is open power*/ |
| /*assuming cam unpluged, config the INT to waiting-for-plugin mode*/ |
| pc->init_irq(pc, IRQF_TRIGGER_LOW); |
| |
| INIT_WORK(&pc->pcmcia_work, aml_pcmcia_work); |
| |
| mode = IRQF_ONESHOT; |
| if (pc->io_device_type == AML_DVB_IO_TYPE_SPI_T312) { |
| mode = mode | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; |
| } |
| |
| err = request_irq(pc->irq, |
| pcmcia_irq_handler, |
| mode, "aml-pcmcia", pc); |
| if (err != 0) { |
| pr_error("ERROR: IRQ registration failed ! <%d>", err); |
| return -ENODEV; |
| } |
| |
| pc_cur = pc; |
| pr_dbg("aml_pcmcia_init ok\r\n"); |
| if (pc->io_device_type == AML_DVB_IO_TYPE_SPI_T312) { |
| //mcu start very fast,so she can detect cam before soc init end. |
| //so we need add detect cam fun for first time. |
| aml_pcmcia_detect_cam(pc); |
| } |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_pcmcia_init); |
| |
| int aml_pcmcia_exit(struct aml_pcmcia *pc) |
| { |
| free_irq(pc->irq, pc); |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_pcmcia_exit); |
| |
| int aml_pcmcia_reset(struct aml_pcmcia *pc) |
| { |
| pr_dbg("CAM RESET-->\n"); |
| /* viaccess neotion cam need delay 2000 and 3000 */ |
| /* smit cam need delay 1000 and 1500 */ |
| /* need change delay according cam vendor */ |
| pc->rst(pc, AML_H);/*HI is reset*/ |
| mdelay(1000); |
| pc->rst(pc, AML_L);/*defaule LOW*/ |
| pr_dbg("CAM RESET--\n"); |
| mdelay(1500); |
| pr_dbg("CAM RESET--end\n"); |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_pcmcia_reset); |
| |
| |
| #if 0 |
| static ssize_t aml_pcmcia_test_cmd(struct class *class, |
| struct class_attribute *attr, const char *buf, size_t size) |
| { |
| pr_dbg("pcmcia cmd: %s\n", buf); |
| if (pc_cur) { |
| if (memcmp(buf, "reset", 5) == 0) |
| aml_pcmcia_reset(pc_cur); |
| else if (memcmp(buf, "on", 2) == 0) |
| pc_cur->pwr(pc_cur, AML_PWR_OPEN); |
| else if (memcmp(buf, "off", 3) == 0) |
| pc_cur->pwr(pc_cur, AML_PWR_CLOSE); |
| else if (memcmp(buf, "poll", 4) == 0) |
| schedule_work(&pc_cur->pcmcia_work); |
| else if (memcmp(buf, "intr", 4) == 0) |
| pc_cur->init_irq(pc_cur, IRQF_TRIGGER_RISING); |
| else if (memcmp(buf, "intf", 4) == 0) |
| pc_cur->init_irq(pc_cur, IRQF_TRIGGER_FALLING); |
| } |
| return size; |
| } |
| |
| static struct class_attribute aml_pcmcia_class_attrs[] = { |
| __ATTR(cmd, S_IRUGO | S_IWUSR, NULL, aml_pcmcia_test_cmd), |
| __ATTR_NULL |
| }; |
| |
| static struct class aml_pcmcia_class = { |
| .name = "aml_pcmcia_test", |
| .class_attrs = aml_pcmcia_class_attrs, |
| }; |
| |
| static int __init aml_pcmcia_mod_init(void) |
| { |
| pr_dbg("Amlogic PCMCIA Init\n"); |
| |
| class_register(&aml_pcmcia_class); |
| |
| return 0; |
| } |
| |
| static void __exit aml_pcmcia_mod_exit(void) |
| { |
| pr_dbg("Amlogic PCMCIA Exit\n"); |
| |
| class_unregister(&aml_pcmcia_class); |
| } |
| |
| |
| |
| module_init(aml_pcmcia_mod_init); |
| module_exit(aml_pcmcia_mod_exit); |
| |
| MODULE_LICENSE("GPL"); |
| #endif |