之前的文章電源管理入門-1關機重啟詳解介紹了整機SoC的重啟也可以說是reset,那么子系統的reset,例如某個驅動(網卡、USB等)或者某個子系統(NPU、ISP等運行在獨立的M核或者R核上的AI系統),這些零碎模塊的reset就需要用另外一種機制,Linux提供了reset framework框架,我們可以使用這個框架對子系統reset,然后操作硬件CRU寄存器進行硬件的reset操作。
考慮到安全的因素對CRU寄存器的操作可以放在:
- ATF里面的BL31(通過SMC指令)
- 或者放到SCP里面(通過Linux-SCMI-》SCP)里面進行。
本小節先介紹下Linux里面的通用reset框架,下篇介紹arm-scmi到SCP進行CRU硬件操作的實現。
1. 簡介
復雜SoC內部有很多具有獨立功能的硬件模塊,例如CPU cores、GPU cores、USB控制器、MMC控制器、等等,出于功耗、穩定性等方面的考慮,有些SoC在內部為這些硬件模塊設計了復位信號(reset signals),軟件可通過寄存器(一般1個bit控制1個硬件)控制這些硬件模塊的復位狀態。
例如有3個軟件I2C/EMMC/IPC都有復位某個硬件模塊的需求,那么要寫三個復位操作代碼。
- 這些代碼可以進行抽象出來一個獨立的軟件框架-reset framework,
- 這樣軟件使用者(consumer:I2C/EMMC/IPC)直接使用硬件模塊的名字,就可以對硬件進行復位。
- 一個模塊硬件的復位實現為單獨的reset driver(provider),只用實現一次就可以了。
再次說明了,解決復雜問題的普遍方法就是抽象,而Linux內核可以說是玩得一手好抽象,也是操作系統的必備技能。
2. consumer-驅動軟件
對于硬件驅動來的需求來說,就是復位某個硬件,在驅動代碼里面可以通過硬件的名字進行復位,這個名字對應設置放在了dts文件中,例如:
i2c0: i2c@0xA1006000 {
compatible = "arch64,a10-i2c";
reg = <0 0xA1006000 0 0x100>;
interrupt-parent = <&gic>;
interrupts = <0 32 4>;
clock-frequency = <24000000>;
resets = <&rst 0x50 11>;
reset-names = "i2c0";
status = "disabled";
};
&rst:使用rst驅動,0x50:寄存器偏移,11:使用那個bit 進行復位的時候,在驅動軟件里面加上
i2c_dev->i2c_rst =
devm_reset_control_get(i2c_dev->dev, "i2c0");
static int i2c_reset_assert(struct reset_control *rstc)
{
int rc = 0;
rc = reset_control_assert(rstc);
if (rc < 0) {
pr_err("%s: failed
", __func__);
return rc;
}
return rc;
}
static int i2c_reset_assert(struct reset_control *rstc)
{
int rc = 0;
rc = reset_control_assert(rstc);
if (rc < 0) {
pr_err("%s: failed
", __func__);
return rc;
}
return rc;
}
static int i2c_hw_reset(struct i2c_dev *i2c_dev)
{
i2c_reset_assert(i2c_dev->i2c_rst );
udelay(1);
i2c_reset_release(i2c_dev->i2c_rst );
}
i2c_dev->i2c_rst是一個reset_control的結構體
struct reset_control {
struct reset_controller_dev *rcdev;
struct list_head list;
unsigned int id;
struct kref refcnt;
bool acquired;
bool shared;
bool array;
atomic_t deassert_count;
atomic_t triggered_count;
};
上面i2c驅動作為consumer調用了reset framework提供的API函數(include/linux/reset.h),如下:
/* 通過reset_control_get或者devm_reset_control_get獲得reset句柄 */
struct reset_control *reset_control_get(struct device *dev, const char *id);
void reset_control_put(struct reset_control *rstc);
struct reset_control *devm_reset_control_get(struct device *dev, const char *id);
/* 通過reset_control_reset進行復位,或者通過reset_control_assert使設備處于復位生效狀態,通過reset_control_deassert使復位失效 */
reset_control_deassert(struct reset_control *rstc)//解復位
reset_control_assert(struct reset_control *rstc)//復位
reset_control_reset(struct reset_control *rstc)//先復位,延遲一會,然后解復位
3. provider-reset驅動
3.1 整體介紹
reset驅動是一個獨立驅動,為其他驅動提供硬件復位的服務。首先在dts里面設置.compatible這樣驅動就可以加載了,如下定義了rst驅動:
rst: reset-controller {
compatible = "arch64,a10-reset";
#reset-cells = <2>;
reg = <0x0 0x91000000 0x0 0x1000>;
};
上述是一個reset控制器的節點,0x91000000是寄存器基址,0x1000是映射大小。#reset-cells代表引用該reset時需要的cells個數。
然后就是reset驅動的實現,reset驅動編寫的基本步驟:
- 實現struct reset_control_ops結構體中的.reset、.assert、.deassert、.status函數
- 分配struct reset_controller_dev結構體,填充ops、owner、nr_resets等成員內容
- 調用reset_controller_register函數注冊reset設備
首先定義platform_driver:
static const struct of_device_id a10_reset_dt_ids[] = {
{ .compatible = "hobot,a10-reset", },
{ },
};
static struct platform_driver a10_reset_driver = {
.probe = a10_reset_probe,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = a10_reset_dt_ids,
},
};
static int __init a10_reset_init(void)
{
return platform_driver_register(&a10_reset_driver);
}
系統初始化,dts中配置了此reset驅動,就會調用a10_reset_probe
static int a10_reset_probe(struct platform_device *pdev)
{
struct a10_reset_data *data;
struct resource *res;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
u32 modrst_offset;
/*
* The binding was mainlined without the required property.
* Do not continue, when we encounter an old DT.
*/
if (!of_find_property(pdev->dev.of_node, "#reset-cells", NULL)) {
dev_err(&pdev->dev, "%s missing #reset-cells property
",
pdev->dev.of_node->full_name);
return -EINVAL;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->membase = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->membase))
return PTR_ERR(data->membase);
spin_lock_init(&data->lock);
data->rcdev.owner = THIS_MODULE;
data->rcdev.nr_resets = a10_MAX_NR_RESETS;
data->rcdev.ops = &a10_reset_ops;
data->rcdev.of_node = pdev->dev.of_node;
data->rcdev.of_xlate = a10_reset_of_xlate;
data->rcdev.of_reset_n_cells = 2;
return devm_reset_controller_register(dev, &data->rcdev);
}
data->rcdev的定義如下:
struct reset_controller_dev{
const struct reset_control_ops *ops;//復位控制操作函數
struct list_head list;//全局鏈表,復位控制器注冊后掛載到全局鏈表
struct list_head reset_control_head;//各個模塊復位的鏈表頭
struct device *dev;int of_reset_n_cells;//dts中引用時,需要幾個參數
//通過dts引用的參數,解析復位控制器中相應的參數
int (*of_xlate)(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec);unsigned int nr_resets;//復位設備個數
}
- ops提供reset操作的實現,基本上是reset provider的所有工作量。
- of_xlate和of_reset_n_cells用于解析consumer device dts node中的“resets = ; ”節點,如果reset controller比較簡單(僅僅是線性的索引),可以不實現,使用reset framework提供的簡單版本----of_reset_simple_xlate即可。
- nr_resets,該reset controller所控制的reset信號的個數。
a10_reset_ops定義了reset framework的回調函數,對具體寄存器位進行操作
//reset可控制設備完成一次完整的復位過程。
//assert和deassert分別控制設備reset狀態的生效和失效。
static const struct reset_control_ops a10_reset_ops = {
.assert = a10_reset_assert,
.deassert = a10_reset_deassert,
.status = a10_reset_status,
};
static int a10_reset_assert(struct reset_controller_dev *rcdev,
unsigned long id)
{
void __iomem *regaddr;
uint32_t reg_val, offset;
unsigned long flags;
u8 bit;
struct a10_reset_data *data = to_a10_reset_data(rcdev);
if (rcdev == NULL || id < 0)
return -EINVAL;
spin_lock_irqsave(&data->lock, flags);
offset = (id & RESET_REG_OFFSET_MASK) >> RESET_REG_OFFSET_SHIFT;
regaddr = data->membase + offset;
reg_val = readl(regaddr);
bit = (id & RESET_REG_BIT_MASK);
reg_val |= BIT(bit);
writel(reg_val, regaddr);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int a10_reset_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
{
void __iomem *regaddr;
uint32_t reg_val, offset;
unsigned long flags;
u8 bit;
struct a10_reset_data *data = to_a10_reset_data(rcdev);
if (rcdev == NULL || id < 0)
return -EINVAL;
spin_lock_irqsave(&data->lock, flags);
offset = (id & RESET_REG_OFFSET_MASK) >> RESET_REG_OFFSET_SHIFT;
regaddr = data->membase + offset;
reg_val = readl(regaddr);
bit = (id & RESET_REG_BIT_MASK);
reg_val &= ~(BIT(bit));
writel(reg_val, regaddr);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
static int a10_reset_status(struct reset_controller_dev *rcdev,
unsigned long id)
{
return 0;
}
3.2 reset復位API說明
devm_reset_control_get
struct reset_control *devm_reset_control_get(struct device *dev, const char *id)
?作用:獲取相應的reset句柄
?參數:
? dev:指向申請reset資源的設備句柄
? id:指向要申請的reset資源名(字符串),可以為NULL
?返回:
? 成功:返回reset句柄
? 失敗:返回NULL
reset_control_deassert
int reset_control_deassert(struct reset_control *rstc)
?作用:對傳入的reset資源進行解復位操作
?參數:
? rstc:指向申請reset資源的設備句柄
?返回:
? 成功:返回0
? 失敗:返回錯誤碼
reset_control_assert
int reset_control_assert(struct reset_control *rstc)
?作用:對傳入的reset資源進行復位操作。
參數和返回值與reset_control_deassert相同
reset_control_reset
int reset_control_reset(struct reset_control *rstc)
?作用:對傳入的reset資源先進行復位操作,然后等待5us,再進行解復位操作。
?相當于執行了一遍reset_control_assert后,然后delay一會,再調用reset_control_deassert
后記:
使用markdown寫中文發現段落行首空格實在不好搞,然后調研了很多牛人寫的中文博客發現行首不用空格的很多,咱們這里為了方便書寫,也不要行首空格了。畢竟工具是服務人的,規則都是在變化的。
后續文章先在稀土掘金首發(寫的快),然后復制過來,歡迎關注:https://juejin.cn/user/2052111227697336
電源管理,可能很多人不喜歡看,我分幾次多篇一塊發完。也歡迎大家把喜歡看的技術留言。
電源管理這個專欄其實比較小眾,大伙并不是那么愛看,我就先多寫幾篇存著,到時一塊推送,避免公共資源的浪費,節省點大家的時間。有時候我也劃開微信看看直播和視頻號,發現很多無腦的直播,比如河邊鋼筋磨石頭、在家轉大棍子,什么科目三,感覺這些都有人看,這么無腦,我就算寫點垃圾文字也比這強的吧,也有可能人看視頻就是為了無腦休息下。
-
控制器
+關注
關注
112文章
16332瀏覽量
177806 -
寄存器
+關注
關注
31文章
5336瀏覽量
120230 -
reset
+關注
關注
0文章
34瀏覽量
12877
原文標題:3. provider-reset驅動
文章出處:【微信號:OS與AUTOSAR研究,微信公眾號:OS與AUTOSAR研究】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論