Linux第106步_Linux内核RTC驱动实验

1、了解rtc_device结构体

1)、打开“include/linux/rtc.h”

rtc_class_ops是需要用户根据所使用的RTC设备编写的,其结构体如下:

struct rtc_class_ops {

int (*ioctl)(struct device *, unsigned int, unsigned long);/*函数指针ioctl*/

int (*read_time)(struct device *, struct rtc_time *);/*函数指针read_time*/

int (*set_time)(struct device *, struct rtc_time *);/*函数指针set_time*/

int (*read_alarm)(struct device *, struct rtc_wkalrm *);

/*函数指针read_alarm*/

int (*set_alarm)(struct device *, struct rtc_wkalrm *);

/*函数指针set_alarm*/

int (*proc)(struct device *, struct seq_file *);/*函数指针proc*/

int (*alarm_irq_enable)(struct device *, unsigned int enabled);

    /*函数指针alarm_irq_enable*/

int (*read_offset)(struct device *, long *offset);/*函数指针read_offset*/

int (*set_offset)(struct device *, long offset);/*函数指针set_offset*/

};

struct rtc_device {

struct device dev; /*设备*/

struct module *owner;

int id; /*设备ID号*/

const struct rtc_class_ops *ops;/*RTC设备最底层操作函数*/

struct mutex ops_lock;

struct cdev char_dev;/*字符设备*/

unsigned long flags;

unsigned long irq_data;

spinlock_t irq_lock;

wait_queue_head_t irq_queue;

struct fasync_struct *async_queue;

int irq_freq;

int max_user_freq;

struct timerqueue_head timerqueue;

struct rtc_timer aie_timer;

struct rtc_timer uie_rtctimer;

struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */

int pie_enabled;

struct work_struct irqwork;

/* Some hardware can't support UIE mode */

int uie_unsupported;

/* Number of nsec it takes to set the RTC clock. This influences when

 * the set ops are called. An offset:

 *   - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s

 *   - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s

 *   - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s

 */

long set_offset_nsec;

bool registered;

/* Old ABI support */

bool nvram_old_abi;

struct bin_attribute *nvram;

time64_t range_min;

timeu64_t range_max;

time64_t start_secs;

time64_t offset_secs;

bool set_start_time;

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL

struct work_struct uie_task;

struct timer_list uie_timer;

/* Those fields are protected by rtc->irq_lock */

unsigned int oldsecs;

unsigned int uie_irq_active:1;

unsigned int stop_uie_polling:1;

unsigned int uie_task_active:1;

unsigned int uie_timer_active:1;

#endif

};

2)、打开“drivers/rtc/class.c”

/*注册RTC类设备

 * devm_rtc_device_register - resource managed rtc_device_register()

 * @dev: 要注册的设备,the device to register

 * @name: 设备名字,the name of the device (unused)

 * @ops: 底层驱动函数集,the rtc operations structure

 * @owner: 驱动模块拥有者,the module owner

 *返回值:注册成功的话就返回rtcdevice,4错误的话会返回一个负值

 * @return a struct rtc on success, or an ERR_PTR on error

 *

 * Managed rtc_device_register(). The rtc_device returned from this function

 * are automatically freed on driver detach.

 * This function is deprecated, use devm_rtc_allocate_device and

 * rtc_register_device instead

 */

struct rtc_device *devm_rtc_device_register(struct device *dev,

    const char *name,

    const struct rtc_class_ops *ops,

    struct module *owner)

{

struct rtc_device *rtc;

int err;

rtc = devm_rtc_allocate_device(dev);

if (IS_ERR(rtc))

return rtc;

rtc->ops = ops;

err = __rtc_register_device(owner, rtc);

if (err)

return ERR_PTR(err);

return rtc;

}

struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner);//rtc_device_register()和devm_rtc_device_register()功能相同;

/**

 *卸载RTC驱动时,删除先前注册的RTC类设备

 * rtc_device_unregister - removes the previously registered RTC class device

 *

 * @rtc: the RTC class device to destroy

 */

static void rtc_device_unregister(struct rtc_device *rtc)

{

mutex_lock(&rtc->ops_lock);

/*

 * Remove innards of this RTC, then disable it, before

 * letting any rtc_class_open() users access it again

 */

rtc_proc_del_device(rtc);

cdev_device_del(&rtc->char_dev, &rtc->dev);

rtc->ops = NULL;

mutex_unlock(&rtc->ops_lock);

put_device(&rtc->dev);

}

int __rtc_register_device(struct module *owner, struct rtc_device *rtc)

{

struct rtc_wkalrm alrm;

int err;

if (!rtc->ops) {

dev_dbg(&rtc->dev, "no ops set\n");

return -EINVAL;

}

rtc->owner = owner;

rtc_device_get_offset(rtc);

/* 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);

rtc_dev_prepare(rtc);

err = cdev_device_add(&rtc->char_dev, &rtc->dev);

if (err)

dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n",

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

else

dev_dbg(rtc->dev.parent, "char device (%d:%d)\n",

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

rtc_proc_add_device(rtc);

rtc->registered = true;

dev_info(rtc->dev.parent, "registered as %s\n",

 dev_name(&rtc->dev));

return 0;

}

/**卸载RTC驱动时,删除先前注册的RTC类设备;

 * rtc_device_unregister - removes the previously registered RTC class device

 *

 * @rtc: the RTC class device to destroy

 */

static void rtc_device_unregister(struct rtc_device *rtc)

{

mutex_lock(&rtc->ops_lock);/* 上锁 */

/*

 * Remove innards of this RTC, then disable it, before

 * letting any rtc_class_open() users access it again

 */

rtc_proc_del_device(rtc);

cdev_device_del(&rtc->char_dev, &rtc->dev);

rtc->ops = NULL;

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

put_device(&rtc->dev);

}

devm_rtc_device_unregister()和rtc_device_unregister(struct rtc_device *rtc)功能相同;

3)、打开“include/linux/rtc.h”

/*

 * For these RTC methods the device parameter is the physical device

 * on whatever bus holds the hardware (I2C, Platform, SPI, etc), which

 * was passed to rtc_device_register().  Its driver_data normally holds

 * device state, including the rtc_device pointer for the RTC.

 *

 * Most of these methods are called with rtc_device.ops_lock held,

 * through the rtc_*(struct rtc_device *, ...) calls.

 *

 * The (current) exceptions are mostly filesystem hooks:

 *   - the proc() hook for procfs

 *   - non-ioctl() chardev hooks:  open(), release()

 *

 * REVISIT those periodic irq calls *do* have ops_lock when they're

 * issued through ioctl() ...

 rtc_class_ops是需要用户根据所使用的RTC设备编写的,其结构体如下:

 */

struct rtc_class_ops {

int (*ioctl)(struct device *, unsigned int, unsigned long);/*函数指针ioctl*/

int (*read_time)(struct device *, struct rtc_time *);/*函数指针read_time*/

int (*set_time)(struct device *, struct rtc_time *);/*函数指针set_time*/

int (*read_alarm)(struct device *, struct rtc_wkalrm *);/*函数指针read_alarm*/

int (*set_alarm)(struct device *, struct rtc_wkalrm *);/*函数指针set_alarm*/

int (*proc)(struct device *, struct seq_file *);/*函数指针proc*/

int (*alarm_irq_enable)(struct device *, unsigned int enabled);/*函数指针alarm_irq_enable*/

int (*read_offset)(struct device *, long *offset);/*函数指针read_offset*/

int (*set_offset)(struct device *, long offset);/*函数指针set_offset*/

};

打开“/usr/include/linux/rtc.h”

struct rtc_time {

int tm_sec;

int tm_min;

int tm_hour;

int tm_mday;

int tm_mon;

int tm_year;

int tm_wday;

int tm_yday;

int tm_isdst;

};

4)、打开“drivers/rtc/dev.c

/* 设备操作函数结构体 */

/*声明file_operations结构变量rtc_dev_fops它是指向设备的操作函数集合变量*/

static const struct file_operations rtc_dev_fops = {

.owner = THIS_MODULE,

/*表示该文件的操作结构体所属的模块是当前的模块,即这个模块属于内核*/

.llseek = no_llseek,

.read = rtc_dev_read,

.poll = rtc_dev_poll,

.unlocked_ioctl = rtc_dev_ioctl,

.open = rtc_dev_open,

.release = rtc_dev_release,

.fasync = rtc_dev_fasync,

};

static long rtc_dev_ioctl(struct file *file,

  unsigned int cmd, unsigned long arg)

{

int err = 0;

struct rtc_device *rtc = file->private_data;

const struct rtc_class_ops *ops = rtc->ops;

struct rtc_time tm;

struct rtc_wkalrm alarm;

void __user *uarg = (void __user *)arg;

err = mutex_lock_interruptible(&rtc->ops_lock);

if (err)

return err;

/* check that the calling task has appropriate permissions

 * for certain ioctls. doing this check here is useful

 * to avoid duplicate code in each driver.

 */

switch (cmd) {

case RTC_EPOCH_SET:

case RTC_SET_TIME:

if (!capable(CAP_SYS_TIME))

err = -EACCES;

break;

case RTC_IRQP_SET:

if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))

err = -EACCES;

break;

case RTC_PIE_ON:

if (rtc->irq_freq > rtc->max_user_freq &&

    !capable(CAP_SYS_RESOURCE))

err = -EACCES;

break;

}

if (err)

goto done;

/*

 * Drivers *SHOULD NOT* provide ioctl implementations

 * for these requests.  Instead, provide methods to

 * support the following code, so that the RTC's main

 * features are accessible without using ioctls.

 *

 * RTC and alarm times will be in UTC, by preference,

 * but dual-booting with MS-Windows implies RTCs must

 * use the local wall clock time.

 */

switch (cmd) {

case RTC_ALM_READ:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

err = rtc_read_alarm(rtc, &alarm);

if (err < 0)

return err;

if (copy_to_user(uarg, &alarm.time, sizeof(tm)))

err = -EFAULT;

return err;

case RTC_ALM_SET:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

if (copy_from_user(&alarm.time, uarg, sizeof(tm)))

return -EFAULT;

alarm.enabled = 0;

alarm.pending = 0;

alarm.time.tm_wday = -1;

alarm.time.tm_yday = -1;

alarm.time.tm_isdst = -1;

/* RTC_ALM_SET alarms may be up to 24 hours in the future.

 * Rather than expecting every RTC to implement "don't care"

 * for day/month/year fields, just force the alarm to have

 * the right values for those fields.

 *

 * RTC_WKALM_SET should be used instead.  Not only does it

 * eliminate the need for a separate RTC_AIE_ON call, it

 * doesn't have the "alarm 23:59:59 in the future" race.

 *

 * NOTE:  some legacy code may have used invalid fields as

 * wildcards, exposing hardware "periodic alarm" capabilities.

 * Not supported here.

 */

{

time64_t now, then;

err = rtc_read_time(rtc, &tm);

if (err < 0)

return err;

now = rtc_tm_to_time64(&tm);

alarm.time.tm_mday = tm.tm_mday;

alarm.time.tm_mon = tm.tm_mon;

alarm.time.tm_year = tm.tm_year;

err  = rtc_valid_tm(&alarm.time);

if (err < 0)

return err;

then = rtc_tm_to_time64(&alarm.time);

/* alarm may need to wrap into tomorrow */

if (then < now) {

rtc_time64_to_tm(now + 24 * 60 * 60, &tm);

alarm.time.tm_mday = tm.tm_mday;

alarm.time.tm_mon = tm.tm_mon;

alarm.time.tm_year = tm.tm_year;

}

}

return rtc_set_alarm(rtc, &alarm);

case RTC_RD_TIME: /*读时间命令*/

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

err = rtc_read_time(rtc, &tm);

/*获取当前RTC时钟,rtc_read_time()会调用__rtc_read_time()*/

if (err < 0)

return err;

if (copy_to_user(uarg, &tm, sizeof(tm)))

err = -EFAULT;

return err;

case RTC_SET_TIME: /*设置时间命令*/

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

if (copy_from_user(&tm, uarg, sizeof(tm)))

return -EFAULT;

return rtc_set_time(rtc, &tm);/*设置时间*/

case RTC_PIE_ON:

err = rtc_irq_set_state(rtc, 1);

break;

case RTC_PIE_OFF:

err = rtc_irq_set_state(rtc, 0);

break;

case RTC_AIE_ON:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

return rtc_alarm_irq_enable(rtc, 1);

case RTC_AIE_OFF:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

return rtc_alarm_irq_enable(rtc, 0);

case RTC_UIE_ON:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

return rtc_update_irq_enable(rtc, 1);

case RTC_UIE_OFF:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

return rtc_update_irq_enable(rtc, 0);

case RTC_IRQP_SET:

err = rtc_irq_set_freq(rtc, arg);

break;

case RTC_IRQP_READ:

err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);

break;

case RTC_WKALM_SET:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

if (copy_from_user(&alarm, uarg, sizeof(alarm)))

return -EFAULT;

return rtc_set_alarm(rtc, &alarm);

case RTC_WKALM_RD:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

err = rtc_read_alarm(rtc, &alarm);

if (err < 0)

return err;

if (copy_to_user(uarg, &alarm, sizeof(alarm)))

err = -EFAULT;

return err;

default:

/* Finally try the driver's ioctl interface */

if (ops->ioctl) {

err = ops->ioctl(rtc->dev.parent, cmd, arg);

if (err == -ENOIOCTLCMD)

err = -ENOTTY;

} else {

err = -ENOTTY;

}

break;

}

done:

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

return err;

}

5)、打开“/linux-5.4.31/drivers/rtc/interface.c”

static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)

{

int err;

if (!rtc->ops) {

err = -ENODEV;

} else if (!rtc->ops->read_time) {

err = -EINVAL;

} else {

memset(tm, 0, sizeof(struct rtc_time));

err = rtc->ops->read_time(rtc->dev.parent, tm);

if (err < 0) {

dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",

err);

return err;

}

rtc_add_offset(rtc, tm);

err = rtc_valid_tm(tm);

if (err < 0)

dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");

}

return err;

}

int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)

{

int err;

err = mutex_lock_interruptible(&rtc->ops_lock);

if (err)

return err;

err = __rtc_read_time(rtc, tm);

mutex_unlock(&rtc->ops_lock);/* 解锁,释放互斥锁 */

trace_rtc_read_time(rtc_tm_to_time64(tm), err);

return err;

}

6)、打开“/linux-5.4.31/drivers/rtc/rtc-stm32.c”

static const struct of_device_id stm32_rtc_of_match[] = {

{ .compatible = "st,stm32-rtc", .data = &stm32_rtc_data },

{ .compatible = "st,stm32h7-rtc", .data = &stm32h7_rtc_data },

{ .compatible = "st,stm32mp1-rtc", .data = &stm32mp1_data },

/*"st,stm32mp1-rtc"在设备树中*/

{ /*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/

      /* Sentinel */ 

}

};

/*标准的platform驱动框架*/

static struct platform_driver stm32_rtc_driver = {

.probe = stm32_rtc_probe,

.remove = stm32_rtc_remove,

.driver = {

.name = DRIVER_NAME,

.pm = &stm32_rtc_pm_ops,

.of_match_table = stm32_rtc_of_match,

},

};

static const struct rtc_class_ops stm32_rtc_ops = {

.read_time = stm32_rtc_read_time,

.set_time = stm32_rtc_set_time,

.read_alarm = stm32_rtc_read_alarm,

.set_alarm = stm32_rtc_set_alarm,

.alarm_irq_enable = stm32_rtc_alarm_irq_enable,

};

2、修改设备树

1)、打开“stm32mp151.dtsi”找到“rtc”,内容如下:

rtc: rtc@5c004000 {

compatible = "st,stm32mp1-rtc";

reg = <0x5c004000 0x400>;

clocks = <&scmi0_clk CK_SCMI0_RTCAPB>,

<&scmi0_clk CK_SCMI0_RTC>;

clock-names = "pclk", "rtc_ck";

interrupts-extended = <&exti 19 IRQ_TYPE_LEVEL_HIGH>;

status = "disabled";

};

2)、打开“stm32mp157d-atk.dts”,添加内容如下(注意:不是在根节点“/”下添加):

&rtc {

status = "okay";

};

3、“drivers/rtc/rtc-stm32.c”程序如下:

#include <linux/bcd.h>

#include <linux/clk.h>

#include <linux/clk-provider.h>

#include <linux/errno.h>

#include <linux/iopoll.h>

#include <linux/ioport.h>

#include <linux/mfd/syscon.h>

#include <linux/module.h>//使能stm32_rtc_init()

#include <linux/of_device.h>

#include <linux/pm_wakeirq.h>

#include <linux/regmap.h>

#include <linux/rtc.h>

#include <dt-bindings/rtc/rtc-stm32.h>

#define DRIVER_NAME "stm32_rtc"

/* STM32_RTC_TR bit fields  */

#define STM32_RTC_TR_SEC_SHIFT 0

#define STM32_RTC_TR_SEC GENMASK(6, 0)

#define STM32_RTC_TR_MIN_SHIFT 8

#define STM32_RTC_TR_MIN GENMASK(14, 8)

#define STM32_RTC_TR_HOUR_SHIFT 16

#define STM32_RTC_TR_HOUR GENMASK(21, 16)

/* STM32_RTC_DR bit fields */

#define STM32_RTC_DR_DATE_SHIFT 0

#define STM32_RTC_DR_DATE GENMASK(5, 0)

#define STM32_RTC_DR_MONTH_SHIFT 8

#define STM32_RTC_DR_MONTH GENMASK(12, 8)

#define STM32_RTC_DR_WDAY_SHIFT 13

#define STM32_RTC_DR_WDAY GENMASK(15, 13)

#define STM32_RTC_DR_YEAR_SHIFT 16

#define STM32_RTC_DR_YEAR GENMASK(23, 16)

/* STM32_RTC_CR bit fields */

#define STM32_RTC_CR_FMT BIT(6)

#define STM32_RTC_CR_ALRAE BIT(8)

#define STM32_RTC_CR_ALRAIE BIT(12)

#define STM32_RTC_CR_COSEL BIT(19)

#define STM32_RTC_CR_OSEL_SHIFT 21

#define STM32_RTC_CR_OSEL GENMASK(22, 21)

#define STM32_RTC_CR_COE BIT(23)

#define STM32_RTC_CR_TAMPOE BIT(26)

#define STM32_RTC_CR_OUT2EN BIT(31)

/* STM32_RTC_ISR/STM32_RTC_ICSR bit fields */

#define STM32_RTC_ISR_ALRAWF BIT(0)

#define STM32_RTC_ISR_INITS BIT(4)

#define STM32_RTC_ISR_RSF BIT(5)

#define STM32_RTC_ISR_INITF BIT(6)

#define STM32_RTC_ISR_INIT BIT(7)

#define STM32_RTC_ISR_ALRAF BIT(8)

/* STM32_RTC_PRER bit fields */

#define STM32_RTC_PRER_PRED_S_SHIFT 0

#define STM32_RTC_PRER_PRED_S GENMASK(14, 0)

#define STM32_RTC_PRER_PRED_A_SHIFT 16

#define STM32_RTC_PRER_PRED_A GENMASK(22, 16)

/* STM32_RTC_ALRMAR and STM32_RTC_ALRMBR bit fields */

#define STM32_RTC_ALRMXR_SEC_SHIFT 0

#define STM32_RTC_ALRMXR_SEC GENMASK(6, 0)

#define STM32_RTC_ALRMXR_SEC_MASK BIT(7)

#define STM32_RTC_ALRMXR_MIN_SHIFT 8

#define STM32_RTC_ALRMXR_MIN GENMASK(14, 8)

#define STM32_RTC_ALRMXR_MIN_MASK BIT(15)

#define STM32_RTC_ALRMXR_HOUR_SHIFT 16

#define STM32_RTC_ALRMXR_HOUR GENMASK(21, 16)

#define STM32_RTC_ALRMXR_PM BIT(22)

#define STM32_RTC_ALRMXR_HOUR_MASK BIT(23)

#define STM32_RTC_ALRMXR_DATE_SHIFT 24

#define STM32_RTC_ALRMXR_DATE GENMASK(29, 24)

#define STM32_RTC_ALRMXR_WDSEL BIT(30)

#define STM32_RTC_ALRMXR_WDAY_SHIFT 24

#define STM32_RTC_ALRMXR_WDAY GENMASK(27, 24)

#define STM32_RTC_ALRMXR_DATE_MASK BIT(31)

/* STM32_RTC_SR/_SCR bit fields */

#define STM32_RTC_SR_ALRA BIT(0)

/* STM32_RTC_CFGR bit fields */

#define STM32_RTC_CFGR_OUT2_RMP BIT(0)

#define STM32_RTC_CFGR_LSCOEN_OUT1 1

#define STM32_RTC_CFGR_LSCOEN_OUT2_RMP 2

/* STM32_RTC_VERR bit fields */

#define STM32_RTC_VERR_MINREV_SHIFT 0

#define STM32_RTC_VERR_MINREV GENMASK(3, 0)

#define STM32_RTC_VERR_MAJREV_SHIFT 4

#define STM32_RTC_VERR_MAJREV GENMASK(7, 4)

/* STM32_RTC_WPR key constants */

#define RTC_WPR_1ST_KEY 0xCA

#define RTC_WPR_2ND_KEY 0x53

#define RTC_WPR_WRONG_KEY 0xFF

/* Max STM32 RTC register offset is 0x3FC */

#define UNDEF_REG 0xFFFF

struct stm32_rtc;//声明stm32_rtc结构类型

struct stm32_rtc_registers {

u16 tr;

u16 dr;

u16 cr;

u16 isr;/*RTC_ICSR寄存器*/

u16 prer;

u16 alrmar;

u16 wpr;

u16 sr;

u16 scr;

u16 cfgr;

u16 verr;

};

struct stm32_rtc_events {

u32 alra;

};

struct stm32_rtc_data {

const struct stm32_rtc_registers regs;

const struct stm32_rtc_events events;

void (*clear_events)(struct stm32_rtc *rtc, unsigned int flags);

bool has_pclk;

bool need_dbp;

bool has_lsco;

};

struct stm32_rtc {

struct rtc_device *rtc_dev;

void __iomem *base;

struct regmap *dbp;

unsigned int dbp_reg;

unsigned int dbp_mask;

struct clk *pclk;

struct clk *rtc_ck;

const struct stm32_rtc_data *data;

int irq_alarm;

int lsco;

struct clk *clk_lsco;

};

/*

 *  -------------------------------------------------------------------------

 * | TAMPOE | OSEL[1:0] | COE | OUT2EN |     RTC_OUT1     |     RTC_OUT2     |

 * |     |           |     |        |                  | or RTC_OUT2_RMP  |

 * |-------------------------------------------------------------------------|

 * |    0   |     00    |  0  | 0 or 1 |         -        |         -        |

 * |--------|-----------|-----|--------|------------------|------------------|

 * |    0   |     00    |  1  |    0   |      CALIB       |         -        |

 * |--------|-----------|-----|--------|------------------|------------------|

 * | 0 or 1 |    !=00   |  0  |    0   |     TAMPALRM     |         -        |

 * |--------|-----------|-----|--------|------------------|------------------|

 * |    0   |     00    |  1  |    1   |         -        |      CALIB       |

 * |--------|-----------|-----|--------|------------------|------------------|

 * | 0 or 1 |    !=00   |  0  |    1   |         -        |     TAMPALRM     |

 * |--------|-----------|-----|--------|------------------|------------------|

 * | 0 or 1 |    !=00   |  1  |    1   |     TAMPALRM     |      CALIB       |

 *  -------------------------------------------------------------------------

 */

static int stm32_rtc_clk_lsco_check_availability(struct stm32_rtc *rtc)

{

struct stm32_rtc_registers regs = rtc->data->regs;

unsigned int cr = readl_relaxed(rtc->base + regs.cr);

/*读取STM32MP1的RTC_CR寄存器的值*/

unsigned int cfgr = readl_relaxed(rtc->base + regs.cfgr);

/*读取STM32MP1的RTC_CFGR寄存器的值*/

unsigned int calib = STM32_RTC_CR_COE;

unsigned int tampalrm = STM32_RTC_CR_TAMPOE | STM32_RTC_CR_OSEL;

switch (rtc->lsco) {

case RTC_OUT1:

if ((!(cr & STM32_RTC_CR_OUT2EN) &&

     ((cr & calib) || cr & tampalrm)) ||

     ((cr & calib) && (cr & tampalrm)))

return -EBUSY;

break;

case RTC_OUT2_RMP:

if ((cr & STM32_RTC_CR_OUT2EN) &&

    (cfgr & STM32_RTC_CFGR_OUT2_RMP) &&

    ((cr & calib) || (cr & tampalrm)))

return -EBUSY;

break;

default:

return -EINVAL;

}

if (clk_get_rate(rtc->rtc_ck) != 32768)

/*获得rtc->rtc_ck时钟源的当前时钟频率(HZ)*/

return -ERANGE;

return 0;

}

static int stm32_rtc_clk_lsco_register(struct platform_device *pdev)

{

struct stm32_rtc *rtc = platform_get_drvdata(pdev);

struct stm32_rtc_registers regs = rtc->data->regs;

u8 lscoen;

int ret;

ret = stm32_rtc_clk_lsco_check_availability(rtc);

if (ret)

return ret;

lscoen = (rtc->lsco == RTC_OUT1) ? STM32_RTC_CFGR_LSCOEN_OUT1 :

   STM32_RTC_CFGR_LSCOEN_OUT2_RMP;

rtc->clk_lsco = clk_register_gate(&pdev->dev, "rtc_lsco",

  __clk_get_name(rtc->rtc_ck),

  CLK_IGNORE_UNUSED | CLK_IS_CRITICAL,

  rtc->base + regs.cfgr, lscoen,

  0, NULL);

if (IS_ERR(rtc->clk_lsco))

return PTR_ERR(rtc->clk_lsco);

of_clk_add_provider(pdev->dev.of_node,

    of_clk_src_simple_get, rtc->clk_lsco);

return 0;

}

/*“RTC寄存器“解锁,允许写入*/

static void stm32_rtc_wpr_unlock(struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

writel_relaxed(RTC_WPR_1ST_KEY, rtc->base + regs->wpr);

writel_relaxed(RTC_WPR_2ND_KEY, rtc->base + regs->wpr);

/*

解锁步骤:

1. Write 0xCA into the RTC_WPR register.

2. Write 0x53 into the RTC_WPR register.

*/

}

/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器;

Writing a wrong key reactivates the write protection.

*/

static void stm32_rtc_wpr_lock(struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

writel_relaxed(RTC_WPR_WRONG_KEY, rtc->base + regs->wpr);

/*将0xFF写入“RTC写保护寄存器RTC_WPR“*/

}

//函数功能:令RTC进入初始化模式

static int stm32_rtc_enter_init_mode(struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int isr = readl_relaxed(rtc->base + regs->isr);

/*读“RTC_ICSR寄存器“*/

if (!(isr & STM32_RTC_ISR_INITF)) {

isr |= STM32_RTC_ISR_INIT;

/*准备将“RTC_ICSR寄存器“的bit7置1*/

/*Initialization mode used to program time and date register

(RTC_TR and RTC_DR), and prescaler register (RTC_PRER).*/

writel_relaxed(isr, rtc->base + regs->isr);

/*将isr的值写入“RTC_ICSR寄存器“*/

/*

 * It takes around 2 rtc_ck clock cycles to enter in

 * initialization phase mode (and have INITF flag set). As

 * slowest rtc_ck frequency may be 32kHz and highest should be

 * 1MHz, we poll every 10 us with a timeout of 100ms.

 */

return readl_relaxed_poll_timeout_atomic(

rtc->base + regs->isr,

isr, (isr & STM32_RTC_ISR_INITF),

10, 100000);

/*每10us轮询一次“RTC_ICSR寄存器“的bit7是否置1;

直到从“Free running mode”进入“Initialization mode“

*/

}

return 0;

}

//函数功能:令RTC进入“自由运行模式“

static void stm32_rtc_exit_init_mode(struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int isr = readl_relaxed(rtc->base + regs->isr);

/*读“RTC_ICSR寄存器“*/

isr &= ~STM32_RTC_ISR_INIT;

/*准备将“RTC_ICSR寄存器“的bit7置0,

令RTC从“Initialization mode“进入“Free running mode”*/

writel_relaxed(isr, rtc->base + regs->isr);

/*将isr的值写入“RTC_ICSR寄存器“*/

}

//函数功能:令“日历影子寄存器“同步

static int stm32_rtc_wait_sync(struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int isr = readl_relaxed(rtc->base + regs->isr);

/*读“RTC_ICSR寄存器“*/

isr &= ~STM32_RTC_ISR_RSF;

/*准备将“RTC_ICSR寄存器“的bit5置0,准备令“日历影子寄存器“不同步;

“RTC_ICSR寄存器“的bit5=1,表示“Calendar shadow registers synchronized“

*/

writel_relaxed(isr, rtc->base + regs->isr);

/*将isr的值写入“RTC_ICSR寄存器“*/

/*

 * Wait for RSF to be set to ensure the calendar registers are

 * synchronised, it takes around 2 rtc_ck clock cycles

 */

return readl_relaxed_poll_timeout_atomic(rtc->base + regs->isr,

 isr,

 (isr & STM32_RTC_ISR_RSF),

 10, 100000);

/*每10us轮询一次“RTC_ICSR寄存器“的bit5是否置1;

直到“日历影子寄存器“同步

*/

}

/*将flags写入RTC_SCR寄存器(RTC status clear register)*/

static void stm32_rtc_clear_event_flags(struct stm32_rtc *rtc,

unsigned int flags)

{

rtc->data->clear_events(rtc, flags);

/*将flags写入RTC_SCR寄存器(RTC status clear register)*/

}

/*RTC的“Alarm A”中断服务函数*/

static irqreturn_t stm32_rtc_alarm_irq(int irq, void *dev_id)

{

struct stm32_rtc *rtc = (struct stm32_rtc *)dev_id;

const struct stm32_rtc_registers *regs = &rtc->data->regs;

const struct stm32_rtc_events *evts = &rtc->data->events;

unsigned int status, cr;

mutex_lock(&rtc->rtc_dev->ops_lock);/* 上锁 */

status = readl_relaxed(rtc->base + regs->sr);/*读“RTC_SR寄存器“*/

cr = readl_relaxed(rtc->base + regs->cr);/*读“RTC_CR寄存器“*/

if ((status & evts->alra) &&

    (cr & STM32_RTC_CR_ALRAIE)) {

/* Alarm A flag - Alarm interrupt */

dev_dbg(&rtc->rtc_dev->dev, "Alarm occurred\n");

/*将事件传递给内核,Pass event to the kernel */

rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);

/* Clear event flags, otherwise new events won't be received */

stm32_rtc_clear_event_flags(rtc, evts->alra);

/*将evts->alra写入RTC_SCR寄存器(RTC status clear register);

Clear alarm A flag;

*/

}

mutex_unlock(&rtc->rtc_dev->ops_lock);/* 解锁,释放互斥锁 */

return IRQ_HANDLED;

}

/* Convert rtc_time structure from bin to bcd format */

/*函数功能:将rtc_time结构转换为BCD格式*/

static void tm2bcd(struct rtc_time *tm)

{

tm->tm_sec = bin2bcd(tm->tm_sec);

tm->tm_min = bin2bcd(tm->tm_min);

tm->tm_hour = bin2bcd(tm->tm_hour);

tm->tm_mday = bin2bcd(tm->tm_mday);

tm->tm_mon = bin2bcd(tm->tm_mon + 1);

tm->tm_year = bin2bcd(tm->tm_year - 100);

/*

 * Number of days since Sunday

 * - on kernel side, 0=Sunday...6=Saturday

 * - on rtc side, 0=invalid,1=Monday...7=Sunday

 */

tm->tm_wday = (!tm->tm_wday) ? 7 : tm->tm_wday;

}

/* Convert rtc_time structure from bcd to bin format */

/*函数功能:将BCD格式转换为rtc_time结构*/

static void bcd2tm(struct rtc_time *tm)

{

tm->tm_sec = bcd2bin(tm->tm_sec);

tm->tm_min = bcd2bin(tm->tm_min);

tm->tm_hour = bcd2bin(tm->tm_hour);

tm->tm_mday = bcd2bin(tm->tm_mday);

tm->tm_mon = bcd2bin(tm->tm_mon) - 1;

tm->tm_year = bcd2bin(tm->tm_year) + 100;

/*

 * Number of days since Sunday

 * - on kernel side, 0=Sunday...6=Saturday

 * - on rtc side, 0=invalid,1=Monday...7=Sunday

 */

tm->tm_wday %= 7;

}

//函数功能:读取RTC时间和日期,保存到tm中

static int stm32_rtc_read_time(struct device *dev, struct rtc_time *tm)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int tr, dr;

/* Time and Date in BCD format */

tr = readl_relaxed(rtc->base + regs->tr);/*读取STM32MP1的RTC_TR时间寄存器的值*/

dr = readl_relaxed(rtc->base + regs->dr);/*读取STM32MP1的RTC_DR日期寄存器的值*/

tm->tm_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;

tm->tm_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;

tm->tm_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;

tm->tm_mday = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;

tm->tm_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;

tm->tm_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;

tm->tm_wday = (dr & STM32_RTC_DR_WDAY) >> STM32_RTC_DR_WDAY_SHIFT;

/* We don't report tm_yday and tm_isdst */

bcd2tm(tm);/*将BCD格式转换为rtc_time格式*/

return 0;

}

//函数功能:将tm中的时间和日期写入RTC_TR时间寄存器和RTC_DR日期寄存器

static int stm32_rtc_set_time(struct device *dev, struct rtc_time *tm)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int tr, dr;

int ret = 0;

tm2bcd(tm);/*将rtc_time结构转换为BCD格式*/

/* Time in BCD format */

tr = ((tm->tm_sec << STM32_RTC_TR_SEC_SHIFT) & STM32_RTC_TR_SEC) |

     ((tm->tm_min << STM32_RTC_TR_MIN_SHIFT) & STM32_RTC_TR_MIN) |

     ((tm->tm_hour << STM32_RTC_TR_HOUR_SHIFT) & STM32_RTC_TR_HOUR);

/* Date in BCD format */

dr = ((tm->tm_mday << STM32_RTC_DR_DATE_SHIFT) & STM32_RTC_DR_DATE) |

     ((tm->tm_mon << STM32_RTC_DR_MONTH_SHIFT) & STM32_RTC_DR_MONTH) |

     ((tm->tm_year << STM32_RTC_DR_YEAR_SHIFT) & STM32_RTC_DR_YEAR) |

     ((tm->tm_wday << STM32_RTC_DR_WDAY_SHIFT) & STM32_RTC_DR_WDAY);

stm32_rtc_wpr_unlock(rtc);/*“RTC寄存器“解锁,允许写入*/

ret = stm32_rtc_enter_init_mode(rtc);/*令RTC进入初始化模式*/

if (ret) {

dev_err(dev, "Can't enter in init mode. Set time aborted.\n");

goto end;

}

writel_relaxed(tr, rtc->base + regs->tr);/*tr写入RTC_TR时间寄存器*/

writel_relaxed(dr, rtc->base + regs->dr);/*dr写入RTC_DR日期寄存器*/

stm32_rtc_exit_init_mode(rtc);/*令RTC进入“自由运行模式“*/

ret = stm32_rtc_wait_sync(rtc);/*令“日历影子寄存器“同步*/

end:

stm32_rtc_wpr_lock(rtc);/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器*/

return ret;

}

//函数功能:读RTC报警时间,保存到alrm结构中

static int stm32_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

const struct stm32_rtc_events *evts = &rtc->data->events;

struct rtc_time *tm = &alrm->time;

unsigned int alrmar, cr, status;

alrmar = readl_relaxed(rtc->base + regs->alrmar);

/*读取STM32MP1的RTC_ALRMAR寄存器的值*/

cr = readl_relaxed(rtc->base + regs->cr);

/*读取STM32MP1的RTC_CR寄存器的值*/

status = readl_relaxed(rtc->base + regs->sr);

/*读取STM32MP1的RTC_CS寄存器的值*/

if (alrmar & STM32_RTC_ALRMXR_DATE_MASK) {

/*日期/天不用匹配,即每天触发一次报警

 * Date/day doesn't matter in Alarm comparison so alarm

 * triggers every day

 */

tm->tm_mday = -1;

tm->tm_wday = -1;

} else {//日期/天匹配,才会报警

if (alrmar & STM32_RTC_ALRMXR_WDSEL) {

/*在星期几触发一次报警;

Alarm is set to a day of week */

tm->tm_mday = -1;

tm->tm_wday = (alrmar & STM32_RTC_ALRMXR_WDAY) >>

            STM32_RTC_ALRMXR_WDAY_SHIFT;

/*将RTC_ALRMAR中的DU[3:0]保存到tm->tm_wday*/

tm->tm_wday %= 7;/*得到星期几*/

} else {

/* 在某天触发报警

Alarm is set to a day of month */

tm->tm_wday = -1;

tm->tm_mday = (alrmar & STM32_RTC_ALRMXR_DATE) >>

       STM32_RTC_ALRMXR_DATE_SHIFT;

/*将RTC_ALRMAR中的DU[3:0]保存到tm->tm_wday*/

}

}

if (alrmar & STM32_RTC_ALRMXR_HOUR_MASK) {

/* 不要求小时报警

Hours don't matter in Alarm comparison */

tm->tm_hour = -1;

} else {/*要求小时报警*/

tm->tm_hour = (alrmar & STM32_RTC_ALRMXR_HOUR) >>

       STM32_RTC_ALRMXR_HOUR_SHIFT;

/*将RTC_ALRMAR中的HU[3:0]保存到tm->tm_hour*/

if (alrmar & STM32_RTC_ALRMXR_PM)

tm->tm_hour += 12;

}

if (alrmar & STM32_RTC_ALRMXR_MIN_MASK) {

/* 不要求分钟报警

Minutes don't matter in Alarm comparison */

tm->tm_min = -1;

} else {/*要求分钟报警*/

tm->tm_min = (alrmar & STM32_RTC_ALRMXR_MIN) >>

      STM32_RTC_ALRMXR_MIN_SHIFT;

/*将RTC_ALRMAR中的MNT[2:0]和MNU[3:0]保存到tm->tm_min*/

}

if (alrmar & STM32_RTC_ALRMXR_SEC_MASK) {

/* 不要求妙报警

Seconds don't matter in Alarm comparison */

tm->tm_sec = -1;

} else {/*要求妙报警*/

tm->tm_sec = (alrmar & STM32_RTC_ALRMXR_SEC) >>

      STM32_RTC_ALRMXR_SEC_SHIFT;

/*将RTC_ALRMAR中的ST[2:0]和SU[3:0]保存到tm->tm_sec*/

}

bcd2tm(tm);

alrm->enabled = (cr & STM32_RTC_CR_ALRAE) ? 1 : 0;

/*如果“Alarm A enable”,则返回1;

如果“Alarm A disabled”,则返回0;

*/

alrm->pending = (status & evts->alra) ? 1 : 0;

/*如果“Alarm A标志“建立,则返回1;

如果“Alarm A标志“没有建立,则返回0;

*/

return 0;

}

/*函数功能:

enabled=1,设置使能“ALARM A“和使能“ALARM A“报警时能产生中断;

enabled=0,设置不使能“ALARM A“和不使能“ALARM A“报警时能产生中断;

*/

static int stm32_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

const struct stm32_rtc_events *evts = &rtc->data->events;

unsigned int cr;

cr = readl_relaxed(rtc->base + regs->cr);

/*读取STM32MP1的RTC_CR寄存器的值*/

stm32_rtc_wpr_unlock(rtc);/*“RTC寄存器“解锁,允许写入*/

/* We expose Alarm A to the kernel */

if (enabled)

cr |= (STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);

/*准备将RTC_CR寄存器的bit12置1,使能ALARM A报警中断*/

/*准备将RTC_CR寄存器的bit8置1,使能ALARM A*/

else

cr &= ~(STM32_RTC_CR_ALRAIE | STM32_RTC_CR_ALRAE);

/*准备将RTC_CR寄存器的bit12置0,不使能ALARM A报警中断*/

/*准备将RTC_CR寄存器的bit8置0,不使能ALARM A*/

writel_relaxed(cr, rtc->base + regs->cr);/*cr写入RTC_CR寄存器*/

/* Clear event flags, otherwise new events won't be received */

stm32_rtc_clear_event_flags(rtc, evts->alra);

/*将evts->alra写入RTC_SCR寄存器(RTC status clear register)*/

stm32_rtc_wpr_lock(rtc);/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器*/

return 0;

}

//函数功能:返回0,表示设置的ALARM报警时间tm是有效的

static int stm32_rtc_valid_alrm(struct stm32_rtc *rtc, struct rtc_time *tm)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

int cur_day, cur_mon, cur_year, cur_hour, cur_min, cur_sec;

unsigned int dr = readl_relaxed(rtc->base + regs->dr);

/*读取STM32MP1的RTC_DR日期寄存器的值*/

unsigned int tr = readl_relaxed(rtc->base + regs->tr);

/*读取STM32MP1的RTC_TR时间寄存器的值*/

cur_day = (dr & STM32_RTC_DR_DATE) >> STM32_RTC_DR_DATE_SHIFT;

cur_mon = (dr & STM32_RTC_DR_MONTH) >> STM32_RTC_DR_MONTH_SHIFT;

cur_year = (dr & STM32_RTC_DR_YEAR) >> STM32_RTC_DR_YEAR_SHIFT;

cur_sec = (tr & STM32_RTC_TR_SEC) >> STM32_RTC_TR_SEC_SHIFT;

cur_min = (tr & STM32_RTC_TR_MIN) >> STM32_RTC_TR_MIN_SHIFT;

cur_hour = (tr & STM32_RTC_TR_HOUR) >> STM32_RTC_TR_HOUR_SHIFT;

/*

 * Assuming current date is M-D-Y H:M:S.

 * RTC alarm can't be set on a specific month and year.

 * So the valid alarm range is:

 * M-D-Y H:M:S < alarm <= (M+1)-D-Y H:M:S

 * with a specific case for December...

 */

if ((((tm->tm_year > cur_year) &&

      (tm->tm_mon == 0x1) && (cur_mon == 0x12)) ||

     ((tm->tm_year == cur_year) &&

      (tm->tm_mon <= cur_mon + 1))) &&

    ((tm->tm_mday > cur_day) ||

     ((tm->tm_mday == cur_day) &&

     ((tm->tm_hour > cur_hour) ||

      ((tm->tm_hour == cur_hour) && (tm->tm_min > cur_min)) ||

      ((tm->tm_hour == cur_hour) && (tm->tm_min == cur_min) &&

       (tm->tm_sec >= cur_sec))))))

return 0;

return -EINVAL;

}

//函数功能:设置“ALARM A报警时间“,报警时间保存在alrm中

static int stm32_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

struct rtc_time *tm = &alrm->time;

unsigned int cr, isr, alrmar;

int ret = 0;

tm2bcd(tm);/*将rtc_time结构转换为BCD格式*/

/*

 * RTC alarm can't be set on a specific date, unless this date is

 * up to the same day of month next month.

 */

if (stm32_rtc_valid_alrm(rtc, tm) < 0) {

/*stm32_rtc_valid_alrm()返回0,表示设置的ALARM报警时间tm是有效的*/

dev_err(dev, "Alarm can be set only on upcoming month.\n");

return -EINVAL;

}

alrmar = 0;

/* tm_year and tm_mon are not used because not supported by RTC */

alrmar |= (tm->tm_mday << STM32_RTC_ALRMXR_DATE_SHIFT) &

  STM32_RTC_ALRMXR_DATE;

/* 24-hour format */

alrmar &= ~STM32_RTC_ALRMXR_PM;

alrmar |= (tm->tm_hour << STM32_RTC_ALRMXR_HOUR_SHIFT) &

  STM32_RTC_ALRMXR_HOUR;

alrmar |= (tm->tm_min << STM32_RTC_ALRMXR_MIN_SHIFT) &

  STM32_RTC_ALRMXR_MIN;

alrmar |= (tm->tm_sec << STM32_RTC_ALRMXR_SEC_SHIFT) &

  STM32_RTC_ALRMXR_SEC;

stm32_rtc_wpr_unlock(rtc);/*“RTC寄存器“解锁,允许写入*/

/* Disable Alarm */

cr = readl_relaxed(rtc->base + regs->cr);/*读取STM32MP1的RTC_CR寄存器的值*/

cr &= ~STM32_RTC_CR_ALRAE;/*准备将RTC_CR寄存器的bit8置0,不使能ALARM A*/

writel_relaxed(cr, rtc->base + regs->cr);/*cr写入RTC_CR寄存器*/

/*

 * Poll Alarm write flag to be sure that Alarm update is allowed: it

 * takes around 2 rtc_ck clock cycles

 */

ret = readl_relaxed_poll_timeout_atomic(rtc->base + regs->isr,

isr,

(isr & STM32_RTC_ISR_ALRAWF),

10, 100000);

/*每10us轮询一次“RTC_ICSR寄存器“的bit0是否置1;

直到建立“Alarm A写标志“,才可以设置报警时间;

*/

if (ret) {

dev_err(dev, "Alarm update not allowed\n");

goto end;

}

/* Write to Alarm register */

writel_relaxed(alrmar, rtc->base + regs->alrmar);

/*将alrmar的值写入STM32MP1的RTC_ALRMAR寄存器*/

stm32_rtc_alarm_irq_enable(dev, alrm->enabled);

    /*

      enabled=1,设置使能“ALARM A“和使能“ALARM A“报警时能产生中断;

      enabled=0,设置不使能“ALARM A“和不使能“ALARM A“报警时能产生中断;

    */

end:

stm32_rtc_wpr_lock(rtc);/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器*/

return ret;

}

static const struct rtc_class_ops stm32_rtc_ops = {

.read_time = stm32_rtc_read_time,

.set_time = stm32_rtc_set_time,

.read_alarm = stm32_rtc_read_alarm,

/*给函数指针赋值,使用stm32_rtc_read_alarm()读RTC报警时间*/

.set_alarm = stm32_rtc_set_alarm,

/*给函数指针赋值,使用stm32_rtc_set_alarm()设置“ALARM A报警时间“*/

.alarm_irq_enable = stm32_rtc_alarm_irq_enable,

    /*给函数指针赋值,使用stm32_rtc_alarm_irq_enable(enabled),

      enabled=1,设置使能“ALARM A“和使能“ALARM A“报警时能产生中断;

      enabled=0,设置不使能“ALARM A“和不使能“ALARM A“报警时能产生中断;

    */

};

/*清除“RTC initialization control and status register“中的事件标志位*/

/*

static void stm32_rtc_clear_events(struct stm32_rtc *rtc,

   unsigned int flags)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

//Flags are cleared by writing 0 in RTC_ISR

writel_relaxed(readl_relaxed(rtc->base + regs->isr) & ~flags,

       rtc->base + regs->isr);

//readl_relaxed()读取STM32MP1的RTC_ICSR寄存器的值

//writel_relaxed()写STM32MP1的RTC_ICSR寄存器

//采用先读后写,清除“RTC initialization control and status register“中的事件标志位

}

*/

/*根据设备树,这个结构没用*/

/*

static const struct stm32_rtc_data stm32_rtc_data = {

.has_pclk = false,

.need_dbp = true,

.has_lsco = false,

.regs = {

.tr = 0x00,

.dr = 0x04,

.cr = 0x08,

.isr = 0x0C,

.prer = 0x10,

.alrmar = 0x1C,

.wpr = 0x24,

.sr = 0x0C, //set to ISR offset to ease alarm management

.scr = UNDEF_REG,

.cfgr = UNDEF_REG,

.verr = UNDEF_REG,

},

.events = {

.alra = STM32_RTC_ISR_ALRAF,

},

.clear_events = stm32_rtc_clear_events,

};

*/

/*根据设备树,这个结构没用*/

/*

static const struct stm32_rtc_data stm32h7_rtc_data = {

.has_pclk = true,

.need_dbp = true,

.has_lsco = false,

.regs = {

.tr = 0x00,

.dr = 0x04,

.cr = 0x08,

.isr = 0x0C,

.prer = 0x10,

.alrmar = 0x1C,

.wpr = 0x24,

.sr = 0x0C, //set to ISR offset to ease alarm management

.scr = UNDEF_REG,

.cfgr = UNDEF_REG,

.verr = UNDEF_REG,

},

.events = {

.alra = STM32_RTC_ISR_ALRAF,

},

.clear_events = stm32_rtc_clear_events,

};

*/

/*将flags写入RTC_SCR寄存器(RTC status clear register)*/

static void stm32mp1_rtc_clear_events(struct stm32_rtc *rtc,unsigned int flags)

{

struct stm32_rtc_registers regs = rtc->data->regs;

/* Flags are cleared by writing 1 in RTC_SCR */

writel_relaxed(flags, rtc->base + regs.scr);

/*将flags写入RTC_SCR寄存器*/

}

/*STM32MP157的RTC寄存器*/

static const struct stm32_rtc_data stm32mp1_data = {

.has_pclk = true,

.need_dbp = false,

.has_lsco = true,

.regs = {

.tr = 0x00,/*RTC_TR寄存器偏移地址*/

.dr = 0x04,/*RTC_DR寄存器偏移地址*/

.cr = 0x18,/*RTC_CR寄存器偏移地址*/

.isr = 0x0C, /*RTC_ICSR寄存器偏移地址,named RTC_ICSR on stm32mp1 */

.prer = 0x10,/*RTC_PRER寄存器偏移地址*/

.alrmar = 0x40,/*RTC_ALRMAR寄存器偏移地址*/

.wpr = 0x24,/*RTC_WPR寄存器偏移地址*/

.sr = 0x50,/*RTC_SR寄存器偏移地址*/

.scr = 0x5C,/*RTC_SCR寄存器偏移地址*/

.cfgr = 0x60,/*RTC_CFGR寄存器偏移地址*/

.verr = 0x3F4,/*RTC_VERR寄存器偏移地址*/

},

.events = {

.alra = STM32_RTC_SR_ALRA,/*将events.alra初始化为0*/

},

.clear_events = stm32mp1_rtc_clear_events,

/*将flags写入RTC_SCR寄存器(RTC status clear register)*/

};

/* 匹配列表 */

//驱动中的compatible属性要和和设备树中的compatible属性相匹配。

static const struct of_device_id stm32_rtc_of_match[] = {

/*{ .compatible = "st,stm32-rtc", .data = &stm32_rtc_data },

{ .compatible = "st,stm32h7-rtc", .data = &stm32h7_rtc_data },*/

{ .compatible = "st,stm32mp1-rtc", .data = &stm32mp1_data },

/*"st,stm32mp1-rtc"在设备树中*/

{ /*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/

      /* Sentinel */

}

};

MODULE_DEVICE_TABLE(of, stm32_rtc_of_match);

/**初始化STM32MP1的rtc寄存器*/

static int stm32_rtc_init(struct platform_device *pdev,

  struct stm32_rtc *rtc)

{

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int prer, pred_a, pred_s, pred_a_max, pred_s_max, cr;

unsigned int rate;

int ret = 0;

rate = clk_get_rate(rtc->rtc_ck);/*获得rtc->rtc_ck时钟源的当前时钟频率(HZ)*/

/* Find prediv_a and prediv_s to obtain the 1Hz calendar clock */

pred_a_max = STM32_RTC_PRER_PRED_A >> STM32_RTC_PRER_PRED_A_SHIFT;

pred_s_max = STM32_RTC_PRER_PRED_S >> STM32_RTC_PRER_PRED_S_SHIFT;

for (pred_a = pred_a_max; pred_a + 1 > 0; pred_a--) {

pred_s = (rate / (pred_a + 1)) - 1;

if (((pred_s + 1) * (pred_a + 1)) == rate)

break;

}

/*

 * Can't find a 1Hz, so give priority to RTC power consumption

 * by choosing the higher possible value for prediv_a

 */

if ((pred_s > pred_s_max) || (pred_a > pred_a_max)) {

pred_a = pred_a_max;

pred_s = (rate / (pred_a + 1)) - 1;

dev_warn(&pdev->dev, "rtc_ck is %s\n",

 (rate < ((pred_a + 1) * (pred_s + 1))) ?

 "fast" : "slow");

}

stm32_rtc_wpr_unlock(rtc);/*RTC写保护寄存器RTC_WPR*/

ret = stm32_rtc_enter_init_mode(rtc);/*令RTC进入初始化模式*/

if (ret) {

dev_err(&pdev->dev,

"Can't enter in init mode. Prescaler config failed.\n");

goto end;

}

prer = (pred_s << STM32_RTC_PRER_PRED_S_SHIFT) & STM32_RTC_PRER_PRED_S;

writel_relaxed(prer, rtc->base + regs->prer);

prer |= (pred_a << STM32_RTC_PRER_PRED_A_SHIFT) & STM32_RTC_PRER_PRED_A;

writel_relaxed(prer, rtc->base + regs->prer);

/* Force 24h time format */

cr = readl_relaxed(rtc->base + regs->cr);

cr &= ~STM32_RTC_CR_FMT;

writel_relaxed(cr, rtc->base + regs->cr);

stm32_rtc_exit_init_mode(rtc);/*令RTC进入“自由运行模式“*/

ret = stm32_rtc_wait_sync(rtc);/*令“日历影子寄存器“同步*/

end:

stm32_rtc_wpr_lock(rtc);/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器*/

return ret;

}

/*platform的probe函数为stm32_rtc_probe()*/

static int stm32_rtc_probe(struct platform_device *pdev)

{

struct stm32_rtc *rtc;

const struct stm32_rtc_registers *regs;

struct resource *res;

int ret;

rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);

/*向内核申请一块内存,当设备驱动程序被卸载时,内存会被自动释放*/

if (!rtc)

return -ENOMEM;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

/*platform_get_resource()函数从设备树中获取到RTC外设寄存器基地址*/

rtc->base = devm_ioremap_resource(&pdev->dev, res);

/*devm_ioremap_resource()函数完成内存映射,得到RTC外设寄存器物理基地址对应的虚拟地址*/

if (IS_ERR(rtc->base))

return PTR_ERR(rtc->base);

rtc->data = (struct stm32_rtc_data *)

    of_device_get_match_data(&pdev->dev);

regs = &rtc->data->regs;

if (rtc->data->need_dbp) {

rtc->dbp = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,

   "st,syscfg");

if (IS_ERR(rtc->dbp)) {

dev_err(&pdev->dev, "no st,syscfg\n");

return PTR_ERR(rtc->dbp);

}

ret = of_property_read_u32_index(pdev->dev.of_node, "st,syscfg",

 1, &rtc->dbp_reg);

if (ret) {

dev_err(&pdev->dev, "can't read DBP register offset\n");

return ret;

}

ret = of_property_read_u32_index(pdev->dev.of_node, "st,syscfg",

 2, &rtc->dbp_mask);

if (ret) {

dev_err(&pdev->dev, "can't read DBP register mask\n");

return ret;

}

}

if (!rtc->data->has_pclk) {

rtc->pclk = NULL;

rtc->rtc_ck = devm_clk_get(&pdev->dev, NULL);

} else {

rtc->pclk = devm_clk_get(&pdev->dev, "pclk");

if (IS_ERR(rtc->pclk)) {

if (PTR_ERR(rtc->pclk) != -EPROBE_DEFER)

dev_err(&pdev->dev, "no pclk clock");

return PTR_ERR(rtc->pclk);

}

rtc->rtc_ck = devm_clk_get(&pdev->dev, "rtc_ck");

}

if (IS_ERR(rtc->rtc_ck)) {

if (PTR_ERR(rtc->pclk) != -EPROBE_DEFER)

dev_err(&pdev->dev, "no rtc_ck clock");

return PTR_ERR(rtc->rtc_ck);

}

if (rtc->data->has_pclk) {

ret = clk_prepare_enable(rtc->pclk);

if (ret)

return ret;

}

ret = clk_prepare_enable(rtc->rtc_ck);/*使能预分频器时钟*/

if (ret)

goto err;

if (rtc->data->need_dbp)

regmap_update_bits(rtc->dbp, rtc->dbp_reg,

   rtc->dbp_mask, rtc->dbp_mask);

/*

 * After a system reset, RTC_ISR.INITS flag can be read to check if

 * the calendar has been initialized or not. INITS flag is reset by a

 * power-on reset (no vbat, no power-supply). It is not reset if

 * rtc_ck parent clock has changed (so RTC prescalers need to be

 * changed). That's why we cannot rely on this flag to know if RTC

 * init has to be done.

 */

ret = stm32_rtc_init(pdev, rtc);

/**初始化STM32MP1的rtc寄存器*/

if (ret)

goto err;

rtc->irq_alarm = platform_get_irq(pdev, 0);

/*获取设备树的中断号*/

if (rtc->irq_alarm <= 0) {

ret = rtc->irq_alarm;

goto err;

}

ret = device_init_wakeup(&pdev->dev, true);

if (ret)

goto err;

ret = dev_pm_set_wake_irq(&pdev->dev, rtc->irq_alarm);

if (ret)

goto err;

platform_set_drvdata(pdev, rtc);

rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,&stm32_rtc_ops, THIS_MODULE);

/*注册RTC类设备*/

if (IS_ERR(rtc->rtc_dev)) {

ret = PTR_ERR(rtc->rtc_dev);

dev_err(&pdev->dev, "rtc device registration failed, err=%d\n",

ret);

goto err;

}

/* Handle RTC alarm interrupts */

ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,

stm32_rtc_alarm_irq, IRQF_ONESHOT,

pdev->name, rtc);

/*RTC的“Alarm A”中断服务函数为stm32_rtc_alarm_irq()*/

if (ret) {

dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already claimed\n",

rtc->irq_alarm);

goto err;

}

if (rtc->data->has_lsco) {

ret = of_property_read_s32(pdev->dev.of_node,

   "st,lsco", &rtc->lsco);

if (!ret) {

ret = stm32_rtc_clk_lsco_register(pdev);

if (ret)

dev_warn(&pdev->dev,

 "LSCO clock registration failed: %d\n",

 ret);

} else {

rtc->lsco = ret;

dev_dbg(&pdev->dev, "No LSCO clock: %d\n", ret);

}

}

/*

 * If INITS flag is reset (calendar year field set to 0x00), calendar

 * must be initialized

 */

if (!(readl_relaxed(rtc->base + regs->isr) & STM32_RTC_ISR_INITS))

dev_warn(&pdev->dev, "Date/Time must be initialized\n");

if (regs->verr != UNDEF_REG) {

u32 ver = readl_relaxed(rtc->base + regs->verr);

dev_info(&pdev->dev, "registered rev:%d.%d\n",

 (ver >> STM32_RTC_VERR_MAJREV_SHIFT) & 0xF,

 (ver >> STM32_RTC_VERR_MINREV_SHIFT) & 0xF);

}

return 0;

err:

if (rtc->data->has_pclk)

clk_disable_unprepare(rtc->pclk);

clk_disable_unprepare(rtc->rtc_ck);

if (rtc->data->need_dbp)

regmap_update_bits(rtc->dbp, rtc->dbp_reg, rtc->dbp_mask, 0);

dev_pm_clear_wake_irq(&pdev->dev);

device_init_wakeup(&pdev->dev, false);

return ret;

}

/*platform的remove函数为stm32_rtc_remove()*/

static int stm32_rtc_remove(struct platform_device *pdev)

{

struct stm32_rtc *rtc = platform_get_drvdata(pdev);

const struct stm32_rtc_registers *regs = &rtc->data->regs;

unsigned int cr;

if (!IS_ERR_OR_NULL(rtc->clk_lsco))

clk_unregister_gate(rtc->clk_lsco);

/* Disable interrupts */

stm32_rtc_wpr_unlock(rtc);/*“RTC寄存器“解锁,允许写入*/

cr = readl_relaxed(rtc->base + regs->cr);

cr &= ~STM32_RTC_CR_ALRAIE;

writel_relaxed(cr, rtc->base + regs->cr);

stm32_rtc_wpr_lock(rtc);/*写错值,会重新激活“RTC寄存器“写保护,不允许写RTC寄存器*/

clk_disable_unprepare(rtc->rtc_ck);

if (rtc->data->has_pclk)

clk_disable_unprepare(rtc->pclk);

/* Enable backup domain write protection if needed */

if (rtc->data->need_dbp)

regmap_update_bits(rtc->dbp, rtc->dbp_reg, rtc->dbp_mask, 0);

dev_pm_clear_wake_irq(&pdev->dev);

device_init_wakeup(&pdev->dev, false);

return 0;

}

#ifdef CONFIG_PM_SLEEP

/*RTC暂停工作*/

static int stm32_rtc_suspend(struct device *dev)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

if (rtc->data->has_pclk)

clk_disable_unprepare(rtc->pclk);

return 0;

}

/*RTC恢复工作*/

static int stm32_rtc_resume(struct device *dev)

{

struct stm32_rtc *rtc = dev_get_drvdata(dev);

int ret = 0;

if (rtc->data->has_pclk) {

ret = clk_prepare_enable(rtc->pclk);

if (ret)

return ret;

}

ret = stm32_rtc_wait_sync(rtc);/*令“日历影子寄存器“同步*/

if (ret < 0)

return ret;

return ret;

}

#endif

static const struct dev_pm_ops stm32_rtc_pm_ops = {

SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_rtc_suspend, stm32_rtc_resume)

};

/*标准的platform驱动框架*/

static struct platform_driver stm32_rtc_driver = {

.probe = stm32_rtc_probe,/*platform的probe函数为stm32_rtc_probe()*/

.remove = stm32_rtc_remove,/*platform的remove函数为stm32_rtc_remove()*/

.driver = {

.name = DRIVER_NAME,/* 驱动名字,用于和设备匹配 */

.pm = &stm32_rtc_pm_ops,

.of_match_table = stm32_rtc_of_match,/*设备树匹配表*/

},

};

module_platform_driver(stm32_rtc_driver);

//stm32_rtc_driver为platform_driver结构

//用来向linux内核注册platform驱动

MODULE_ALIAS("platform:" DRIVER_NAME);

MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");//添加作者名字

MODULE_DESCRIPTION("STMicroelectronics STM32 Real Time Clock driver");//模块介绍

MODULE_LICENSE("GPL v2");//LICENSE采用“GPL协议”

4、编译设备树

打开VSCode中的终端,输入“make uImage dtbs LOADADDR=0XC2000040 -j8回车”,执行编译“Image”和“dtbs”,并指定装载的起始地址为0XC2000040,j8表示指定采用8线程执行。make dtbs”,用来指定编译设备树。见下图:

②输入“ls arch/arm/boot/uImage -l

查看是否生成了新的“uImage”文件

③输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l

查看是否生成了新的“stm32mp157d-atk.dtb”文件

4)、拷贝输出的文件:

①输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;

②输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC

③输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

④输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

⑤输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹

⑥输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

⑦输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车

给“stm32mp157d-atk.dtb”文件赋予可执行权限

⑧输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车 ,给“uImage”文件赋予可执行权限

⑨输入“ls /home/zgq/linux/tftpboot/ -l回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

5测试

给开发板上电,启动开发板,从网络下载程序

②输入“root

③输入“date回车”,查看时间。

④输入“date --help回车”,查看date命令如何设置系统时间。

⑤输入“date -s "2025-02-09 13:16:00"回车”,修改当前时间,但还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面,因此,系统重启以后时间又会丢失。

⑥输入“date回车”,查看时间。

⑦输入“hwclock -w回车”,将当前的系统时间写入到RTC里。

⑧重启开发板

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/16842.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java项目之基于推荐算法的图书购物网站源码(ssm+mybatis+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的基于推荐算法的图书购物网站项目。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于推荐算法的…

【Antv G2 5.x】饼图添加点击事件,获取当前坐标数据

// 监听 tooltip:show 事件this.chart.on(tooltip:show, (event) => {this.currentShowTooltipName = event.data.items[0].name})// 监听绘图区plot的点击事件this.chart.on(interval:click, ev => {this.$emit(chartClick, this.currentShowTooltipName);})// 监听绘图…

称呼计算器:智能科技,简化您的计算生活

一款手机应用程序&#xff0c;安卓设备上使用。这款计算器应用以其简洁的界面、实用的功能和良好的用户体验而受到用户的喜爱。 计算器的主要特点包括&#xff1a; 基本计算功能&#xff1a;支持加、减、乘、除等基本运算。 科学计算器模式&#xff1a;提供更高级的数学运算功…

STM32 裸机 C编程 vs micropython编程 vs linux python

以led点亮为例。 STM32 裸机 C编程需要设置时钟&#xff0c;管脚。 static void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};// GPIO端口时钟使能__HAL_RCC_GPIOA_CLK_ENABLE();// 配置PA5为推挽输出模式GPIO_InitStruct.Pin GPIO_PIN_5;GPIO_InitStruct.M…

Spring boot(maven) - Mybatis 超级入门版

前言&#xff1a; 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往…

清华大学新闻与传播学院沈阳团队出品的《DeepSeek:从入门到精通》104页PDF

前言 本机运行DeepSeek R1大模型文章如下&#xff1a; Windows电脑本地部署运行DeepSeek R1大模型&#xff08;基于Ollama和Chatbox&#xff09;【保姆级万字教程】在Windows计算机部署DeepSeek大模型&#xff0c;给在实验室无外网的同事们用&#xff08;基于Ollama和OpenWebUI…

kbengine服务器和 数据库 系统路径配置

一、服务器 系统路径配置 二、mysql5.7.44 系统路径配置 mysql 压缩包安装方式 解压压缩包&#xff0c;将解压路径加入 系统环境。 或者 系统变量新增 变量名&#xff1a;MYSQL_HOME 变量值&#xff1a;C:\MyPrograms\mysql-8.0.12-winx64修改系统变量的 path 变量&#xff…

性格测评小程序04题库管理

目录 1 创建数据源1.1 题库表1.2 选项表 2 搭建管理后台2.1 搭建题库功能2.2 搭建选项功能2.3 题库和选项联动 3 最终效果总结 我们现在性格测评的算法是通过40个题目来测评用户属于哪一个分类&#xff0c;为此后台需要有可以设置题目和选项的功能&#xff0c;本篇我们介绍一下…

Navicat导入海量Excel数据到数据库(简易介绍)

目录 前言正文 前言 此处主要作为科普帖进行记录 原先Java处理海量数据的导入时&#xff0c;由于接口超时&#xff0c;数据处理不过来&#xff0c;后续转为Navicat Navicat 是一款功能强大的数据库管理工具&#xff0c;支持多种数据库系统&#xff08;如 MySQL、PostgreSQL、…

sql难点

一、 假设你有一个查询&#xff0c;需要根据 id 是否为 null 来动态生成 SQL 条件&#xff1a; xml复制 <select id"getResources" resultType"Resource">SELECT * FROM resources<where><if test"id ! null">and id <!…

2024年12月中国电子学会青少年软件编程(Python)等级考试试卷(六级)

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;六级&#xff09; 一、单选题(共25题&#xff0c;共50分) 1.下面代码的输出结果正确的是?(B) import json json_str [ "Alice", "girl", 17,"New York"] data json.loa…

Qwen2.5-Max:国内新一代 MoE 大模型的崛起!

通义千问 DeepSeek 才火没多久&#xff0c;国内又出现了一款可以比肩 DeepSeek 的 MoE 大模型——Qwen2.5-Max。这款大模型使用了超过 20 万亿 token 的预训练数据及精心设计的后训练方案进行训练&#xff0c;无疑开启了 AI 的新时代。 Qwen2.5-Max Qwen2.5-Max Qwen&#xff0…

把 DeepSeek1.5b 部署在显卡小于4G的电脑上

这里写自定义目录标题 介绍准备安装 Ollama查看CUDA需要版本安装CudaToolkit检查Cuda是否装好二、设置Ollama环境变量三、验证是否跑在GPU上ollama如何导入本地下载的模型安装及配置docker安装open-webui启动open-webui开始对话介绍 Deepseek1.5b能够运行在只用cpu和gpu内存小…

FPGA 28 ,基于 Vivado Verilog 的呼吸灯效果设计与实现( 使用 Vivado Verilog 实现呼吸灯效果 )

目录 前言 一. 设计流程 1.1 需求分析 1.2 方案设计 1.3 PWM解析 二. 实现流程 2.1 确定时间单位和精度 2.2 定义参数和寄存器 2.3 实现计数器逻辑 2.4 控制 LED 状态 三. 整体流程 3.1 全部代码 3.2 代码逻辑 1. 参数定义 2. 分级计数 3. 状态切换 4. LED 输…

日常知识点之面试后反思裸写string类

1&#xff1a;实现一个字符串类。 简单汇总 最简单的方案&#xff0c;使用一个字符串指针&#xff0c;以及实际字符串长度即可。 参考stl的实现&#xff0c;为了提升string的性能&#xff0c;实际上单纯的字符串指针和实际长度是不够了&#xff0c;如上&#xff0c;有优化方案…

用AI绘制CAD气温曲线图

此文章视频讲解地址 https://www.bilibili.com/video/BV1JtKjenEhF 需求 根据气温的JSON数据&#xff0c;用AI自动生成CAD格式的气温曲线DWG图 数据准备 用deepseek获取了北京市最近一个月的气温json数据 AI对话 首先进入唯杰地图云端管理平台 选择与唯杰地图AI对话 需求描…

Web应用项目开发 ——Spring Boot邮件发送

一.邮件发送介绍 邮件发送是一个非常常见的功能&#xff0c;注册时的身份认证、重要通知发送等都会用到邮件发送。在现代的Web应用程序中&#xff0c;邮件发送功能是非常常见且重要的一部分&#xff0c;Spring Boot框架提供了简单且强大的方式来实现邮件发送功能。Spring中提供…

【STM32】通过L496的HAL库Flash建立FatFS文件系统(CubeMX自动配置R0.12C版本)

【STM32】通过L496的HAL库Flash建立FatFS文件系统&#xff08;CubeMX自动配置R0.12C版本&#xff09; 文章目录 FlashFlash地址写Flash地址读 FatFS文件系统配置FatFS移植驱动函数时间戳函数 文件操作函数工作区缓存文件挂载和格式化测试文件读写测试其他文件操作函数 测试附录…

机械臂运动学笔记(一):正向运动学

正向运动学指的是通过相邻关节间的转动和移动坐标&#xff0c;将末端的坐标计算出来。 反向运动学指的是已知机械臂末端的坐标&#xff0c;反算每个关节可能的转动和移动参数。 参考资料&#xff1a;4.机械臂几何法与DH表示法_哔哩哔哩_bilibili 一.任意连杆连接的变量定义&a…

Leetcode - 周赛435

目录 一、3442. 奇偶频次间的最大差值 I二、3443. K 次修改后的最大曼哈顿距离三、3444. 使数组包含目标值倍数的最少增量四、3445. 奇偶频次间的最大差值 II 一、3442. 奇偶频次间的最大差值 I 题目链接 本题使用数组统计字符串 s s s 中每个字符的出现次数&#xff0c;然后…