18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux 48c2ecf20Sopenharmony_ci * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Based in the radio Maestro PCI driver. Actually it uses the same chip 78c2ecf20Sopenharmony_ci * for radio but different pci controller. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * I didn't have any specs I reversed engineered the protocol from 108c2ecf20Sopenharmony_ci * the windows driver (radio.dll). 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * The card uses the TEA5757 chip that includes a search function but it 138c2ecf20Sopenharmony_ci * is useless as I haven't found any way to read back the frequency. If 148c2ecf20Sopenharmony_ci * anybody does please mail me. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * For the pdf file see: 178c2ecf20Sopenharmony_ci * http://www.nxp.com/acrobat_download2/expired_datasheets/TEA5757_5759_3.pdf 188c2ecf20Sopenharmony_ci * 198c2ecf20Sopenharmony_ci * 208c2ecf20Sopenharmony_ci * CHANGES: 218c2ecf20Sopenharmony_ci * 0.75b 228c2ecf20Sopenharmony_ci * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr> 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * 0.75 Sun Feb 4 22:51:27 EET 2001 258c2ecf20Sopenharmony_ci * - tiding up 268c2ecf20Sopenharmony_ci * - removed support for multiple devices as it didn't work anyway 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * BUGS: 298c2ecf20Sopenharmony_ci * - card unmutes if you change frequency 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * (c) 2006, 2007 by Mauro Carvalho Chehab <mchehab@kernel.org>: 328c2ecf20Sopenharmony_ci * - Conversion to V4L2 API 338c2ecf20Sopenharmony_ci * - Uses video_ioctl2 for parsing and to add debug support 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#include <linux/module.h> 388c2ecf20Sopenharmony_ci#include <linux/init.h> 398c2ecf20Sopenharmony_ci#include <linux/ioport.h> 408c2ecf20Sopenharmony_ci#include <linux/delay.h> 418c2ecf20Sopenharmony_ci#include <linux/mutex.h> 428c2ecf20Sopenharmony_ci#include <linux/pci.h> 438c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 448c2ecf20Sopenharmony_ci#include <linux/io.h> 458c2ecf20Sopenharmony_ci#include <linux/slab.h> 468c2ecf20Sopenharmony_ci#include <media/drv-intf/tea575x.h> 478c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 488c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 498c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h> 508c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 518c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); 548c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000."); 558c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 568c2ecf20Sopenharmony_ciMODULE_VERSION("1.0.0"); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic int radio_nr = -1; 598c2ecf20Sopenharmony_cimodule_param(radio_nr, int, 0644); 608c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device number"); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/* TEA5757 pin mappings */ 638c2ecf20Sopenharmony_cistatic const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic atomic_t maxiradio_instance = ATOMIC_INIT(0); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define PCI_VENDOR_ID_GUILLEMOT 0x5046 688c2ecf20Sopenharmony_ci#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistruct maxiradio 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct snd_tea575x tea; 738c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 748c2ecf20Sopenharmony_ci struct pci_dev *pdev; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci u16 io; /* base of radio io */ 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci return container_of(v4l2_dev, struct maxiradio, v4l2_dev); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct maxiradio *dev = tea->private_data; 878c2ecf20Sopenharmony_ci u8 bits = 0; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci bits |= (pins & TEA575X_DATA) ? data : 0; 908c2ecf20Sopenharmony_ci bits |= (pins & TEA575X_CLK) ? clk : 0; 918c2ecf20Sopenharmony_ci bits |= (pins & TEA575X_WREN) ? wren : 0; 928c2ecf20Sopenharmony_ci bits |= power; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci outb(bits, dev->io); 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/* Note: this card cannot read out the data of the shift registers, 988c2ecf20Sopenharmony_ci only the mono/stereo pin works. */ 998c2ecf20Sopenharmony_cistatic u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea) 1008c2ecf20Sopenharmony_ci{ 1018c2ecf20Sopenharmony_ci struct maxiradio *dev = tea->private_data; 1028c2ecf20Sopenharmony_ci u8 bits = inb(dev->io); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return ((bits & data) ? TEA575X_DATA : 0) | 1058c2ecf20Sopenharmony_ci ((bits & mo_st) ? TEA575X_MOST : 0); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic const struct snd_tea575x_ops maxiradio_tea_ops = { 1138c2ecf20Sopenharmony_ci .set_pins = maxiradio_tea575x_set_pins, 1148c2ecf20Sopenharmony_ci .get_pins = maxiradio_tea575x_get_pins, 1158c2ecf20Sopenharmony_ci .set_direction = maxiradio_tea575x_set_direction, 1168c2ecf20Sopenharmony_ci}; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic int maxiradio_probe(struct pci_dev *pdev, 1198c2ecf20Sopenharmony_ci const struct pci_device_id *ent) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct maxiradio *dev; 1228c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev; 1238c2ecf20Sopenharmony_ci int retval = -ENOMEM; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 1268c2ecf20Sopenharmony_ci if (dev == NULL) { 1278c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "not enough memory\n"); 1288c2ecf20Sopenharmony_ci return -ENOMEM; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci v4l2_dev = &dev->v4l2_dev; 1328c2ecf20Sopenharmony_ci v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci retval = v4l2_device_register(&pdev->dev, v4l2_dev); 1358c2ecf20Sopenharmony_ci if (retval < 0) { 1368c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); 1378c2ecf20Sopenharmony_ci goto errfr; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci dev->tea.private_data = dev; 1408c2ecf20Sopenharmony_ci dev->tea.ops = &maxiradio_tea_ops; 1418c2ecf20Sopenharmony_ci /* The data pin cannot be read. This may be a hardware limitation, or 1428c2ecf20Sopenharmony_ci we just don't know how to read it. */ 1438c2ecf20Sopenharmony_ci dev->tea.cannot_read_data = true; 1448c2ecf20Sopenharmony_ci dev->tea.v4l2_dev = v4l2_dev; 1458c2ecf20Sopenharmony_ci dev->tea.radio_nr = radio_nr; 1468c2ecf20Sopenharmony_ci strscpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card)); 1478c2ecf20Sopenharmony_ci snprintf(dev->tea.bus_info, sizeof(dev->tea.bus_info), 1488c2ecf20Sopenharmony_ci "PCI:%s", pci_name(pdev)); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci retval = -ENODEV; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (!request_region(pci_resource_start(pdev, 0), 1538c2ecf20Sopenharmony_ci pci_resource_len(pdev, 0), v4l2_dev->name)) { 1548c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "can't reserve I/O ports\n"); 1558c2ecf20Sopenharmony_ci goto err_hdl; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci if (pci_enable_device(pdev)) 1598c2ecf20Sopenharmony_ci goto err_out_free_region; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci dev->io = pci_resource_start(pdev, 0); 1628c2ecf20Sopenharmony_ci if (snd_tea575x_init(&dev->tea, THIS_MODULE)) { 1638c2ecf20Sopenharmony_ci printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n"); 1648c2ecf20Sopenharmony_ci goto err_out_free_region; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci return 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cierr_out_free_region: 1698c2ecf20Sopenharmony_ci release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); 1708c2ecf20Sopenharmony_cierr_hdl: 1718c2ecf20Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 1728c2ecf20Sopenharmony_cierrfr: 1738c2ecf20Sopenharmony_ci kfree(dev); 1748c2ecf20Sopenharmony_ci return retval; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic void maxiradio_remove(struct pci_dev *pdev) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); 1808c2ecf20Sopenharmony_ci struct maxiradio *dev = to_maxiradio(v4l2_dev); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci snd_tea575x_exit(&dev->tea); 1838c2ecf20Sopenharmony_ci /* Turn off power */ 1848c2ecf20Sopenharmony_ci outb(0, dev->io); 1858c2ecf20Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 1868c2ecf20Sopenharmony_ci release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); 1878c2ecf20Sopenharmony_ci kfree(dev); 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic const struct pci_device_id maxiradio_pci_tbl[] = { 1918c2ecf20Sopenharmony_ci { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, 1928c2ecf20Sopenharmony_ci PCI_ANY_ID, PCI_ANY_ID, }, 1938c2ecf20Sopenharmony_ci { 0 } 1948c2ecf20Sopenharmony_ci}; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic struct pci_driver maxiradio_driver = { 1998c2ecf20Sopenharmony_ci .name = "radio-maxiradio", 2008c2ecf20Sopenharmony_ci .id_table = maxiradio_pci_tbl, 2018c2ecf20Sopenharmony_ci .probe = maxiradio_probe, 2028c2ecf20Sopenharmony_ci .remove = maxiradio_remove, 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cimodule_pci_driver(maxiradio_driver); 206