162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * uio_aec.c -- simple driver for Adrienne Electronics Corp time code PCI device
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2008 Brandon Philips <brandon@ifup.org>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/pci.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/interrupt.h>
1362306a36Sopenharmony_ci#include <linux/cdev.h>
1462306a36Sopenharmony_ci#include <linux/fs.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/uaccess.h>
1762306a36Sopenharmony_ci#include <linux/uio_driver.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define PCI_VENDOR_ID_AEC 0xaecb
2162306a36Sopenharmony_ci#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define INT_ENABLE_ADDR		0xFC
2462306a36Sopenharmony_ci#define INT_ENABLE		0x10
2562306a36Sopenharmony_ci#define INT_DISABLE		0x0
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define INT_MASK_ADDR		0x2E
2862306a36Sopenharmony_ci#define INT_MASK_ALL		0x3F
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define INTA_DRVR_ADDR		0xFE
3162306a36Sopenharmony_ci#define INTA_ENABLED_FLAG	0x08
3262306a36Sopenharmony_ci#define INTA_FLAG		0x01
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define MAILBOX			0x0F
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic struct pci_device_id ids[] = {
3762306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), },
3862306a36Sopenharmony_ci	{ 0, }
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, ids);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic irqreturn_t aectc_irq(int irq, struct uio_info *dev_info)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	void __iomem *int_flag = dev_info->priv + INTA_DRVR_ADDR;
4562306a36Sopenharmony_ci	unsigned char status = ioread8(int_flag);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if ((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG)) {
4962306a36Sopenharmony_ci		/* application writes 0x00 to 0x2F to get next interrupt */
5062306a36Sopenharmony_ci		status = ioread8(dev_info->priv + MAILBOX);
5162306a36Sopenharmony_ci		return IRQ_HANDLED;
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return IRQ_NONE;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic void print_board_data(struct pci_dev *pdev, struct uio_info *i)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	dev_info(&pdev->dev, "PCI-TC board vendor: %x%x number: %x%x"
6062306a36Sopenharmony_ci		" revision: %c%c\n",
6162306a36Sopenharmony_ci		ioread8(i->priv + 0x01),
6262306a36Sopenharmony_ci		ioread8(i->priv + 0x00),
6362306a36Sopenharmony_ci		ioread8(i->priv + 0x03),
6462306a36Sopenharmony_ci		ioread8(i->priv + 0x02),
6562306a36Sopenharmony_ci		ioread8(i->priv + 0x06),
6662306a36Sopenharmony_ci		ioread8(i->priv + 0x07));
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int probe(struct pci_dev *pdev, const struct pci_device_id *id)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct uio_info *info;
7262306a36Sopenharmony_ci	int ret;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL);
7562306a36Sopenharmony_ci	if (!info)
7662306a36Sopenharmony_ci		return -ENOMEM;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (pci_enable_device(pdev))
7962306a36Sopenharmony_ci		return -ENODEV;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (pci_request_regions(pdev, "aectc"))
8262306a36Sopenharmony_ci		goto out_disable;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	info->name = "aectc";
8562306a36Sopenharmony_ci	info->port[0].start = pci_resource_start(pdev, 0);
8662306a36Sopenharmony_ci	if (!info->port[0].start)
8762306a36Sopenharmony_ci		goto out_release;
8862306a36Sopenharmony_ci	info->priv = pci_iomap(pdev, 0, 0);
8962306a36Sopenharmony_ci	if (!info->priv)
9062306a36Sopenharmony_ci		goto out_release;
9162306a36Sopenharmony_ci	info->port[0].size = pci_resource_len(pdev, 0);
9262306a36Sopenharmony_ci	info->port[0].porttype = UIO_PORT_GPIO;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	info->version = "0.0.1";
9562306a36Sopenharmony_ci	info->irq = pdev->irq;
9662306a36Sopenharmony_ci	info->irq_flags = IRQF_SHARED;
9762306a36Sopenharmony_ci	info->handler = aectc_irq;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	print_board_data(pdev, info);
10062306a36Sopenharmony_ci	ret = uio_register_device(&pdev->dev, info);
10162306a36Sopenharmony_ci	if (ret)
10262306a36Sopenharmony_ci		goto out_unmap;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	iowrite32(INT_ENABLE, info->priv + INT_ENABLE_ADDR);
10562306a36Sopenharmony_ci	iowrite8(INT_MASK_ALL, info->priv + INT_MASK_ADDR);
10662306a36Sopenharmony_ci	if (!(ioread8(info->priv + INTA_DRVR_ADDR)
10762306a36Sopenharmony_ci			& INTA_ENABLED_FLAG))
10862306a36Sopenharmony_ci		dev_err(&pdev->dev, "aectc: interrupts not enabled\n");
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	pci_set_drvdata(pdev, info);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return 0;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ciout_unmap:
11562306a36Sopenharmony_ci	pci_iounmap(pdev, info->priv);
11662306a36Sopenharmony_ciout_release:
11762306a36Sopenharmony_ci	pci_release_regions(pdev);
11862306a36Sopenharmony_ciout_disable:
11962306a36Sopenharmony_ci	pci_disable_device(pdev);
12062306a36Sopenharmony_ci	return -ENODEV;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic void remove(struct pci_dev *pdev)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct uio_info *info = pci_get_drvdata(pdev);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* disable interrupts */
12862306a36Sopenharmony_ci	iowrite8(INT_DISABLE, info->priv + INT_MASK_ADDR);
12962306a36Sopenharmony_ci	iowrite32(INT_DISABLE, info->priv + INT_ENABLE_ADDR);
13062306a36Sopenharmony_ci	/* read mailbox to ensure board drops irq */
13162306a36Sopenharmony_ci	ioread8(info->priv + MAILBOX);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	uio_unregister_device(info);
13462306a36Sopenharmony_ci	pci_release_regions(pdev);
13562306a36Sopenharmony_ci	pci_disable_device(pdev);
13662306a36Sopenharmony_ci	pci_iounmap(pdev, info->priv);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct pci_driver pci_driver = {
14062306a36Sopenharmony_ci	.name = "aectc",
14162306a36Sopenharmony_ci	.id_table = ids,
14262306a36Sopenharmony_ci	.probe = probe,
14362306a36Sopenharmony_ci	.remove = remove,
14462306a36Sopenharmony_ci};
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cimodule_pci_driver(pci_driver);
14762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
148