| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * 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> |
| */ |
| |
| #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, const 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_DESCRIPTION("mISDN driver for Sedlbauer Speedfax+ cards"); |
| 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); |