linux 支援32M SPI Flash(W25Q256) — 兼談裝置驅動中的shutdown方法
阿新 • • 發佈:2019-02-01
前言
OpenWrt的最新kernel(3.14.28)已經能夠支援32M SPI Flash的讀寫以及擦除操作.然而,可能是系統考慮不周,亦或是MT7620系統的BUG,在配置了W25Q256的MT7620開發板系統上,無法soft reset!經過查閱相關資料,發現,MT7620預設支援24bit(3byte)的spi地址模式,而要支援32M以上的spi flash,則必須切換到32bit(4byte)地址模式.在soft reset的時候,spi停留在了32bit模式,沒有切換回預設的24bit模式,導致reset後,MT7620在預設的24bit模式,無法和32bit模式的spi通訊,系統宕機.那麼問題來了:如何在soft reset時刻,讓spi flash切換回24bit模式呢?本文通過裝置驅動中的一個shutdown方法來解決這個問題.
背景知識
在linux原始碼kernel目錄下,有一個reboot.c檔案,裡面暴露了一個register_reboot_notifier方法,可以讓kernel中的程式碼有機會獲得reboot的通知,當我們繼續分析reboot.c的程式碼時,會發現更有意思的東西:
在kernel_restart中,又呼叫了kernel_restart_prepare方法:/** * kernel_restart - reboot the system * @cmd: pointer to buffer containing command to execute for restart * or %NULL * * Shutdown everything and perform a clean reboot. * This is not safe to call in interrupt context. */ void kernel_restart(char *cmd) { kernel_restart_prepare(cmd); migrate_to_reboot_cpu(); syscore_shutdown(); if (!cmd) pr_emerg("Restarting system\n"); else pr_emerg("Restarting system with command '%s'\n", cmd); kmsg_dump(KMSG_DUMP_RESTART); machine_restart(cmd); }
void kernel_restart_prepare(char *cmd)
{
blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
system_state = SYSTEM_RESTART;
usermodehelper_disable();
device_shutdown();
}
device_shutdown在drivers/base/core.c中實現:
通過閱讀程式碼,我們不難發現,在device_shutdown中,枚舉了裝置的shutdown方法,如果存在該方法,則會呼叫之./** * device_shutdown - call ->shutdown() on each device to shutdown. */ void device_shutdown(void) { struct device *dev, *parent; spin_lock(&devices_kset->list_lock); /* * Walk the devices list backward, shutting down each in turn. * Beware that device unplug events may also start pulling * devices offline, even as the system is shutting down. */ while (!list_empty(&devices_kset->list)) { dev = list_entry(devices_kset->list.prev, struct device, kobj.entry); /* * hold reference count of device's parent to * prevent it from being freed because parent's * lock is to be held */ parent = get_device(dev->parent); get_device(dev); /* * Make sure the device is off the kset list, in the * event that dev->*->shutdown() doesn't remove it. */ list_del_init(&dev->kobj.entry); spin_unlock(&devices_kset->list_lock); /* hold lock to avoid race with probe/release */ if (parent) device_lock(parent); device_lock(dev); /* Don't allow any more runtime suspends */ pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); // manfeel, add debug info //dev_info(dev,"search shutdown method...\n"); if (dev->bus && dev->bus->shutdown) { //if (initcall_debug) manfeel dev_info(dev, "shutdown\n"); dev->bus->shutdown(dev); } else if (dev->driver && dev->driver->shutdown) { //if (initcall_debug) manfeel dev_info(dev, "shutdown\n"); dev->driver->shutdown(dev); } device_unlock(dev); if (parent) device_unlock(parent); put_device(dev); put_device(parent); spin_lock(&devices_kset->list_lock); } spin_unlock(&devices_kset->list_lock); async_synchronize_full(); }
於是,32M spi flash的reset方法噴薄而出.
解決辦法
轉到drivers/mtd/devices/m25p80.c
修改如下程式碼:
static int m25p_remove(struct spi_device *spi)
{
struct m25p *flash = spi_get_drvdata(spi);
// manfeel note: add spi flash reset code
flash->command[0] = 0x66;
spi_write(flash->spi, flash->command, 1);
flash->command[0] = 0x99;
spi_write(flash->spi, flash->command, 1);
/* Clean up MTD stuff. */
return mtd_device_unregister(&flash->mtd);
}
static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80",
.owner = THIS_MODULE,
},
.id_table = m25p_ids,
.probe = m25p_probe,
.remove = m25p_remove,
// manfeel, add shutdown method to reset spi flash
.shutdown = m25p_remove,
/* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
};
總結
通過註冊裝置的shutdown方法,讓我們有機會在系統重啟的時刻,做一些deinit的操作.通過此種方法來複位spi flash,優雅而簡潔.