Linux RTC device driver

Keywords: Linux

hardware platform

  • RiotBoard imx6sol cortex A9
  • linux 3.10
  • RTC chip MC13XXX i2c interface

Drive loading process

<1> dts
Mc13xxx is an RTC chip mounted under I2C. It has an example of I2C and mc13xxx driver code in the way of SOC's own RTC peripherals. The following contents about RTC core are consistent.

a) Firstly, the driver of mc13xxx is implemented under i2c.
compatible as follows

static const struct of_device_id mc13xxx_dt_ids[] = {
	{
		.compatible = "fsl,mc13892",
		.data = &mc13xxx_variant_mc13892,
	}, {
		.compatible = "fsl,mc34708",
		.data = &mc13xxx_variant_mc34708,
	}, {
		/* sentinel */
	}
};

b) dts information is as follows

&i2c2 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2_1>;
	status = "okay";

	pmic: mc13892@08 {
		compatible = "fsl,mc13892", "fsl,mc13xxx";
		reg = <0x08>;
	};

	codec: sgtl5000@0a {
		compatible = "fsl,sgtl5000";
		reg = <0x0a>;
	};
};

c) Underlying implementation
mc13xxx_i2c.c mainly realizes the matching of device and driver, so that mc13xxx device can obtain the resources of the operation itself, that is, i2c driver.

With the help of I2C driver, the basic RTC operations are realized in mc13xxx core.c, mainly the operations of reading and writing registers and interrupt access.

int mc13xxx_reg_read(struct mc13xxx *mc13xxx, unsigned int offset, u32 *val)
int mc13xxx_reg_write(struct mc13xxx *mc13xxx, unsigned int offset, u32 val)

These operations will be used by rtc-mc13xxx to instantiate an RTC device and implement RTC related operations in it. (what layers of dolls.)

Because rtc-mc3xxx is a virtual RTC device, its essence is mc13xxx_ The collusion between I2C and mc13xxx.
Therefore, it can not match the device and driver like mc13xxx_ In I2C, the association is performed through dts files. Here, the following functions are used:

static const struct platform_device_id mc13xxx_rtc_idtable[] = {
	{
		.name = "mc13783-rtc",
	}, {
		.name = "mc13892-rtc",
	}, {
		.name = "mc34708-rtc",
	},
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, mc13xxx_rtc_idtable);

static struct platform_driver mc13xxx_rtc_driver = {
	.id_table = mc13xxx_rtc_idtable,
	.remove = __exit_p(mc13xxx_rtc_remove),
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	},
};

module_platform_driver_probe(mc13xxx_rtc_driver, &mc13xxx_rtc_probe);

Match the RTC equipment with the relevant driver.
The following operations of rtc device and driver are realized:

static const struct rtc_class_ops mc13xxx_rtc_ops = {
	.read_time = mc13xxx_rtc_read_time,
	.set_mmss = mc13xxx_rtc_set_mmss,
	.read_alarm = mc13xxx_rtc_read_alarm,
	.set_alarm = mc13xxx_rtc_set_alarm,
	.alarm_irq_enable = mc13xxx_rtc_alarm_irq_enable,
};

Mc13xxx above_ rtc_ read_ Time is realized by calling the functions in mc13xxx core.c to read and write the addresses of different registers. At this time, the device driver at the bottom of RTC has been completed, and the device file will be added next.

d) Add device file
In the underlying operation above, static int__ init mc13xxx_ rtc_ Probe (struct platform_device * PDEV) is used to match devices and drivers. After matching, it will be executed. This is mainly about the space used by the equipment and some resources of the equipment.

static int __init mc13xxx_rtc_probe(struct platform_device *pdev)
{
	int ret;
	struct mc13xxx_rtc *priv;
	struct mc13xxx *mc13xxx;
	int rtcrst_pending;

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);  //Request memory for device
	if (!priv)
		return -ENOMEM;

	mc13xxx = dev_get_drvdata(pdev->dev.parent); 
	priv->mc13xxx = mc13xxx;

	platform_set_drvdata(pdev, priv);

	mc13xxx_lock(mc13xxx);

	ret = mc13xxx_irq_request(mc13xxx, MC13XXX_IRQ_RTCRST,
			mc13xxx_rtc_reset_handler, DRIVER_NAME, priv);
	if (ret)
		goto err_reset_irq_request;

	ret = mc13xxx_irq_status(mc13xxx, MC13XXX_IRQ_RTCRST,
			NULL, &rtcrst_pending);
	if (ret)
		goto err_reset_irq_status;

	priv->valid = !rtcrst_pending;

	ret = mc13xxx_irq_request_nounmask(mc13xxx, MC13XXX_IRQ_1HZ,
			mc13xxx_rtc_update_handler, DRIVER_NAME, priv);
	if (ret)
		goto err_update_irq_request;

	ret = mc13xxx_irq_request_nounmask(mc13xxx, MC13XXX_IRQ_TODA,
			mc13xxx_rtc_alarm_handler, DRIVER_NAME, priv);
	if (ret)
		goto err_alarm_irq_request;

	mc13xxx_unlock(mc13xxx);

	priv->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
					&mc13xxx_rtc_ops, THIS_MODULE);  //Key places, register RTC equipment files, track all the way and go down
	if (IS_ERR(priv->rtc)) {
		ret = PTR_ERR(priv->rtc);

		mc13xxx_lock(mc13xxx);

		mc13xxx_irq_free(mc13xxx, MC13XXX_IRQ_TODA, priv);
err_alarm_irq_request:

		mc13xxx_irq_free(mc13xxx, MC13XXX_IRQ_1HZ, priv);
err_update_irq_request:

err_reset_irq_status:

		mc13xxx_irq_free(mc13xxx, MC13XXX_IRQ_RTCRST, priv);
err_reset_irq_request:

		mc13xxx_unlock(mc13xxx);

		platform_set_drvdata(pdev, NULL);
	}

	return ret;
}

rtc device registration function in class.c file

struct rtc_device *rtc_device_register(const char *name, struct device *dev,
					const struct rtc_class_ops *ops,
					struct module *owner)
{
	struct rtc_device *rtc;
	struct rtc_wkalrm alrm;
	int id, err;

	id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL);
	if (id < 0) {
		err = id;
		goto exit;
	}

	rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
	if (rtc == NULL) {
		err = -ENOMEM;
		goto exit_ida;
	}

	rtc->id = id;
	rtc->ops = ops;  //Basic operation of RTC underlying
	rtc->owner = owner;
	rtc->irq_freq = 1;
	rtc->max_user_freq = 64;
	rtc->dev.parent = dev;
	rtc->dev.class = rtc_class;
	rtc->dev.release = rtc_device_release;

	mutex_init(&rtc->ops_lock);
	spin_lock_init(&rtc->irq_lock);
	spin_lock_init(&rtc->irq_task_lock);
	init_waitqueue_head(&rtc->irq_queue);

	/* Init timerqueue */
	timerqueue_init_head(&rtc->timerqueue);
	INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
	/* Init aie timer */
	rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);
	/* Init uie timer */
	rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
	/* Init pie timer */
	hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	rtc->pie_timer.function = rtc_pie_update_irq;
	rtc->pie_enabled = 0;

	/* Check to see if there is an ALARM already set in hw */
	err = __rtc_read_alarm(rtc, &alrm);

	if (!err && !rtc_valid_tm(&alrm.time))
		rtc_initialize_alarm(rtc, &alrm);

	strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
	dev_set_name(&rtc->dev, "rtc%d", id);  //Add name

	rtc_dev_prepare(rtc);  //The key function pre'parse is parsed in advance 

	err = device_register(&rtc->dev); //Device registration
	if (err) {
		put_device(&rtc->dev);
		goto exit_kfree;
	}

	rtc_dev_add_device(rtc);  //Add RTC device
	rtc_sysfs_add_device(rtc);//Add rtc to sys
	rtc_proc_add_device(rtc);//Add rtc to proc

	dev_info(dev, "rtc core: registered %s as %s\n",
			rtc->name, dev_name(&rtc->dev));

	return rtc;

exit_kfree:
	kfree(rtc);

exit_ida:
	ida_simple_remove(&rtc_ida, id);

exit:
	dev_err(dev, "rtc core: unable to register %s, err = %d\n",
			name, err);
	return ERR_PTR(err);
}

There are two functions in RTC_ Implemented in dev.c.

void rtc_dev_prepare(struct rtc_device *rtc)
{
	if (!rtc_devt)
		return;

	if (rtc->id >= RTC_DEV_MAX) {
		dev_dbg(&rtc->dev, "%s: too many RTC devices\n", rtc->name);
		return;
	}

	rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
	INIT_WORK(&rtc->uie_task, rtc_uie_task);
	setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc);
#endif

	cdev_init(&rtc->char_dev, &rtc_dev_fops);  //Initialize the character device and pass in FOPS, which contains fops and RTC_ The functions of ops in mcxxx are the same.
	rtc->char_dev.owner = rtc->owner;
}

void rtc_dev_add_device(struct rtc_device *rtc)
{
	if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))//Add character device
		dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
			rtc->name, MAJOR(rtc_devt), rtc->id);
	else
		dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,
			MAJOR(rtc_devt), rtc->id);
}

rtc_mcxxx.c and RTC_ Many functions of ops and fops in dev.c are repeated, RTC_ The RTC function called in dev is through the interface in another file interface.c, and the interface in interface is through calling RTC_ The ops in mc13xxx is implemented. So essentially, RTC_ fops and RTC in dev_ Mc13xxx is the same thing. rtc_ Inside mcxxx. C is rtc_dev interface, RTC_ Inside dev is char_dev interface. You can call RTC after you have the resources of rtcdev_ ops operates RTC, but in / dev, RTC is used as a character device, and a char is required_ The fops of dev is used to operate the read / write RTC device. Both of them finally call mc13xxx_ The content in core. C is just different from the object-oriented. rtc_ F in dev.c_ ops is prepared for the file under the character device / dev, rtc_mc3xxx.c is for rtc_dev prepared it.

rtc_dev.c correlation function

void rtc_dev_add_device(struct rtc_device *rtc)
{
	if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))//Add a device file under dev
		dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
			rtc->name, MAJOR(rtc_devt), rtc->id);
	else
		dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,
			MAJOR(rtc_devt), rtc->id);
}
There is an operation to apply for the equipment number in advance in this file init Function and the one below sys dependent init The function will be in class.c Called in the file.
void __init rtc_dev_init(void)
{
	int err;

	err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
	if (err < 0)
		pr_err("failed to allocate char dev region\n");
}

class.c correlation function
static int __init rtc_init(void)
{
	rtc_class = class_create(THIS_MODULE, "rtc");
	if (IS_ERR(rtc_class)) {
		pr_err("couldn't create class\n");
		return PTR_ERR(rtc_class);
	}
	rtc_class->suspend = rtc_suspend;
	rtc_class->resume = rtc_resume;
	rtc_dev_init();
	rtc_sysfs_init(rtc_class);
	return 0;
}
subsys_initcall(rtc_init);//rtc class will be initialized in advance
rtc_sysfs.c Inner correlation function
void __init rtc_sysfs_init(struct class *rtc_class)
{
	rtc_class->dev_attrs = rtc_attrs;//What are some attributes of comfort
}

At this point, the rtc device under / dev is connected with the underlying driver.
Here are some functions of sys and proc in rtc_sysyfs.c h and RTC_ Implemented in proc. C.
sysfs implements the corresponding functions by calling some functions in interface.c.

void rtc_sysfs_add_device(struct rtc_device *rtc)//rtc_device_register calls here to create the device in sys and the RTC at the time of registration_ Dev.
{
	int err;

	/* not all RTCs support both alarms and wakeup */
	if (!rtc_does_wakealarm(rtc))
		return;

	err = device_create_file(&rtc->dev, &dev_attr_wakealarm);
	if (err)
		dev_err(rtc->dev.parent,
			"failed to create alarm attribute, %d\n", err);
}
static ssize_t
rtc_sysfs_show_time(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	ssize_t retval;
	struct rtc_time tm;

	retval = rtc_read_time(to_rtc_device(dev), &tm);//The interface in interface.c.
	if (retval == 0) {
		retval = sprintf(buf, "%02d:%02d:%02d\n",
			tm.tm_hour, tm.tm_min, tm.tm_sec);
	}

	return retval;
}

static struct device_attribute rtc_attrs[] = {
	__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),//Define some attributes for easy use in sys
	__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
	__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
	__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
	__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
			rtc_sysfs_set_max_user_freq),
	__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
	{ },
};

The operations in proc are similar to those in sysy.

summary

linux device drivers are ever-changing and inseparable from devices / Drivers / files. Today, I just analyzed the RTC driver of the board I have in hand. The kernel is broad and profound and needs to be studied in detail; The last picture summarizes what we learned at the weekend.

Posted by Adthegreat on Sat, 30 Oct 2021 23:58:50 -0700