| /* |
| * drivers/amlogic/mtd/mtd_driver.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| */ |
| |
| #include "aml_mtd.h" |
| |
| /* extern int nand_get_device( struct mtd_info *mtd, int new_state); */ |
| /* extern void nand_release_device(struct mtd_info *mtd); */ |
| |
| #define nand_notifier_to_blk(l) container_of(l, struct aml_nand_device, nb) |
| |
| static int mtd_nand_reboot_notifier(struct notifier_block *nb, |
| unsigned long priority, void *arg) |
| { |
| int error = 0; |
| struct aml_nand_device *aml_nand_dev = nand_notifier_to_blk(nb); |
| struct aml_nand_platform *plat = NULL; |
| struct aml_nand_chip *aml_chip = NULL; |
| struct mtd_info *mtd = NULL; |
| int i; |
| |
| pr_info("%s %d\n", __func__, __LINE__); |
| for (i = 1; i < aml_nand_dev->dev_num; i++) { |
| plat = &aml_nand_dev->aml_nand_platform[i]; |
| aml_chip = plat->aml_chip; |
| if (aml_chip) { |
| mtd = aml_chip->mtd; |
| /*fixit! nothing*/ |
| } |
| } |
| |
| return error; |
| } |
| |
| static const struct of_device_id amlogic_nand_dt_match[] = { |
| { |
| .compatible = "amlogic, aml_mtd_nand", |
| .data = (void *)&aml_nand_mid_device, |
| }, |
| {}, |
| }; |
| |
| static inline struct aml_nand_device |
| *aml_get_driver_data(struct platform_device *pdev) |
| { |
| if (pdev->dev.of_node) { |
| const struct of_device_id *match; |
| |
| match = of_match_node(amlogic_nand_dt_match, pdev->dev.of_node); |
| return (struct aml_nand_device *)match->data; |
| } |
| return NULL; |
| } |
| |
| static int amlogic_parse_nand_partion(struct device_node *np, |
| struct aml_nand_platform *plat) |
| { |
| struct mtd_partition *mtd_ptn = NULL; |
| struct mtd_partition *p = NULL; |
| struct device_node *child; |
| unsigned int part_num = 0; |
| |
| child = of_get_next_child(np, NULL); |
| if (!child) { |
| pr_info("no partition\n"); |
| goto err; |
| } |
| part_num = of_get_child_count(np); |
| if (part_num > 0) { |
| mtd_ptn = kcalloc(part_num, sizeof(struct mtd_partition), |
| GFP_KERNEL); |
| p = mtd_ptn; |
| if (!mtd_ptn) { |
| pr_info("malloc mtd_partition error\n"); |
| goto err; |
| } |
| memset(p, 0, sizeof(struct mtd_partition) * part_num); |
| for_each_child_of_node(np, child) { |
| p->name = (char *)child->name; |
| if (of_property_read_u64(child, "offset", &p->offset)) |
| goto err; |
| if (of_property_read_u64(child, "size", &p->size)) |
| goto err; |
| p++; |
| } |
| plat->platform_nand_data.chip.partitions = mtd_ptn; |
| plat->platform_nand_data.chip.nr_partitions = part_num; |
| return 0; |
| } |
| err: |
| if (mtd_ptn) |
| kzfree(mtd_ptn); |
| return -1; |
| } |
| |
| static int prase_get_dtb_nand_parameter(struct aml_nand_device *aml_nand_dev, |
| struct platform_device *pdev) |
| { |
| const char *propname; |
| struct property *prop; |
| const __be32 *list; |
| int size, config, index; |
| phandle phandle; |
| struct device_node *np_config; |
| struct device_node *np_part; |
| struct device_node *np = pdev->dev.of_node; |
| struct aml_nand_platform *plat = NULL; |
| struct aml_nand_platform *plat_array = NULL; |
| int val = 0, ret; |
| |
| pr_info("%s:%d,parse dts start\n", __func__, __LINE__); |
| if (pdev->dev.of_node) { |
| of_node_get(np); |
| ret = of_property_read_u32(np, |
| "plat-num", (u32 *)&aml_nand_dev->dev_num); |
| if (ret) { |
| pr_info("%s:%d,please config plat-num item\n", |
| __func__, __LINE__); |
| return -1; |
| } |
| if (aml_nand_dev->dev_num > 0) { |
| plat_array = |
| kzalloc(sizeof(*plat_array) * aml_nand_dev->dev_num, |
| GFP_KERNEL); |
| if (!plat_array) |
| return -1; |
| aml_nand_dev->aml_nand_platform = plat_array; |
| } |
| |
| for (index = 0; index < aml_nand_dev->dev_num; index++) { |
| plat = &aml_nand_dev->aml_nand_platform[index]; |
| if (plat == NULL) { |
| pr_info("%s:%d, kzalloc mem fail\n", |
| __func__, __LINE__); |
| continue; |
| } |
| propname = kasprintf(GFP_KERNEL, "plat-part-%d", index); |
| prop = of_find_property(np, propname, &size); |
| kfree(propname); |
| if (!prop) |
| break; |
| list = prop->value; |
| size /= sizeof(*list); |
| ret = of_property_read_string_index(np, |
| "plat-names", |
| index, (const char **)&plat->name); |
| if (ret < 0) |
| plat->name = prop->name + 8; |
| |
| for (config = 0; config < size; config++) { |
| phandle = be32_to_cpup(list++); |
| np_config = of_find_node_by_phandle(phandle); |
| if (!np_config) { |
| pr_info("%s:%d,not find device node\n", |
| __func__, __LINE__); |
| goto err; |
| } |
| /* |
| *ret = of_property_read_u32(np_config, |
| * "part_num", |
| * &plat->platform_nand_data.chip.nr_partitions); |
| **/ |
| ret = of_property_read_u32(np_config, |
| "partition", &val); |
| if (ret == 0) { |
| phandle = val; |
| np_part = |
| of_find_node_by_phandle(phandle); |
| if (!np_config) { |
| pr_info("%s:%d not find device node\n", |
| __func__, __LINE__); |
| goto err; |
| } |
| ret = amlogic_parse_nand_partion(np_part, plat); |
| if (ret) { |
| pr_info("%s:%d,prase part table fail\n", |
| __func__, __LINE__); |
| goto err; |
| } |
| } |
| of_node_put(np_config); |
| } |
| } |
| }; |
| pr_info("%s:%d,parse dts end\n", __func__, __LINE__); |
| return 0; |
| err: |
| return -1; |
| } |
| |
| static int mtd_nand_probe(struct platform_device *pdev) |
| { |
| struct aml_nand_device *aml_nand_dev = NULL; |
| int err = 0; |
| |
| aml_nand_dev = aml_get_driver_data(pdev); |
| pdev->dev.platform_data = aml_nand_dev; |
| |
| if (!aml_nand_dev) { |
| dev_err(&pdev->dev, "no platform specific information\n"); |
| err = -ENOMEM; |
| goto exit_error; |
| } |
| |
| platform_set_drvdata(pdev, aml_nand_dev); |
| |
| spin_lock_init(&upper_controller.lock); |
| init_waitqueue_head(&upper_controller.wq); |
| |
| aml_nand_dev->nb.notifier_call = mtd_nand_reboot_notifier; |
| register_reboot_notifier(&aml_nand_dev->nb); |
| atomic_notifier_chain_register(&panic_notifier_list, &aml_nand_dev->nb); |
| |
| /*prase dtb and get device(part) information*/ |
| prase_get_dtb_nand_parameter(aml_nand_dev, pdev); |
| |
| nand_init(pdev); |
| |
| exit_error: |
| return err; |
| } |
| |
| static int mtd_nand_remove(struct platform_device *pdev) |
| { |
| struct aml_nand_device *aml_nand_dev = aml_get_driver_data(pdev); |
| struct aml_nand_platform *plat = NULL; |
| struct aml_nand_chip *aml_chip = NULL; |
| struct mtd_info *mtd = NULL; |
| int i; |
| |
| platform_set_drvdata(pdev, NULL); |
| for (i = 0; i < aml_nand_dev->dev_num; i++) { |
| plat = &aml_nand_dev->aml_nand_platform[i]; |
| aml_chip = plat->aml_chip; |
| if (aml_chip) { |
| mtd = aml_chip->mtd; |
| if (mtd) { |
| nand_release(mtd_to_nand(mtd)); |
| kfree(mtd); |
| } |
| kfree(aml_chip); |
| } |
| } |
| if (pdev->dev.of_node) { |
| if (aml_nand_dev->aml_nand_platform) { |
| for (i = 0; i < aml_nand_dev->dev_num; i++) |
| plat = &aml_nand_dev->aml_nand_platform[i]; |
| kfree(aml_nand_dev->aml_nand_platform); |
| } |
| of_node_put(pdev->dev.of_node); |
| } |
| return 0; |
| } |
| |
| static void mtd_nand_shutdown(struct platform_device *pdev) |
| { |
| /*NULL*/ |
| } |
| |
| /* driver device registration */ |
| static struct platform_driver mtd_nand_driver = { |
| .probe = mtd_nand_probe, |
| .remove = mtd_nand_remove, |
| .shutdown = mtd_nand_shutdown, |
| .driver = { |
| .name = "aml_mtd_nand", |
| .owner = THIS_MODULE, |
| .of_match_table = amlogic_nand_dt_match, |
| }, |
| }; |
| |
| static int __init mtd_nand_init(void) |
| { |
| pr_info("amlogic mtd driver init\n"); |
| return platform_driver_register(&mtd_nand_driver); |
| } |
| |
| static void __exit mtd_nand_exit(void) |
| { |
| pr_info("amlogic mtd driver exit\n"); |
| platform_driver_unregister(&mtd_nand_driver); |
| } |
| |
| module_init(mtd_nand_init); |
| module_exit(mtd_nand_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("AML NAND TEAM"); |
| MODULE_DESCRIPTION("aml mtd nand flash driver"); |
| |