二  3c2440_nand_probe

如果你没有意见.我们开始进入3c2440_nand_probe.这人函数可是干活的家伙.故事就是从这里开始的.

static int s3c2440_nand_probe(struct platform_device *dev)

{

       return s3c24xx_nand_probe(dev, TYPE_S3C2440);

}

static int s3c24xx_nand_probe(struct platform_device *pdev,

                           enum s3c_cpu_type cpu_type)

{

       struct s3c2410_platform_nand *plat = to_nand_plat(pdev);

       struct s3c2410_nand_info *info;

       struct s3c2410_nand_mtd *nmtd;

       struct s3c2410_nand_set *sets;

       struct resource *res;

       int err = 0;

       int size;

       int nr_sets;

       int setno;

 

       pr_debug("s3c2410_nand_probe(%p)/n", pdev);

 

       info = kmalloc(sizeof(*info), GFP_KERNEL);

/*

   

*/

       if (info == NULL) {

       dev_err(&pdev->dev, "no memory for flash info/n");

       err = -ENOMEM;

       goto exit_error;

       }

 

       memzero(info, sizeof(*info));

 

      

       platform_set_drvdata(pdev, info);

 

       spin_lock_init(&info->controller.lock);

       init_waitqueue_head(&info->controller.wq);

 

       /* get the clock source and enable it */

 

       info->clk = clk_get(&pdev->dev, "nand");

       if (IS_ERR(info->clk)) {

       dev_err(&pdev->dev, "failed to get clock/n");

       err = -ENOENT;

       goto exit_error;

       }

 

       clk_enable(info->clk);

 

       /* allocate and map the resource */

 

       /* currently we assume we have the one resource */

       res  = pdev->resource;

       size = res->end - res->start + 1;

/

       info->area = request_mem_region(res->start, size, pdev->name);

 

       if (info->area == NULL) {

       dev_err(&pdev->dev, "cannot reserve register region/n");

       err = -ENOENT;

       goto exit_error;

       }

 

       info->device     = &pdev->dev;

       info->platform   = plat;

       info->regs       = ioremap(res->start, size);

       info->cpu_type   = cpu_type;

 

       if (info->regs == NULL) {

       dev_err(&pdev->dev, "cannot reserve register region/n");

       err = -EIO;

       goto exit_error;

       }

 

       dev_dbg(&pdev->dev, "mapped registers at %p/n", info->regs);

 

       /* initialise the hardware */

 

       err = s3c2410_nand_inithw(info, pdev);

       if (err != 0)

              goto exit_error;

       sets = (plat != NULL) ? plat->sets : NULL;

       nr_sets = (plat != NULL) ? plat->nr_sets : 1;

 

       info->mtd_count = nr_sets;

 

       /* allocate our information */

 

       size = nr_sets * sizeof(*info->mtds);

       info->mtds = kmalloc(size, GFP_KERNEL);

       if (info->mtds == NULL) {

       dev_err(&pdev->dev, "failed to allocate mtd storage/n");

       err = -ENOMEM;

       goto exit_error;

       }

 

       memzero(info->mtds, size);

 

       /* initialise all possible chips */

 

       nmtd = info->mtds;

 

       for (setno = 0; setno < nr_sets; setno++, nmtd++) {

       pr_debug("initialising set %d (%p, info %p)/n", setno, nmtd, info);

 

       s3c2410_nand_init_chip(info, nmtd, sets);

 

       nmtd->scan_res = nand_scan_ident(&nmtd->mtd,

                                           (sets) ? sets->nr_chips : 1);

 

       if (nmtd->scan_res == 0) {

       s3c2410_nand_update_chip(info, nmtd);

       nand_scan_tail(&nmtd->mtd);

       s3c2410_nand_add_partition(info, nmtd, sets);

       }

 

       if (sets != NULL)

       sets++;

       }

 

       if (allow_clk_stop(info)) {

       dev_info(&pdev->dev, "clock idle support enabled/n");

       clk_disable(info->clk);

       }

 

       pr_debug("initialised ok/n");

       return 0;

 

 exit_error:

       s3c2410_nand_remove(pdev);

 

       if (err == 0)

              err = -EINVAL;

       return err;

}    

好家伙.那么长.吓人一大跳.还好很多函数都是明白的. Plat就当初我们说的

static struct s3c2410_platform_nand smdk_nand_info = {

       .tacls              = 20,

       .twrph0          = 60,

       .twrph1          = 20,

       .nr_sets   = ARRAY_SIZE(smdk_nand_sets),

       .sets        = smdk_nand_sets,

};

info = kmalloc(sizeof(*info), GFP_KERNEL);为info 分配内存.

platform_set_drvdata(pdev, info);pdev与info关联.

这些都是比较统一函数结构.

来看下这个函数: s3c2410_nand_inithw

static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,

                            struct platform_device *pdev)

{

       struct s3c2410_platform_nand *plat = to_nand_plat(pdev);

       unsigned long clkrate = clk_get_rate(info->clk);

       int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;

       int tacls, twrph0, twrph1;

       unsigned long cfg = 0;

 

       /* calculate the timing information for the controller */

 

       clkrate /= 1000;      /* turn clock into kHz for ease of use */

if (plat != NULL) {

              tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);

              twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);

              twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);

       } else {

              /* default timings */

              tacls = tacls_max;

              twrph0 = 8;

              twrph1 = 8;

       }

 

       if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {

              dev_err(info->device, "cannot get suitable timings/n");

              return -EINVAL;

       }

 

       dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns/n",

              tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));

 

      switch (info->cpu_type) {

      case TYPE_S3C2410:

              cfg = S3C2410_NFCONF_EN;

              cfg |= S3C2410_NFCONF_TACLS(tacls - 1);

              cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);

              cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);

              break;

 

      case TYPE_S3C2440:

      case TYPE_S3C2412:

              cfg = S3C2440_NFCONF_TACLS(tacls - 1);

              cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);

              cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);

 

              /* enable the controller and de-assert nFCE */

 

              writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);

       }

 

       dev_dbg(info->device, "NF_CONF is 0x%lx/n", cfg);

 

       writel(cfg, info->regs + S3C2410_NFCONF);

       return 0;

}

这个函数是总线的设定.如何设定呢?先来说下几个数据.

 

 

 

 

Tacls是当CLE/ALE有效时过了多少时间后nwe才有效.

TWRPH0是nwe的有效时间.

TWRPH1是当nWE无效后DATA的保持时间.

这里tcs,twp,tclh就是我们要的

       .tacls              = 20,

       .twrph0          = 60,

       .twrph1          = 20,

注意单位是ns.

好回到我们的s3c2410_nand_inithw中来.

tacls_max是怎么算的呢?还不是S3C2440 的TACLS只占了二位.二位就是4. =====真叨

clkrate 是NAND的源时钟./1000把它转换成KHZ

plat是不为NULL的,于是

#define NS_IN_KHZ 1000000

 

static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)

{

       int result;

 

       result = (wanted * clk) / NS_IN_KHZ;

       result++;

 

       pr_debug("result %d from %ld, %d/n", result, clk, wanted);

 

       if (result > max) {

              printk("%d ns is too big for current clock rate %ld/n", wanted, clk);

              return -1;

       }

 

       if (result < 1)

              result = 1;

 

       return result;

}

这里怎么算呢.举个例子:(1/HZ)*n=20ns

于是n=HZ*(20ns)由于这里的单位是ns 所于除了NS_IN_KHZ纯数学问题very simple.

Result 算出来以后就返回了.

好返回s3c2410_nand_inithw中来:

twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);

twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);

这两个也是一样的.

来看一下switch(info->cpu_type)这个句子

 

       case TYPE_S3C2440:

      case TYPE_S3C2412:

              cfg = S3C2440_NFCONF_TACLS(tacls - 1);

              cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);

              cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);

这里为什么减一呢.原来是这样的

 

算的时候它自动加1了.三星的东西很多都是这样的.这里算的时候也不是很严的时间限制.有没有注意到上面result++.不管怎么样误差不是太大就行了.

writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);

#define S3C2440_NFCONT_ENABLE         (1<<0)

NFCONF有个开始/禁止控制位.这里开启使nand控制器跑起来.

s3c2410_nand_inithw完了.返回到s3c24xx_nand_probe中来.

sets = (plat != NULL) ? plat->sets : NULL;

nr_sets = (plat != NULL) ? plat->nr_sets : 1;

set就是开始那个.

set _sets 就是1,表示我只有一块NAND.

 

805行分配一个mtd. Info是这么样的一个结构体.

struct s3c2410_nand_info {

       /* mtd info */

       struct nand_hw_control        controller;

       struct s3c2410_nand_mtd            *mtds;

       struct s3c2410_platform_nand      *platform;

 

       /* device info */

       struct device                 *device;

       struct resource                     *area;

       struct clk               *clk;

       void __iomem               *regs;

       void __iomem               *sel_reg;

       int                         sel_bit;

       int                         mtd_count;

       unsigned long                save_sel;

 

       enum s3c_cpu_type              cpu_type;

};

struct s3c2410_nand_mtd {

       struct mtd_info                    mtd;

       struct nand_chip           chip;

       struct s3c2410_nand_set              *set;

       struct s3c2410_nand_info     *info;

       int                         scan_res;

};

 

其中mtds里面的mtd就表示NAND.它表示所有分区的master.如果没有分区的话.这个mtd就会添加到分区表中去.如果有分区.则不会添加到分区中.而是作为所有分区的master.

816 行nmtd指向我们刚才分配的mtds

 

到了818行.那个for只会循环一次.因为我们的nr_sets是1的.

进入s3c2410_nand_init_chip 一段一段来:

 

 

602行使chip指向nmtd内的chip.chip就表示一块芯片.这是高一层的结构体.

/**

 * struct nand_chip - NAND Private Flash Chip Data

 * @IO_ADDR_R:        [BOARDSPECIFIC] address to read the 8 I/O lines of the flash device

 * @IO_ADDR_W:              [BOARDSPECIFIC] address to write the 8 I/O lines of the flash device

 * @read_byte:             [REPLACEABLE] read one byte from the chip

 * @read_word:           [REPLACEABLE] read one word from the chip

 * @write_buf:             [REPLACEABLE] write data from the buffer to the chip

 * @read_buf:              [REPLACEABLE] read data from the chip into the buffer

 * @verify_buf:            [REPLACEABLE] verify buffer contents against the chip data

 * @select_chip:    [REPLACEABLE] select chip nr

 * @block_bad:            [REPLACEABLE] check, if the block is bad

 * @block_markbad:     [REPLACEABLE] mark the block bad

 * @cmd_ctrl:              [BOARDSPECIFIC] hardwarespecific funtion for controlling

 *                  ALE/CLE/nCE. Also used to write command and address

 * @dev_ready:            [BOARDSPECIFIC] hardwarespecific function for accesing device ready/busy line

 *                  If set to NULL no access to ready/busy is available and the ready/busy information

 *                  is read from the chip status register

 * @cmdfunc:              [REPLACEABLE] hardwarespecific function for writing commands to the chip

 * @waitfunc:              [REPLACEABLE] hardwarespecific function for wait on ready

 * @ecc:        [BOARDSPECIFIC] ecc control ctructure

 * @buffers:         buffer structure for read/write

 * @hwcontrol:            platform-specific hardware control structure

 * @ops:        oob operation operands

 * @erase_cmd:           [INTERN] erase command write function, selectable due to AND support

 * @scan_bbt:              [REPLACEABLE] function to scan bad block table

 * @chip_delay:            [BOARDSPECIFIC] chip dependent delay for transfering data from array to read regs (tR)

 * @wq:               [INTERN] wait queue to sleep on if a NAND operation is in progress

 * @state:             [INTERN] the current state of the NAND device

 * @oob_poi:        poison value buffer

 * @page_shift:            [INTERN] number of address bits in a page (column address bits)

 * @phys_erase_shift:   [INTERN] number of address bits in a physical eraseblock

 * @bbt_erase_shift:     [INTERN] number of address bits in a bbt entry

 * @chip_shift:             [INTERN] number of address bits in one chip

 * @datbuf:           [INTERN] internal buffer for one page + oob

 * @oobbuf:          [INTERN] oob buffer for one eraseblock

 * @oobdirty:        [INTERN] indicates that oob_buf must be reinitialized

 * @data_poi:        [INTERN] pointer to a data buffer

 * @options:         [BOARDSPECIFIC] various chip options. They can partly be set to inform nand_scan about

 *                  special functionality. See the defines for further explanation

 * @badblockpos:  [INTERN] position of the bad block marker in the oob area

 * @cellinfo:         [INTERN] MLC/multichip data from chip ident

 * @numchips:             [INTERN] number of physical chips

 * @chipsize:        [INTERN] the size of one chip for multichip arrays

 * @pagemask:             [INTERN] page number mask = number of (pages / chip) - 1

 * @pagebuf:        [INTERN] holds the pagenumber which is currently in data_buf

 * @subpagesize:   [INTERN] holds the subpagesize

 * @ecclayout:             [REPLACEABLE] the default ecc placement scheme

 * @bbt:        [INTERN] bad block table pointer

 * @bbt_td:           [REPLACEABLE] bad block table descriptor for flash lookup

 * @bbt_md:         [REPLACEABLE] bad block table mirror descriptor

 * @badblock_pattern:  [REPLACEABLE] bad block scan pattern used for initial bad block scan

 * @controller:             [REPLACEABLE] a pointer to a hardware controller structure

 *                  which is shared among multiple independend devices

 * @priv:              [OPTIONAL] pointer to private chip date

 * @errstat:           [OPTIONAL] hardware specific function to perform additional error status checks

 *                  (determine if errors are correctable)

 * @write_page:           [REPLACEABLE] High-level page write function

 */

 

struct nand_chip {

          //数据写地址

       void  __iomem      *IO_ADDR_R;

       //数据读地址

       void  __iomem      *IO_ADDR_W;

 

       uint8_t    (*read_byte)(struct mtd_info *mtd);

      

       u16         (*read_word)(struct mtd_info *mtd);

       ///

       void        (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);

       void        (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);

       int           (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);

       void        (*select_chip)(struct mtd_info *mtd, int chip);

       int           (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);

       int           (*block_markbad)(struct mtd_info *mtd, loff_t ofs);

       ///

       void        (*cmd_ctrl)(struct mtd_info *mtd, int dat,

                                unsigned int ctrl);

       //命令-----数据地址命令

       int           (*dev_ready)(struct mtd_info *mtd);

       void        (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);

       int           (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);

       void        (*erase_cmd)(struct mtd_info *mtd, int page);

       int           (*scan_bbt)(struct mtd_info *mtd);

       int           (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);

       int           (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,

                                  const uint8_t *buf, int page, int cached, int raw);

 

       int           chip_delay;

       unsigned int    options;

 

       int           page_shift;

       int           phys_erase_shift;

       int           bbt_erase_shift;

       int           chip_shift;

       int           numchips;

       unsigned long  chipsize;

       int           pagemask;

       int           pagebuf;

       int           subpagesize;

       uint8_t           cellinfo;

       int           badblockpos;

 

       nand_state_t   state;

 

       uint8_t           *oob_poi;

       struct nand_hw_control  *controller;

       struct nand_ecclayout    *ecclayout;

 

       struct nand_ecc_ctrl ecc;

       struct nand_buffers *buffers;

       struct nand_hw_control hwcontrol;

 

       struct mtd_oob_ops ops;

 

       uint8_t           *bbt;

       struct nand_bbt_descr   *bbt_td;

       struct nand_bbt_descr   *bbt_md;

 

       struct nand_bbt_descr   *badblock_pattern;

 

       void        *priv;

};

如果不是在大学混了三年.看到这样的结构休.我老早就溜了.

还好俺也算知识混子

605到612对chip初始化.这些值用到的时候我们再来说.我会时时提醒你.

624行IO_ADDR_W 写地址NFDATA. 三星规定了写数据就住这里写.

info->sel_reg   = regs + S3C2410_NFCONF;

info->sel_bit   = S3C2410_NFCONF_nFCE;

chip->cmd_ctrl  = s3c2410_nand_hwcontrol;

chip->dev_ready = s3c2410_nand_devready;

NFCONT有这么样一个位:

 

 

为0表示Enable chip select

s3c2410_nand_select_chip中我们会用上的.待得瞧.

回来看s3c2410_nand_init_chip:

chip->IO_ADDR_R = chip->IO_ADDR_W;

 

nmtd->info        = info;

nmtd->mtd.priv         = chip;

nmtd->mtd.owner    = THIS_MODULE;

nmtd->set         = set;

读地址与写地址是一样.

传说中的ECC出来了.

static int hardware_ecc = 1;表示要ECC.什么是ECC:就是对数据的保护.数据传送有没有出错.ECC有两种.一种是通过软件计算出的.一种是通过硬件算出来的.拿S3C2440来说它的ECC是硬件算出来的.怎么算????当写数据时:例如写512个数据到NAND中,当512写完以后ECC就会被算出,放在NFMCCD0-NFMCCD2中.这个过程是全自动的.

 

但有人就会问生成以后呢.??有没有注意到上面.拿512来说其实 1 page=528

多了16个.ECC就是放在这16个当中的.

NAND_ECC_SOFT表示软件生成ECC.

683行set->disable_ecc如果为1就表示是hi ECC你不用检测了..

我们顺便把s3c2410_nand_calculate_ecc也看了.

static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)

{

       struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);

 

       ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0);

       ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);

       ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);

 

       pr_debug("%s: returning ecc %02x%02x%02x/n", __func__,

               ecc_code[0], ecc_code[1], ecc_code[2]);

 

       return 0;

}

这个函数就是读一下ECC寄存器.

s3c2410_nand_init_chip就这样完了.但chip还没有完.回到s3c24xx_nand_probe中来

823行调用nand_scan_ident这个函数可不是打杂的.

 

 

arm-linux东东之nand之2:3c2440_nand_probe相关推荐

  1. arm-linux东东之nand

    <p></p> <div> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;&q ...

  2. arm linux kernel 从入口到start_kernel 的代码分析

    Linux系统启动过程分析(主要是加载内核前的动作) 经过对Linux系统有了一定了解和熟悉后,想对其更深层次的东西做进一步探究.这当中就包括系统的启动流程.文件系统的组成结构.基于动态库和静态库的程 ...

  3. linux java web.pdf,Java Web应用在ARM Linux平台上的实现.pdf

    Java Web应用在ARM Linux平台上的实现.pdf lSSN1009-3044 and KnowledgeTechnology电■知识与技术 Computer l-5690963 V01.5 ...

  4. ARM Linux 3.x的设备树(Device Tree)【转】

    转自:http://blog.csdn.net/21cnbao/article/details/8457546 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ARM Devi ...

  5. ARM Linux 3.x的设备树(Device Tree)

    宋宝华 Barry Song <21cnbao@gmail.com> 1.    ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux ...

  6. arm linux 脚本 排序,arm-linux连接以及连接脚本

    前言:arm linux的连接工具可以使用arm-linux-ld,在进行连接时可以使用-T命令采用脚本控制,如不指明脚本,则使用默认的脚本文件,参见arm-linux-ld的缺省linker scr ...

  7. 刀片服务器改台式电脑_服务器到底是个什么东东?跟电脑有啥区别?电脑知识学习!...

    一位朋友留言点的内容,想了解服务器方面的知识,对于普通用户而言,确实对服务器感觉很神秘,不知道服务器到底是个什么东东,我保证看完这篇,你就会明白服务器到底是个啥了. 首先可以很明确的告诉你,服务器也是 ...

  8. ARM Linux 3.x的设备树

    2019独角兽企业重金招聘Python工程师标准>>> 转自:http://blog.csdn.net/21cnbao/article/details/8457546 本文部分案例和 ...

  9. 服务器到底是个什么东东?跟电脑有啥区别?

    一位朋友留言点的内容,想了解服务器方面的知识,对于普通用户而言,确实对服务器感觉很神秘,不知道服务器到底是个什么东东,我保证看完这篇,你就会明白服务器到底是个啥了. 首先可以很明确的告诉你,服务器也是 ...

  10. 嵌入式linux内核启动过程,嵌入式Linux:ARM Linux启动流程

    ARM Linux启动流程大致为:bootloader---->kernel---->root filesystem.bootloader 是一上电就拿到cpu 的控制权的,而bootlo ...

最新文章

  1. ActiveMQ学习(七)
  2. MySQL协议.NET Core实现(一)
  3. 红帽linux lnmp搭建,Linux(redhat5.4)下lnmp环境的搭建
  4. 线框图(demo草图)制作的总结
  5. 查全率[召回率]与精度[查准率] 之辨析
  6. 怎么让e-charts折线图只有6个刻度_简单6步,打开图表定制之门
  7. 一个简单的GridView分页通用程序
  8. 支付宝芝麻分多少算正常?分高有什么好处?
  9. easyui下拉选项多怎么解决_作物根部病害多原因在哪?解决病害生根措施怎么做?...
  10. mysql oracle 左链接_mysql左连接与oracle(+)使用对照
  11. UVa 455 - Periodic Strings
  12. go语言构造函数的创建以及赋值使用
  13. android获取手机号码的归属地以及运营商,本地查询
  14. 五星大饭店完整剧情,五星大饭店(完整集数)在线观看
  15. Anaconda多环境python管理(创建、删除、复制环境)
  16. java 将pdf文件转成高清图片(多张合并成一张)
  17. 使用Quantlib,通过YTM计算债券净值
  18. 计算机 最后 一次 开机时间 win 7,Win7如何每次开机都显示上次登录时间?开机显示上次开机时间方法...
  19. unity 转微信小游戏 资源优化
  20. ble4.2空口包详解(air interface packets)

热门文章

  1. 解决,微信网页开发,网页授权域名数量不足问题
  2. 为什么IPv6显示无网络访问权限
  3. win7 计算机名称 ip6,Win7系统为什么会出现IPV6无网络访问权限?
  4. UAP扩展开发 - 新增按钮
  5. 提供4款WEB网页游戏源码下载,亲测绝对可以用
  6. 好好学习,天天向上——“C”
  7. ruoyi是怎么点击菜单跳转页面的_5分钟添加公众号报名功能: 点击公众号菜单报名...
  8. 鲁泰纺织:在行业整合中稳健前行
  9. Android 解压zip文件你知道多少?
  10. 【Mac新手必看】Desktop Picture壁纸文件夹找不到怎么办?苹果壁纸设置教程