162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux 462306a36Sopenharmony_ci * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Based in the radio Maestro PCI driver. Actually it uses the same chip 762306a36Sopenharmony_ci * for radio but different pci controller. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * I didn't have any specs I reversed engineered the protocol from 1062306a36Sopenharmony_ci * the windows driver (radio.dll). 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * The card uses the TEA5757 chip that includes a search function but it 1362306a36Sopenharmony_ci * is useless as I haven't found any way to read back the frequency. If 1462306a36Sopenharmony_ci * anybody does please mail me. 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * For the pdf file see: 1762306a36Sopenharmony_ci * http://www.nxp.com/acrobat_download2/expired_datasheets/TEA5757_5759_3.pdf 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * CHANGES: 2162306a36Sopenharmony_ci * 0.75b 2262306a36Sopenharmony_ci * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr> 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * 0.75 Sun Feb 4 22:51:27 EET 2001 2562306a36Sopenharmony_ci * - tiding up 2662306a36Sopenharmony_ci * - removed support for multiple devices as it didn't work anyway 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * BUGS: 2962306a36Sopenharmony_ci * - card unmutes if you change frequency 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * (c) 2006, 2007 by Mauro Carvalho Chehab <mchehab@kernel.org>: 3262306a36Sopenharmony_ci * - Conversion to V4L2 API 3362306a36Sopenharmony_ci * - Uses video_ioctl2 for parsing and to add debug support 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#include <linux/module.h> 3862306a36Sopenharmony_ci#include <linux/init.h> 3962306a36Sopenharmony_ci#include <linux/ioport.h> 4062306a36Sopenharmony_ci#include <linux/delay.h> 4162306a36Sopenharmony_ci#include <linux/mutex.h> 4262306a36Sopenharmony_ci#include <linux/pci.h> 4362306a36Sopenharmony_ci#include <linux/videodev2.h> 4462306a36Sopenharmony_ci#include <linux/io.h> 4562306a36Sopenharmony_ci#include <linux/slab.h> 4662306a36Sopenharmony_ci#include <media/drv-intf/tea575x.h> 4762306a36Sopenharmony_ci#include <media/v4l2-device.h> 4862306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 4962306a36Sopenharmony_ci#include <media/v4l2-fh.h> 5062306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 5162306a36Sopenharmony_ci#include <media/v4l2-event.h> 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ciMODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); 5462306a36Sopenharmony_ciMODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000."); 5562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 5662306a36Sopenharmony_ciMODULE_VERSION("1.0.0"); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic int radio_nr = -1; 5962306a36Sopenharmony_cimodule_param(radio_nr, int, 0644); 6062306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device number"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* TEA5757 pin mappings */ 6362306a36Sopenharmony_cistatic const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic atomic_t maxiradio_instance = ATOMIC_INIT(0); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define PCI_VENDOR_ID_GUILLEMOT 0x5046 6862306a36Sopenharmony_ci#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistruct maxiradio 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct snd_tea575x tea; 7362306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 7462306a36Sopenharmony_ci struct pci_dev *pdev; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci u16 io; /* base of radio io */ 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci return container_of(v4l2_dev, struct maxiradio, v4l2_dev); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct maxiradio *dev = tea->private_data; 8762306a36Sopenharmony_ci u8 bits = 0; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci bits |= (pins & TEA575X_DATA) ? data : 0; 9062306a36Sopenharmony_ci bits |= (pins & TEA575X_CLK) ? clk : 0; 9162306a36Sopenharmony_ci bits |= (pins & TEA575X_WREN) ? wren : 0; 9262306a36Sopenharmony_ci bits |= power; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci outb(bits, dev->io); 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci/* Note: this card cannot read out the data of the shift registers, 9862306a36Sopenharmony_ci only the mono/stereo pin works. */ 9962306a36Sopenharmony_cistatic u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct maxiradio *dev = tea->private_data; 10262306a36Sopenharmony_ci u8 bits = inb(dev->io); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return ((bits & data) ? TEA575X_DATA : 0) | 10562306a36Sopenharmony_ci ((bits & mo_st) ? TEA575X_MOST : 0); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic const struct snd_tea575x_ops maxiradio_tea_ops = { 11362306a36Sopenharmony_ci .set_pins = maxiradio_tea575x_set_pins, 11462306a36Sopenharmony_ci .get_pins = maxiradio_tea575x_get_pins, 11562306a36Sopenharmony_ci .set_direction = maxiradio_tea575x_set_direction, 11662306a36Sopenharmony_ci}; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int maxiradio_probe(struct pci_dev *pdev, 11962306a36Sopenharmony_ci const struct pci_device_id *ent) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct maxiradio *dev; 12262306a36Sopenharmony_ci struct v4l2_device *v4l2_dev; 12362306a36Sopenharmony_ci int retval = -ENOMEM; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci dev = kzalloc(sizeof(*dev), GFP_KERNEL); 12662306a36Sopenharmony_ci if (dev == NULL) { 12762306a36Sopenharmony_ci dev_err(&pdev->dev, "not enough memory\n"); 12862306a36Sopenharmony_ci return -ENOMEM; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci v4l2_dev = &dev->v4l2_dev; 13262306a36Sopenharmony_ci v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci retval = v4l2_device_register(&pdev->dev, v4l2_dev); 13562306a36Sopenharmony_ci if (retval < 0) { 13662306a36Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); 13762306a36Sopenharmony_ci goto errfr; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci dev->tea.private_data = dev; 14062306a36Sopenharmony_ci dev->tea.ops = &maxiradio_tea_ops; 14162306a36Sopenharmony_ci /* The data pin cannot be read. This may be a hardware limitation, or 14262306a36Sopenharmony_ci we just don't know how to read it. */ 14362306a36Sopenharmony_ci dev->tea.cannot_read_data = true; 14462306a36Sopenharmony_ci dev->tea.v4l2_dev = v4l2_dev; 14562306a36Sopenharmony_ci dev->tea.radio_nr = radio_nr; 14662306a36Sopenharmony_ci strscpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card)); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci retval = -ENODEV; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (!request_region(pci_resource_start(pdev, 0), 15162306a36Sopenharmony_ci pci_resource_len(pdev, 0), v4l2_dev->name)) { 15262306a36Sopenharmony_ci dev_err(&pdev->dev, "can't reserve I/O ports\n"); 15362306a36Sopenharmony_ci goto err_hdl; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (pci_enable_device(pdev)) 15762306a36Sopenharmony_ci goto err_out_free_region; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci dev->io = pci_resource_start(pdev, 0); 16062306a36Sopenharmony_ci if (snd_tea575x_init(&dev->tea, THIS_MODULE)) { 16162306a36Sopenharmony_ci printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n"); 16262306a36Sopenharmony_ci goto err_out_free_region; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cierr_out_free_region: 16762306a36Sopenharmony_ci release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); 16862306a36Sopenharmony_cierr_hdl: 16962306a36Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 17062306a36Sopenharmony_cierrfr: 17162306a36Sopenharmony_ci kfree(dev); 17262306a36Sopenharmony_ci return retval; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic void maxiradio_remove(struct pci_dev *pdev) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); 17862306a36Sopenharmony_ci struct maxiradio *dev = to_maxiradio(v4l2_dev); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci snd_tea575x_exit(&dev->tea); 18162306a36Sopenharmony_ci /* Turn off power */ 18262306a36Sopenharmony_ci outb(0, dev->io); 18362306a36Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 18462306a36Sopenharmony_ci release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); 18562306a36Sopenharmony_ci kfree(dev); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic const struct pci_device_id maxiradio_pci_tbl[] = { 18962306a36Sopenharmony_ci { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, 19062306a36Sopenharmony_ci PCI_ANY_ID, PCI_ANY_ID, }, 19162306a36Sopenharmony_ci { 0 } 19262306a36Sopenharmony_ci}; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic struct pci_driver maxiradio_driver = { 19762306a36Sopenharmony_ci .name = "radio-maxiradio", 19862306a36Sopenharmony_ci .id_table = maxiradio_pci_tbl, 19962306a36Sopenharmony_ci .probe = maxiradio_probe, 20062306a36Sopenharmony_ci .remove = maxiradio_remove, 20162306a36Sopenharmony_ci}; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cimodule_pci_driver(maxiradio_driver); 204