162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Framework for ISA radio drivers.
462306a36Sopenharmony_ci * This takes care of all the V4L2 scaffolding, allowing the ISA drivers
562306a36Sopenharmony_ci * to concentrate on the actual hardware operation.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/ioport.h>
1362306a36Sopenharmony_ci#include <linux/delay.h>
1462306a36Sopenharmony_ci#include <linux/videodev2.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <media/v4l2-device.h>
1862306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
1962306a36Sopenharmony_ci#include <media/v4l2-fh.h>
2062306a36Sopenharmony_ci#include <media/v4l2-ctrls.h>
2162306a36Sopenharmony_ci#include <media/v4l2-event.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "radio-isa.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil");
2662306a36Sopenharmony_ciMODULE_DESCRIPTION("A framework for ISA radio drivers.");
2762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define FREQ_LOW  (87U * 16000U)
3062306a36Sopenharmony_ci#define FREQ_HIGH (108U * 16000U)
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int radio_isa_querycap(struct file *file, void  *priv,
3362306a36Sopenharmony_ci					struct v4l2_capability *v)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	strscpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver));
3862306a36Sopenharmony_ci	strscpy(v->card, isa->drv->card, sizeof(v->card));
3962306a36Sopenharmony_ci	snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name);
4062306a36Sopenharmony_ci	return 0;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int radio_isa_g_tuner(struct file *file, void *priv,
4462306a36Sopenharmony_ci				struct v4l2_tuner *v)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
4762306a36Sopenharmony_ci	const struct radio_isa_ops *ops = isa->drv->ops;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (v->index > 0)
5062306a36Sopenharmony_ci		return -EINVAL;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	strscpy(v->name, "FM", sizeof(v->name));
5362306a36Sopenharmony_ci	v->type = V4L2_TUNER_RADIO;
5462306a36Sopenharmony_ci	v->rangelow = FREQ_LOW;
5562306a36Sopenharmony_ci	v->rangehigh = FREQ_HIGH;
5662306a36Sopenharmony_ci	v->capability = V4L2_TUNER_CAP_LOW;
5762306a36Sopenharmony_ci	if (isa->drv->has_stereo)
5862306a36Sopenharmony_ci		v->capability |= V4L2_TUNER_CAP_STEREO;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (ops->g_rxsubchans)
6162306a36Sopenharmony_ci		v->rxsubchans = ops->g_rxsubchans(isa);
6262306a36Sopenharmony_ci	else
6362306a36Sopenharmony_ci		v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
6462306a36Sopenharmony_ci	v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
6562306a36Sopenharmony_ci	if (ops->g_signal)
6662306a36Sopenharmony_ci		v->signal = ops->g_signal(isa);
6762306a36Sopenharmony_ci	else
6862306a36Sopenharmony_ci		v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ?
6962306a36Sopenharmony_ci								0xffff : 0;
7062306a36Sopenharmony_ci	return 0;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int radio_isa_s_tuner(struct file *file, void *priv,
7462306a36Sopenharmony_ci				const struct v4l2_tuner *v)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
7762306a36Sopenharmony_ci	const struct radio_isa_ops *ops = isa->drv->ops;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (v->index)
8062306a36Sopenharmony_ci		return -EINVAL;
8162306a36Sopenharmony_ci	if (ops->s_stereo) {
8262306a36Sopenharmony_ci		isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO);
8362306a36Sopenharmony_ci		return ops->s_stereo(isa, isa->stereo);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci	return 0;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int radio_isa_s_frequency(struct file *file, void *priv,
8962306a36Sopenharmony_ci				const struct v4l2_frequency *f)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
9262306a36Sopenharmony_ci	u32 freq = f->frequency;
9362306a36Sopenharmony_ci	int res;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci	freq = clamp(freq, FREQ_LOW, FREQ_HIGH);
9862306a36Sopenharmony_ci	res = isa->drv->ops->s_frequency(isa, freq);
9962306a36Sopenharmony_ci	if (res == 0)
10062306a36Sopenharmony_ci		isa->freq = freq;
10162306a36Sopenharmony_ci	return res;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int radio_isa_g_frequency(struct file *file, void *priv,
10562306a36Sopenharmony_ci				struct v4l2_frequency *f)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (f->tuner != 0)
11062306a36Sopenharmony_ci		return -EINVAL;
11162306a36Sopenharmony_ci	f->type = V4L2_TUNER_RADIO;
11262306a36Sopenharmony_ci	f->frequency = isa->freq;
11362306a36Sopenharmony_ci	return 0;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct radio_isa_card *isa =
11962306a36Sopenharmony_ci		container_of(ctrl->handler, struct radio_isa_card, hdl);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	switch (ctrl->id) {
12262306a36Sopenharmony_ci	case V4L2_CID_AUDIO_MUTE:
12362306a36Sopenharmony_ci		return isa->drv->ops->s_mute_volume(isa, ctrl->val,
12462306a36Sopenharmony_ci				isa->volume ? isa->volume->val : 0);
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci	return -EINVAL;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int radio_isa_log_status(struct file *file, void *priv)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct radio_isa_card *isa = video_drvdata(file);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io);
13462306a36Sopenharmony_ci	v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name);
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops radio_isa_ctrl_ops = {
13962306a36Sopenharmony_ci	.s_ctrl = radio_isa_s_ctrl,
14062306a36Sopenharmony_ci};
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic const struct v4l2_file_operations radio_isa_fops = {
14362306a36Sopenharmony_ci	.owner		= THIS_MODULE,
14462306a36Sopenharmony_ci	.open		= v4l2_fh_open,
14562306a36Sopenharmony_ci	.release	= v4l2_fh_release,
14662306a36Sopenharmony_ci	.poll		= v4l2_ctrl_poll,
14762306a36Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops radio_isa_ioctl_ops = {
15162306a36Sopenharmony_ci	.vidioc_querycap    = radio_isa_querycap,
15262306a36Sopenharmony_ci	.vidioc_g_tuner     = radio_isa_g_tuner,
15362306a36Sopenharmony_ci	.vidioc_s_tuner     = radio_isa_s_tuner,
15462306a36Sopenharmony_ci	.vidioc_g_frequency = radio_isa_g_frequency,
15562306a36Sopenharmony_ci	.vidioc_s_frequency = radio_isa_s_frequency,
15662306a36Sopenharmony_ci	.vidioc_log_status  = radio_isa_log_status,
15762306a36Sopenharmony_ci	.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
15862306a36Sopenharmony_ci	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ciint radio_isa_match(struct device *pdev, unsigned int dev)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct radio_isa_driver *drv = pdev->platform_data;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	return drv->probe || drv->io_params[dev] >= 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_isa_match);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	int i;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	for (i = 0; i < drv->num_of_io_ports; i++)
17462306a36Sopenharmony_ci		if (drv->io_ports[i] == io)
17562306a36Sopenharmony_ci			return true;
17662306a36Sopenharmony_ci	return false;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic struct radio_isa_card *radio_isa_alloc(struct radio_isa_driver *drv,
18062306a36Sopenharmony_ci				struct device *pdev)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct v4l2_device *v4l2_dev;
18362306a36Sopenharmony_ci	struct radio_isa_card *isa = drv->ops->alloc();
18462306a36Sopenharmony_ci	if (!isa)
18562306a36Sopenharmony_ci		return NULL;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	dev_set_drvdata(pdev, isa);
18862306a36Sopenharmony_ci	isa->drv = drv;
18962306a36Sopenharmony_ci	v4l2_dev = &isa->v4l2_dev;
19062306a36Sopenharmony_ci	strscpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name));
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return isa;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic int radio_isa_common_probe(struct radio_isa_card *isa,
19662306a36Sopenharmony_ci				  struct device *pdev,
19762306a36Sopenharmony_ci				  int radio_nr, unsigned region_size)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	const struct radio_isa_driver *drv = isa->drv;
20062306a36Sopenharmony_ci	const struct radio_isa_ops *ops = drv->ops;
20162306a36Sopenharmony_ci	struct v4l2_device *v4l2_dev = &isa->v4l2_dev;
20262306a36Sopenharmony_ci	int res;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (!request_region(isa->io, region_size, v4l2_dev->name)) {
20562306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io);
20662306a36Sopenharmony_ci		kfree(isa);
20762306a36Sopenharmony_ci		return -EBUSY;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	res = v4l2_device_register(pdev, v4l2_dev);
21162306a36Sopenharmony_ci	if (res < 0) {
21262306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
21362306a36Sopenharmony_ci		goto err_dev_reg;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	v4l2_ctrl_handler_init(&isa->hdl, 1);
21762306a36Sopenharmony_ci	isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
21862306a36Sopenharmony_ci				V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
21962306a36Sopenharmony_ci	if (drv->max_volume)
22062306a36Sopenharmony_ci		isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
22162306a36Sopenharmony_ci			V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1,
22262306a36Sopenharmony_ci			drv->max_volume);
22362306a36Sopenharmony_ci	v4l2_dev->ctrl_handler = &isa->hdl;
22462306a36Sopenharmony_ci	if (isa->hdl.error) {
22562306a36Sopenharmony_ci		res = isa->hdl.error;
22662306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "Could not register controls\n");
22762306a36Sopenharmony_ci		goto err_hdl;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci	if (drv->max_volume)
23062306a36Sopenharmony_ci		v4l2_ctrl_cluster(2, &isa->mute);
23162306a36Sopenharmony_ci	v4l2_dev->ctrl_handler = &isa->hdl;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	mutex_init(&isa->lock);
23462306a36Sopenharmony_ci	isa->vdev.lock = &isa->lock;
23562306a36Sopenharmony_ci	strscpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name));
23662306a36Sopenharmony_ci	isa->vdev.v4l2_dev = v4l2_dev;
23762306a36Sopenharmony_ci	isa->vdev.fops = &radio_isa_fops;
23862306a36Sopenharmony_ci	isa->vdev.ioctl_ops = &radio_isa_ioctl_ops;
23962306a36Sopenharmony_ci	isa->vdev.release = video_device_release_empty;
24062306a36Sopenharmony_ci	isa->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
24162306a36Sopenharmony_ci	video_set_drvdata(&isa->vdev, isa);
24262306a36Sopenharmony_ci	isa->freq = FREQ_LOW;
24362306a36Sopenharmony_ci	isa->stereo = drv->has_stereo;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	if (ops->init)
24662306a36Sopenharmony_ci		res = ops->init(isa);
24762306a36Sopenharmony_ci	if (!res)
24862306a36Sopenharmony_ci		res = v4l2_ctrl_handler_setup(&isa->hdl);
24962306a36Sopenharmony_ci	if (!res)
25062306a36Sopenharmony_ci		res = ops->s_frequency(isa, isa->freq);
25162306a36Sopenharmony_ci	if (!res && ops->s_stereo)
25262306a36Sopenharmony_ci		res = ops->s_stereo(isa, isa->stereo);
25362306a36Sopenharmony_ci	if (res < 0) {
25462306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "Could not setup card\n");
25562306a36Sopenharmony_ci		goto err_hdl;
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci	res = video_register_device(&isa->vdev, VFL_TYPE_RADIO, radio_nr);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	if (res < 0) {
26062306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "Could not register device node\n");
26162306a36Sopenharmony_ci		goto err_hdl;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n",
26562306a36Sopenharmony_ci			drv->card, isa->io);
26662306a36Sopenharmony_ci	return 0;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cierr_hdl:
26962306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&isa->hdl);
27062306a36Sopenharmony_cierr_dev_reg:
27162306a36Sopenharmony_ci	release_region(isa->io, region_size);
27262306a36Sopenharmony_ci	kfree(isa);
27362306a36Sopenharmony_ci	return res;
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic void radio_isa_common_remove(struct radio_isa_card *isa,
27762306a36Sopenharmony_ci				    unsigned region_size)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	const struct radio_isa_ops *ops = isa->drv->ops;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0);
28262306a36Sopenharmony_ci	video_unregister_device(&isa->vdev);
28362306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&isa->hdl);
28462306a36Sopenharmony_ci	v4l2_device_unregister(&isa->v4l2_dev);
28562306a36Sopenharmony_ci	release_region(isa->io, region_size);
28662306a36Sopenharmony_ci	v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card);
28762306a36Sopenharmony_ci	kfree(isa);
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ciint radio_isa_probe(struct device *pdev, unsigned int dev)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	struct radio_isa_driver *drv = pdev->platform_data;
29362306a36Sopenharmony_ci	const struct radio_isa_ops *ops = drv->ops;
29462306a36Sopenharmony_ci	struct v4l2_device *v4l2_dev;
29562306a36Sopenharmony_ci	struct radio_isa_card *isa;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	isa = radio_isa_alloc(drv, pdev);
29862306a36Sopenharmony_ci	if (!isa)
29962306a36Sopenharmony_ci		return -ENOMEM;
30062306a36Sopenharmony_ci	isa->io = drv->io_params[dev];
30162306a36Sopenharmony_ci	v4l2_dev = &isa->v4l2_dev;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (drv->probe && ops->probe) {
30462306a36Sopenharmony_ci		int i;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci		for (i = 0; i < drv->num_of_io_ports; ++i) {
30762306a36Sopenharmony_ci			int io = drv->io_ports[i];
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci			if (request_region(io, drv->region_size, v4l2_dev->name)) {
31062306a36Sopenharmony_ci				bool found = ops->probe(isa, io);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci				release_region(io, drv->region_size);
31362306a36Sopenharmony_ci				if (found) {
31462306a36Sopenharmony_ci					isa->io = io;
31562306a36Sopenharmony_ci					break;
31662306a36Sopenharmony_ci				}
31762306a36Sopenharmony_ci			}
31862306a36Sopenharmony_ci		}
31962306a36Sopenharmony_ci	}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if (!radio_isa_valid_io(drv, isa->io)) {
32262306a36Sopenharmony_ci		int i;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci		if (isa->io < 0)
32562306a36Sopenharmony_ci			return -ENODEV;
32662306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x",
32762306a36Sopenharmony_ci				drv->io_ports[0]);
32862306a36Sopenharmony_ci		for (i = 1; i < drv->num_of_io_ports; i++)
32962306a36Sopenharmony_ci			printk(KERN_CONT "/0x%03x", drv->io_ports[i]);
33062306a36Sopenharmony_ci		printk(KERN_CONT ".\n");
33162306a36Sopenharmony_ci		kfree(isa);
33262306a36Sopenharmony_ci		return -EINVAL;
33362306a36Sopenharmony_ci	}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	return radio_isa_common_probe(isa, pdev, drv->radio_nr_params[dev],
33662306a36Sopenharmony_ci					drv->region_size);
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_isa_probe);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_civoid radio_isa_remove(struct device *pdev, unsigned int dev)
34162306a36Sopenharmony_ci{
34262306a36Sopenharmony_ci	struct radio_isa_card *isa = dev_get_drvdata(pdev);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	radio_isa_common_remove(isa, isa->drv->region_size);
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_isa_remove);
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci#ifdef CONFIG_PNP
34962306a36Sopenharmony_ciint radio_isa_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	struct pnp_driver *pnp_drv = to_pnp_driver(dev->dev.driver);
35262306a36Sopenharmony_ci	struct radio_isa_driver *drv = container_of(pnp_drv,
35362306a36Sopenharmony_ci					struct radio_isa_driver, pnp_driver);
35462306a36Sopenharmony_ci	struct radio_isa_card *isa;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	if (!pnp_port_valid(dev, 0))
35762306a36Sopenharmony_ci		return -ENODEV;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	isa = radio_isa_alloc(drv, &dev->dev);
36062306a36Sopenharmony_ci	if (!isa)
36162306a36Sopenharmony_ci		return -ENOMEM;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	isa->io = pnp_port_start(dev, 0);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return radio_isa_common_probe(isa, &dev->dev, drv->radio_nr_params[0],
36662306a36Sopenharmony_ci					pnp_port_len(dev, 0));
36762306a36Sopenharmony_ci}
36862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_isa_pnp_probe);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_civoid radio_isa_pnp_remove(struct pnp_dev *dev)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	struct radio_isa_card *isa = dev_get_drvdata(&dev->dev);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	radio_isa_common_remove(isa, pnp_port_len(dev, 0));
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_isa_pnp_remove);
37762306a36Sopenharmony_ci#endif
378