mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-10-02 14:23:19 +08:00
- reorganize common Raspberry Pi drivers under the device framework - add the Raspberry Pi 5 BSP, configuration and documentation - add BCM2712 and DesignWare AXI DMA controller support - distinguish BCM2712 DMA32 and DMA40 channels by channel mask - restore DesignWare AXI DMA enable bits after controller reset - support a shared AXI maximum burst length across DMA channels Signed-off-by: GuEe-GUI <2991707448@qq.com>
336 lines
7.8 KiB
C
Executable File
336 lines
7.8 KiB
C
Executable File
/*
|
|
* Copyright (c) 2006-2022, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2022-11-26 GuEe-GUI first version
|
|
*/
|
|
|
|
#define DBG_TAG "rtc.rpi"
|
|
#define DBG_LVL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
#include "rtc_dm.h"
|
|
#include <firmware-raspberrypi.h>
|
|
|
|
enum
|
|
{
|
|
RTC_TIME,
|
|
RTC_ALARM,
|
|
RTC_ALARM_PENDING,
|
|
RTC_ALARM_ENABLE,
|
|
RTC_BBAT_CHG_VOLTS,
|
|
RTC_BBAT_CHG_VOLTS_MIN,
|
|
RTC_BBAT_CHG_VOLTS_MAX,
|
|
RTC_BBAT_VOLTS
|
|
};
|
|
|
|
struct rpi_rtc
|
|
{
|
|
struct rt_device parent;
|
|
|
|
struct rpi_firmware *rpi_fw;
|
|
rt_uint32_t bbat_vchg_microvolts;
|
|
|
|
struct rt_mutex lock;
|
|
struct rt_timer alarm_mon;
|
|
struct rt_rtc_wkalarm wkalarm;
|
|
};
|
|
|
|
#define raw_to_rpi_rtc(raw) rt_container_of(raw, struct rpi_rtc, parent)
|
|
|
|
static rt_err_t rpi_rtc_alarm_clear_pending(struct rpi_rtc *rrtc)
|
|
{
|
|
rt_uint32_t data[2] = { RTC_ALARM_PENDING, 1 };
|
|
|
|
return rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_SET_RTC_REG,
|
|
&data, sizeof(data));
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_set_charge_voltage(struct rpi_rtc *rrtc)
|
|
{
|
|
rt_err_t err;
|
|
rt_uint32_t data[2] = { RTC_BBAT_CHG_VOLTS, rrtc->bbat_vchg_microvolts };
|
|
|
|
err = rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_SET_RTC_REG,
|
|
&data, sizeof(data));
|
|
|
|
if (err)
|
|
{
|
|
LOG_E("Failed to set trickle charge voltage to %uuV error = %s",
|
|
rrtc->bbat_vchg_microvolts, rt_strerror(err));
|
|
}
|
|
else if (rrtc->bbat_vchg_microvolts)
|
|
{
|
|
LOG_I("Trickle charging enabled at %uuV", rrtc->bbat_vchg_microvolts);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_alarm_irq_is_enabled(struct rpi_rtc *rrtc,
|
|
rt_bool_t *enabled)
|
|
{
|
|
rt_err_t err;
|
|
rt_uint32_t data[2] = { RTC_ALARM_ENABLE };
|
|
|
|
err = rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_GET_RTC_REG,
|
|
&data, sizeof(data));
|
|
|
|
*enabled = !!(data[1] & 0x1);
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_alarm_irq_enable(struct rpi_rtc *rrtc, rt_bool_t enabled)
|
|
{
|
|
rt_uint32_t data[2] = { RTC_ALARM_ENABLE, enabled };
|
|
|
|
return rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_SET_RTC_REG,
|
|
&data, sizeof(data));
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_read_time(struct rpi_rtc *rrtc, time_t *sec)
|
|
{
|
|
rt_err_t err;
|
|
rt_uint32_t data[2] = { RTC_TIME };
|
|
|
|
err = rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_GET_RTC_REG,
|
|
&data, sizeof(data));
|
|
|
|
*sec = data[1];
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_set_time(struct rpi_rtc *rrtc, time_t *sec)
|
|
{
|
|
rt_uint32_t data[2] = { RTC_TIME, (rt_uint32_t)*sec };
|
|
|
|
return rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_SET_RTC_REG,
|
|
&data, sizeof(data));
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_read_alarm(struct rpi_rtc *rrtc,
|
|
struct rt_rtc_wkalarm *alarm)
|
|
{
|
|
rt_err_t err;
|
|
rt_uint32_t data[2] = { RTC_ALARM };
|
|
|
|
if (!(err = rpi_rtc_alarm_irq_is_enabled(rrtc, &alarm->enable)))
|
|
{
|
|
err = rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_GET_RTC_REG,
|
|
&data, sizeof(data));
|
|
}
|
|
|
|
rtc_timestamp_to_wkalarm(data[1], alarm);
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_set_alarm(struct rpi_rtc *rrtc,
|
|
struct rt_rtc_wkalarm *alarm)
|
|
{
|
|
rt_err_t err;
|
|
struct rt_rtc_wkalarm *wkalarm = &rrtc->wkalarm;
|
|
rt_uint32_t data[2] = { RTC_ALARM, (rt_uint32_t)rtc_wkalarm_to_timestamp(alarm) };
|
|
|
|
err = rpi_firmware_property(rrtc->rpi_fw, RPI_FIRMWARE_SET_RTC_REG,
|
|
&data, sizeof(data));
|
|
|
|
if (!err && !(err = rpi_rtc_alarm_irq_enable(rrtc, alarm->enable)))
|
|
{
|
|
rt_mutex_take(&rrtc->lock, RT_WAITING_FOREVER);
|
|
|
|
wkalarm->enable = alarm->enable;
|
|
wkalarm->tm_hour = alarm->tm_hour;
|
|
wkalarm->tm_min = alarm->tm_min;
|
|
wkalarm->tm_sec = alarm->tm_sec;
|
|
|
|
if (wkalarm->enable)
|
|
{
|
|
/*
|
|
* raspberrypi firmware rtc have an IRQ pin that is not connected
|
|
* to a CPU interrupt but rather directly to a PMIC or power supply.
|
|
* So we should poll once per second.
|
|
*/
|
|
rt_tick_t tick = rt_tick_from_millisecond(1000);
|
|
|
|
rt_timer_control(&rrtc->alarm_mon, RT_TIMER_CTRL_SET_TIME, &tick);
|
|
|
|
rt_timer_start(&rrtc->alarm_mon);
|
|
}
|
|
|
|
rt_mutex_release(&rrtc->lock);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_control(rt_device_t dev, int cmd, void *args)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
struct rpi_rtc *rrtc = raw_to_rpi_rtc(dev);
|
|
|
|
if (!args)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case RT_DEVICE_CTRL_RTC_GET_TIME:
|
|
err = rpi_rtc_read_time(rrtc, args);
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_RTC_SET_TIME:
|
|
err = rpi_rtc_set_time(rrtc, args);
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_RTC_GET_TIMEVAL:
|
|
err = rpi_rtc_read_time(rrtc, (time_t *)&((struct timeval *)args)->tv_sec);
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_RTC_SET_TIMEVAL:
|
|
err = rpi_rtc_set_time(rrtc, (time_t *)&((struct timeval *)args)->tv_sec);
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_RTC_GET_ALARM:
|
|
err = rpi_rtc_read_alarm(rrtc, args);
|
|
break;
|
|
|
|
case RT_DEVICE_CTRL_RTC_SET_ALARM:
|
|
err = rpi_rtc_set_alarm(rrtc, args);
|
|
break;
|
|
|
|
default:
|
|
err = -RT_EINVAL;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef RT_USING_DEVICE_OPS
|
|
const static struct rt_device_ops rpi_rtc_rtc_ops =
|
|
{
|
|
.control = rpi_rtc_control,
|
|
};
|
|
#endif
|
|
|
|
static void rpi_rtc_alarm_mon(void *param)
|
|
{
|
|
struct rpi_rtc *rrtc = param;
|
|
struct rt_rtc_wkalarm alarm;
|
|
time_t alarm_time, next_alarm_time;
|
|
|
|
rt_mutex_take(&rrtc->lock, RT_WAITING_FOREVER);
|
|
|
|
rpi_rtc_read_alarm(rrtc, &alarm);
|
|
alarm_time = rtc_wkalarm_to_timestamp(&alarm);
|
|
next_alarm_time = rtc_wkalarm_to_timestamp(&rrtc->wkalarm);
|
|
|
|
if (alarm_time < next_alarm_time)
|
|
{
|
|
rrtc->wkalarm.enable = RT_FALSE;
|
|
rt_alarm_update(&rrtc->parent, 1);
|
|
}
|
|
|
|
rt_mutex_release(&rrtc->lock);
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_probe(struct rt_platform_device *pdev)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
const char *dev_name;
|
|
struct rpi_firmware *rpi_fw;
|
|
struct rt_device *dev = &pdev->parent;
|
|
struct rt_ofw_node *np = dev->ofw_node, *fw_np;
|
|
struct rpi_rtc *rrtc = rt_calloc(1, sizeof(*rrtc));
|
|
|
|
if (!rrtc)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
fw_np = rt_ofw_parse_phandle(np, "firmware", 0);
|
|
|
|
if (!fw_np)
|
|
{
|
|
err = -RT_EINVAL;
|
|
goto _fail;
|
|
}
|
|
|
|
rpi_fw = rpi_firmware_get(fw_np);
|
|
rt_ofw_node_put(fw_np);
|
|
|
|
if (!rpi_fw)
|
|
{
|
|
err = -RT_EINVAL;
|
|
goto _fail;
|
|
}
|
|
|
|
rrtc->rpi_fw = rpi_fw;
|
|
|
|
rpi_rtc_alarm_clear_pending(rrtc);
|
|
|
|
rt_ofw_prop_read_u32(np, "trickle-charge-microvolt", &rrtc->bbat_vchg_microvolts);
|
|
rpi_rtc_set_charge_voltage(rrtc);
|
|
|
|
dev->user_data = rrtc;
|
|
|
|
rrtc->parent.type = RT_Device_Class_RTC;
|
|
#ifdef RT_USING_DEVICE_OPS
|
|
rrtc->parent.ops = &rpi_rtc_rtc_ops;
|
|
#else
|
|
rrtc->parent.control = rpi_rtc_control;
|
|
#endif
|
|
|
|
rtc_dev_set_name(&rrtc->parent);
|
|
dev_name = rt_dm_dev_get_name(&rrtc->parent);
|
|
|
|
rt_mutex_init(&rrtc->lock, dev_name, RT_IPC_FLAG_FIFO);
|
|
rt_timer_init(&rrtc->alarm_mon, dev_name, rpi_rtc_alarm_mon, rrtc,
|
|
0, RT_TIMER_FLAG_PERIODIC);
|
|
|
|
rt_device_register(&rrtc->parent, dev_name, RT_DEVICE_FLAG_RDWR);
|
|
|
|
return RT_EOK;
|
|
|
|
_fail:
|
|
rt_free(rrtc);
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t rpi_rtc_remove(struct rt_platform_device *pdev)
|
|
{
|
|
struct rpi_rtc *rrtc = pdev->parent.user_data;
|
|
|
|
rt_timer_detach(&rrtc->alarm_mon);
|
|
rt_mutex_detach(&rrtc->lock);
|
|
rt_device_unregister(&rrtc->parent);
|
|
|
|
rt_free(rrtc);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static const struct rt_ofw_node_id rpi_rtc_ofw_ids[] =
|
|
{
|
|
{ .compatible = "raspberrypi,rpi-rtc" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct rt_platform_driver rpi_rtc_driver =
|
|
{
|
|
.name = "rtc-rpi",
|
|
.ids = rpi_rtc_ofw_ids,
|
|
|
|
.probe = rpi_rtc_probe,
|
|
.remove = rpi_rtc_remove,
|
|
};
|
|
RT_PLATFORM_DRIVER_EXPORT(rpi_rtc_driver);
|