|  | // 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); |