/* * speedfax.c low level stuff for Sedlbauer Speedfax+ cards * based on the ISAR DSP * Thanks to Sedlbauer AG for informations and HW * * Author Karsten Keil <keil@isdn4linux.de> * * Copyright 2009 by Karsten Keil <keil@isdn4linux.de> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include <linux/interrupt.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/pci.h> #include <linux/delay.h> #include <linux/mISDNhw.h> #include <linux/firmware.h> #include "ipac.h" #include "isar.h" #define SPEEDFAX_REV "2.0" #define PCI_SUBVENDOR_SPEEDFAX_PYRAMID 0x51 #define PCI_SUBVENDOR_SPEEDFAX_PCI 0x54 #define PCI_SUB_ID_SEDLBAUER 0x01 #define SFAX_PCI_ADDR 0xc8 #define SFAX_PCI_ISAC 0xd0 #define SFAX_PCI_ISAR 0xe0 /* TIGER 100 Registers */ #define TIGER_RESET_ADDR 0x00 #define TIGER_EXTERN_RESET_ON 0x01 #define TIGER_EXTERN_RESET_OFF 0x00 #define TIGER_AUX_CTRL 0x02 #define TIGER_AUX_DATA 0x03 #define TIGER_AUX_IRQMASK 0x05 #define TIGER_AUX_STATUS 0x07 /* Tiger AUX BITs */ #define SFAX_AUX_IOMASK 0xdd /* 1 and 5 are inputs */ #define SFAX_ISAR_RESET_BIT_OFF 0x00 #define SFAX_ISAR_RESET_BIT_ON 0x01 #define SFAX_TIGER_IRQ_BIT 0x02 #define SFAX_LED1_BIT 0x08 #define SFAX_LED2_BIT 0x10 #define SFAX_PCI_RESET_ON (SFAX_ISAR_RESET_BIT_ON) #define SFAX_PCI_RESET_OFF (SFAX_LED1_BIT | SFAX_LED2_BIT) static int sfax_cnt; static u32 debug; static u32 irqloops = 4; struct sfax_hw { struct list_head list; struct pci_dev *pdev; char name[MISDN_MAX_IDLEN]; u32 irq; u32 irqcnt; u32 cfg; struct _ioport p_isac; struct _ioport p_isar; u8 aux_data; spinlock_t lock; /* HW access lock */ struct isac_hw isac; struct isar_hw isar; }; static LIST_HEAD(Cards); static DEFINE_RWLOCK(card_lock); /* protect Cards */ static void _set_debug(struct sfax_hw *card) { card->isac.dch.debug = debug; card->isar.ch[0].bch.debug = debug; card->isar.ch[1].bch.debug = debug; } static int set_debug(const char *val, struct kernel_param *kp) { int ret; struct sfax_hw *card; ret = param_set_uint(val, kp); if (!ret) { read_lock(&card_lock); list_for_each_entry(card, &Cards, list) _set_debug(card); read_unlock(&card_lock); } return ret; } MODULE_AUTHOR("Karsten Keil"); MODULE_LICENSE("GPL v2"); MODULE_VERSION(SPEEDFAX_REV); MODULE_FIRMWARE("isdn/ISAR.BIN"); module_param_call(debug, set_debug, param_get_uint, &debug, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(debug, "Speedfax debug mask"); module_param(irqloops, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(irqloops, "Speedfax maximal irqloops (default 4)"); IOFUNC_IND(ISAC, sfax_hw, p_isac) IOFUNC_IND(ISAR, sfax_hw, p_isar) static irqreturn_t speedfax_irq(int intno, void *dev_id) { struct sfax_hw *sf = dev_id; u8 val; int cnt = irqloops; spin_lock(&sf->lock); val = inb(sf->cfg + TIGER_AUX_STATUS); if (val & SFAX_TIGER_IRQ_BIT) { /* for us or shared ? */ spin_unlock(&sf->lock); return IRQ_NONE; /* shared */ } sf->irqcnt++; val = ReadISAR_IND(sf, ISAR_IRQBIT); Start_ISAR: if (val & ISAR_IRQSTA) mISDNisar_irq(&sf->isar); val = ReadISAC_IND(sf, ISAC_ISTA); if (val) mISDNisac_irq(&sf->isac, val); val = ReadISAR_IND(sf, ISAR_IRQBIT); if ((val & ISAR_IRQSTA) && cnt--) goto Start_ISAR; if (cnt < irqloops) pr_debug("%s: %d irqloops cpu%d\n", sf->name, irqloops - cnt, smp_processor_id()); if (irqloops && !cnt) pr_notice("%s: %d IRQ LOOP cpu%d\n", sf->name, irqloops, smp_processor_id()); spin_unlock(&sf->lock); return IRQ_HANDLED; } static void enable_hwirq(struct sfax_hw *sf) { WriteISAC_IND(sf, ISAC_MASK, 0); WriteISAR_IND(sf, ISAR_IRQBIT, ISAR_IRQMSK); outb(SFAX_TIGER_IRQ_BIT, sf->cfg + TIGER_AUX_IRQMASK); } static void disable_hwirq(struct sfax_hw *sf) { WriteISAC_IND(sf, ISAC_MASK, 0xFF); WriteISAR_IND(sf, ISAR_IRQBIT, 0); outb(0, sf->cfg + TIGER_AUX_IRQMASK); } static void reset_speedfax(struct sfax_hw *sf) { pr_debug("%s: resetting card\n", sf->name); outb(TIGER_EXTERN_RESET_ON, sf->cfg + TIGER_RESET_ADDR); outb(SFAX_PCI_RESET_ON, sf->cfg + TIGER_AUX_DATA); mdelay(1); outb(TIGER_EXTERN_RESET_OFF, sf->cfg + TIGER_RESET_ADDR); sf->aux_data = SFAX_PCI_RESET_OFF; outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); mdelay(1); } static int sfax_ctrl(struct sfax_hw *sf, u32 cmd, u_long arg) { int ret = 0; switch (cmd) { case HW_RESET_REQ: reset_speedfax(sf); break; case HW_ACTIVATE_IND: if (arg & 1) sf->aux_data &= ~SFAX_LED1_BIT; if (arg & 2) sf->aux_data &= ~SFAX_LED2_BIT; outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); break; case HW_DEACT_IND: if (arg & 1) sf->aux_data |= SFAX_LED1_BIT; if (arg & 2) sf->aux_data |= SFAX_LED2_BIT; outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA); break; default: pr_info("%s: %s unknown command %x %lx\n", sf->name, __func__, cmd, arg); ret = -EINVAL; break; } return ret; } static int channel_ctrl(struct sfax_hw *sf, struct mISDN_ctrl_req *cq) { int ret = 0; switch (cq->op) { case MISDN_CTRL_GETOP: cq->op = MISDN_CTRL_LOOP | MISDN_CTRL_L1_TIMER3; break; case MISDN_CTRL_LOOP: /* cq->channel: 0 disable, 1 B1 loop 2 B2 loop, 3 both */ if (cq->channel < 0 || cq->channel > 3) { ret = -EINVAL; break; } ret = sf->isac.ctrl(&sf->isac, HW_TESTLOOP, cq->channel); break; case MISDN_CTRL_L1_TIMER3: ret = sf->isac.ctrl(&sf->isac, HW_TIMER3_VALUE, cq->p1); break; default: pr_info("%s: unknown Op %x\n", sf->name, cq->op); ret = -EINVAL; break; } return ret; } static int sfax_dctrl(struct mISDNchannel *ch, u32 cmd, void *arg) { struct mISDNdevice *dev = container_of(ch, struct mISDNdevice, D); struct dchannel *dch = container_of(dev, struct dchannel, dev); struct sfax_hw *sf = dch->hw; struct channel_req *rq; int err = 0; pr_debug("%s: cmd:%x %p\n", sf->name, cmd, arg); switch (cmd) { case OPEN_CHANNEL: rq = arg; if (rq->protocol == ISDN_P_TE_S0) err = sf->isac.open(&sf->isac, rq); else err = sf->isar.open(&sf->isar, rq); if (err) break; if (!try_module_get(THIS_MODULE)) pr_info("%s: cannot get module\n", sf->name); break; case CLOSE_CHANNEL: pr_debug("%s: dev(%d) close from %p\n", sf->name, dch->dev.id, __builtin_return_address(0)); module_put(THIS_MODULE); break; case CONTROL_CHANNEL: err = channel_ctrl(sf, arg); break; default: pr_debug("%s: unknown command %x\n", sf->name, cmd); return -EINVAL; } return err; } static int init_card(struct sfax_hw *sf) { int ret, cnt = 3; u_long flags; ret = request_irq(sf->irq, speedfax_irq, IRQF_SHARED, sf->name, sf); if (ret) { pr_info("%s: couldn't get interrupt %d\n", sf->name, sf->irq); return ret; } while (cnt--) { spin_lock_irqsave(&sf->lock, flags); ret = sf->isac.init(&sf->isac); if (ret) { spin_unlock_irqrestore(&sf->lock, flags); pr_info("%s: ISAC init failed with %d\n", sf->name, ret); break; } enable_hwirq(sf); /* RESET Receiver and Transmitter */ WriteISAC_IND(sf, ISAC_CMDR, 0x41); spin_unlock_irqrestore(&sf->lock, flags); msleep_interruptible(10); if (debug & DEBUG_HW) pr_notice("%s: IRQ %d count %d\n", sf->name, sf->irq, sf->irqcnt); if (!sf->irqcnt) { pr_info("%s: IRQ(%d) got no requests during init %d\n", sf->name, sf->irq, 3 - cnt); } else return 0; } free_irq(sf->irq, sf); return -EIO; } static int setup_speedfax(struct sfax_hw *sf) { u_long flags; if (!request_region(sf->cfg, 256, sf->name)) { pr_info("mISDN: %s config port %x-%x already in use\n", sf->name, sf->cfg, sf->cfg + 255); return -EIO; } outb(0xff, sf->cfg); outb(0, sf->cfg); outb(0xdd, sf->cfg + TIGER_AUX_CTRL); outb(0, sf->cfg + TIGER_AUX_IRQMASK); sf->isac.type = IPAC_TYPE_ISAC; sf->p_isac.ale = sf->cfg + SFAX_PCI_ADDR; sf->p_isac.port = sf->cfg + SFAX_PCI_ISAC; sf->p_isar.ale = sf->cfg + SFAX_PCI_ADDR; sf->p_isar.port = sf->cfg + SFAX_PCI_ISAR; ASSIGN_FUNC(IND, ISAC, sf->isac); ASSIGN_FUNC(IND, ISAR, sf->isar); spin_lock_irqsave(&sf->lock, flags); reset_speedfax(sf); disable_hwirq(sf); spin_unlock_irqrestore(&sf->lock, flags); return 0; } static void release_card(struct sfax_hw *card) { u_long flags; spin_lock_irqsave(&card->lock, flags); disable_hwirq(card); spin_unlock_irqrestore(&card->lock, flags); card->isac.release(&card->isac); free_irq(card->irq, card); card->isar.release(&card->isar); mISDN_unregister_device(&card->isac.dch.dev); release_region(card->cfg, 256); pci_disable_device(card->pdev); pci_set_drvdata(card->pdev, NULL); write_lock_irqsave(&card_lock, flags); list_del(&card->list); write_unlock_irqrestore(&card_lock, flags); kfree(card); sfax_cnt--; } static int setup_instance(struct sfax_hw *card) { const struct firmware *firmware; int i, err; u_long flags; snprintf(card->name, MISDN_MAX_IDLEN - 1, "Speedfax.%d", sfax_cnt + 1); write_lock_irqsave(&card_lock, flags); list_add_tail(&card->list, &Cards); write_unlock_irqrestore(&card_lock, flags); _set_debug(card); spin_lock_init(&card->lock); card->isac.hwlock = &card->lock; card->isar.hwlock = &card->lock; card->isar.ctrl = (void *)&sfax_ctrl; card->isac.name = card->name; card->isar.name = card->name; card->isar.owner = THIS_MODULE; err = request_firmware(&firmware, "isdn/ISAR.BIN", &card->pdev->dev); if (err < 0) { pr_info("%s: firmware request failed %d\n", card->name, err); goto error_fw; } if (debug & DEBUG_HW) pr_notice("%s: got firmware %zu bytes\n", card->name, firmware->size); mISDNisac_init(&card->isac, card); card->isac.dch.dev.D.ctrl = sfax_dctrl; card->isac.dch.dev.Bprotocols = mISDNisar_init(&card->isar, card); for (i = 0; i < 2; i++) { set_channelmap(i + 1, card->isac.dch.dev.channelmap); list_add(&card->isar.ch[i].bch.ch.list, &card->isac.dch.dev.bchannels); } err = setup_speedfax(card); if (err) goto error_setup; err = card->isar.init(&card->isar); if (err) goto error; err = mISDN_register_device(&card->isac.dch.dev, &card->pdev->dev, card->name); if (err) goto error; err = init_card(card); if (err) goto error_init; err = card->isar.firmware(&card->isar, firmware->data, firmware->size); if (!err) { release_firmware(firmware); sfax_cnt++; pr_notice("SpeedFax %d cards installed\n", sfax_cnt); return 0; } disable_hwirq(card); free_irq(card->irq, card); error_init: mISDN_unregister_device(&card->isac.dch.dev); error: release_region(card->cfg, 256); error_setup: card->isac.release(&card->isac); card->isar.release(&card->isar); release_firmware(firmware); error_fw: pci_disable_device(card->pdev); write_lock_irqsave(&card_lock, flags); list_del(&card->list); write_unlock_irqrestore(&card_lock, flags); kfree(card); return err; } static int sfaxpci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int err = -ENOMEM; struct sfax_hw *card = kzalloc(sizeof(struct sfax_hw), GFP_KERNEL); if (!card) { pr_info("No memory for Speedfax+ PCI\n"); return err; } card->pdev = pdev; err = pci_enable_device(pdev); if (err) { kfree(card); return err; } pr_notice("mISDN: Speedfax found adapter %s at %s\n", (char *)ent->driver_data, pci_name(pdev)); card->cfg = pci_resource_start(pdev, 0); card->irq = pdev->irq; pci_set_drvdata(pdev, card); err = setup_instance(card); if (err) pci_set_drvdata(pdev, NULL); return err; } static void sfax_remove_pci(struct pci_dev *pdev) { struct sfax_hw *card = pci_get_drvdata(pdev); if (card) release_card(card); else pr_debug("%s: drvdata already removed\n", __func__); } static struct pci_device_id sfaxpci_ids[] = { { PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100, PCI_SUBVENDOR_SPEEDFAX_PYRAMID, PCI_SUB_ID_SEDLBAUER, 0, 0, (unsigned long) "Pyramid Speedfax + PCI" }, { PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100, PCI_SUBVENDOR_SPEEDFAX_PCI, PCI_SUB_ID_SEDLBAUER, 0, 0, (unsigned long) "Sedlbauer Speedfax + PCI" }, { } }; MODULE_DEVICE_TABLE(pci, sfaxpci_ids); static struct pci_driver sfaxpci_driver = { .name = "speedfax+ pci", .probe = sfaxpci_probe, .remove = sfax_remove_pci, .id_table = sfaxpci_ids, }; static int __init Speedfax_init(void) { int err; pr_notice("Sedlbauer Speedfax+ Driver Rev. %s\n", SPEEDFAX_REV); err = pci_register_driver(&sfaxpci_driver); return err; } static void __exit Speedfax_cleanup(void) { pci_unregister_driver(&sfaxpci_driver); } module_init(Speedfax_init); module_exit(Speedfax_cleanup);