linux/drivers/staging/msm/mdp_dma.c
<<
>>
Prefs
   1/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved.
   2 *
   3 * This program is free software; you can redistribute it and/or modify
   4 * it under the terms of the GNU General Public License version 2 and
   5 * only version 2 as published by the Free Software Foundation.
   6 *
   7 * This program is distributed in the hope that it will be useful,
   8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10 * GNU General Public License for more details.
  11 *
  12 * You should have received a copy of the GNU General Public License
  13 * along with this program; if not, write to the Free Software
  14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  15 * 02110-1301, USA.
  16 */
  17
  18#include <linux/module.h>
  19#include <linux/kernel.h>
  20#include <linux/sched.h>
  21#include <linux/time.h>
  22#include <linux/init.h>
  23#include <linux/interrupt.h>
  24#include <linux/hrtimer.h>
  25
  26#include <mach/hardware.h>
  27#include <linux/io.h>
  28
  29#include <asm/system.h>
  30#include <asm/mach-types.h>
  31#include <linux/semaphore.h>
  32#include <linux/spinlock.h>
  33
  34#include <linux/fb.h>
  35
  36#include "mdp.h"
  37#include "msm_fb.h"
  38#include "mddihost.h"
  39
  40static uint32 mdp_last_dma2_update_width;
  41static uint32 mdp_last_dma2_update_height;
  42static uint32 mdp_curr_dma2_update_width;
  43static uint32 mdp_curr_dma2_update_height;
  44
  45ktime_t mdp_dma2_last_update_time = { 0 };
  46
  47int mdp_lcd_rd_cnt_offset_slow = 20;
  48int mdp_lcd_rd_cnt_offset_fast = 20;
  49int mdp_vsync_usec_wait_line_too_short = 5;
  50uint32 mdp_dma2_update_time_in_usec;
  51uint32 mdp_total_vdopkts;
  52
  53extern u32 msm_fb_debug_enabled;
  54extern struct workqueue_struct *mdp_dma_wq;
  55
  56int vsync_start_y_adjust = 4;
  57
  58static void mdp_dma2_update_lcd(struct msm_fb_data_type *mfd)
  59{
  60        MDPIBUF *iBuf = &mfd->ibuf;
  61        int mddi_dest = FALSE;
  62        uint32 outBpp = iBuf->bpp;
  63        uint32 dma2_cfg_reg;
  64        uint8 *src;
  65        uint32 mddi_ld_param;
  66        uint16 mddi_vdo_packet_reg;
  67        struct msm_fb_panel_data *pdata =
  68            (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data;
  69        uint32 ystride = mfd->fbi->fix.line_length;
  70
  71        dma2_cfg_reg = DMA_PACK_TIGHT | DMA_PACK_ALIGN_LSB |
  72            DMA_OUT_SEL_AHB | DMA_IBUF_NONCONTIGUOUS;
  73
  74#ifdef CONFIG_FB_MSM_MDP30
  75        /*
  76         * Software workaround:  On 7x25/7x27, the MDP will not
  77         * respond if dma_w is 1 pixel.  Set the update width to
  78         * 2 pixels and adjust the x offset if needed.
  79         */
  80        if (iBuf->dma_w == 1) {
  81                iBuf->dma_w = 2;
  82                if (iBuf->dma_x == (iBuf->ibuf_width - 2))
  83                        iBuf->dma_x--;
  84        }
  85#endif
  86
  87        if (mfd->fb_imgType == MDP_BGR_565)
  88                dma2_cfg_reg |= DMA_PACK_PATTERN_BGR;
  89        else
  90                dma2_cfg_reg |= DMA_PACK_PATTERN_RGB;
  91
  92        if (outBpp == 4)
  93                dma2_cfg_reg |= DMA_IBUF_C3ALPHA_EN;
  94
  95        if (outBpp == 2)
  96                dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB565;
  97
  98        mddi_ld_param = 0;
  99        mddi_vdo_packet_reg = mfd->panel_info.mddi.vdopkt;
 100
 101        if ((mfd->panel_info.type == MDDI_PANEL) ||
 102            (mfd->panel_info.type == EXT_MDDI_PANEL)) {
 103                dma2_cfg_reg |= DMA_OUT_SEL_MDDI;
 104                mddi_dest = TRUE;
 105
 106                if (mfd->panel_info.type == MDDI_PANEL) {
 107                        mdp_total_vdopkts++;
 108                        if (mfd->panel_info.pdest == DISPLAY_1) {
 109                                dma2_cfg_reg |= DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY;
 110                                mddi_ld_param = 0;
 111#ifdef MDDI_HOST_WINDOW_WORKAROUND
 112                                mddi_window_adjust(mfd, iBuf->dma_x,
 113                                                   iBuf->dma_w - 1, iBuf->dma_y,
 114                                                   iBuf->dma_h - 1);
 115#endif
 116                        } else {
 117                                dma2_cfg_reg |=
 118                                    DMA_MDDI_DMAOUT_LCD_SEL_SECONDARY;
 119                                mddi_ld_param = 1;
 120#ifdef MDDI_HOST_WINDOW_WORKAROUND
 121                                mddi_window_adjust(mfd, iBuf->dma_x,
 122                                                   iBuf->dma_w - 1, iBuf->dma_y,
 123                                                   iBuf->dma_h - 1);
 124#endif
 125                        }
 126                } else {
 127                        dma2_cfg_reg |= DMA_MDDI_DMAOUT_LCD_SEL_EXTERNAL;
 128                        mddi_ld_param = 2;
 129                }
 130        } else {
 131                if (mfd->panel_info.pdest == DISPLAY_1) {
 132                        dma2_cfg_reg |= DMA_AHBM_LCD_SEL_PRIMARY;
 133                        outp32(MDP_EBI2_LCD0, mfd->data_port_phys);
 134                } else {
 135                        dma2_cfg_reg |= DMA_AHBM_LCD_SEL_SECONDARY;
 136                        outp32(MDP_EBI2_LCD1, mfd->data_port_phys);
 137                }
 138        }
 139
 140        dma2_cfg_reg |= DMA_DITHER_EN;
 141
 142        src = (uint8 *) iBuf->buf;
 143        /* starting input address */
 144        src += iBuf->dma_x * outBpp + iBuf->dma_y * ystride;
 145
 146        mdp_curr_dma2_update_width = iBuf->dma_w;
 147        mdp_curr_dma2_update_height = iBuf->dma_h;
 148
 149        /* MDP cmd block enable */
 150        mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE);
 151
 152#ifdef CONFIG_FB_MSM_MDP22
 153        MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0184,
 154                        (iBuf->dma_h << 16 | iBuf->dma_w));
 155        MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0188, src);
 156        MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x018C, ystride);
 157#else
 158        MDP_OUTP(MDP_BASE + 0x90004, (iBuf->dma_h << 16 | iBuf->dma_w));
 159        MDP_OUTP(MDP_BASE + 0x90008, src);
 160        MDP_OUTP(MDP_BASE + 0x9000c, ystride);
 161#endif
 162
 163        if (mfd->panel_info.bpp == 18) {
 164                dma2_cfg_reg |= DMA_DSTC0G_6BITS |      /* 666 18BPP */
 165                    DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS;
 166        } else {
 167                dma2_cfg_reg |= DMA_DSTC0G_6BITS |      /* 565 16BPP */
 168                    DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS;
 169        }
 170
 171        if (mddi_dest) {
 172#ifdef CONFIG_FB_MSM_MDP22
 173                MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0194,
 174                         (iBuf->dma_y << 16) | iBuf->dma_x);
 175                MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01a0, mddi_ld_param);
 176                MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01a4,
 177                         (MDDI_VDO_PACKET_DESC << 16) | mddi_vdo_packet_reg);
 178#else
 179                MDP_OUTP(MDP_BASE + 0x90010, (iBuf->dma_y << 16) | iBuf->dma_x);
 180                MDP_OUTP(MDP_BASE + 0x00090, mddi_ld_param);
 181                MDP_OUTP(MDP_BASE + 0x00094,
 182                         (MDDI_VDO_PACKET_DESC << 16) | mddi_vdo_packet_reg);
 183#endif
 184        } else {
 185                /* setting EBI2 LCDC write window */
 186                pdata->set_rect(iBuf->dma_x, iBuf->dma_y, iBuf->dma_w,
 187                                iBuf->dma_h);
 188        }
 189
 190        /* dma2 config register */
 191#ifdef MDP_HW_VSYNC
 192        MDP_OUTP(MDP_BASE + 0x90000, dma2_cfg_reg);
 193
 194        if ((mfd->use_mdp_vsync) &&
 195            (mfd->ibuf.vsync_enable) && (mfd->panel_info.lcd.vsync_enable)) {
 196                uint32 start_y;
 197
 198                if (vsync_start_y_adjust <= iBuf->dma_y)
 199                        start_y = iBuf->dma_y - vsync_start_y_adjust;
 200                else
 201                        start_y =
 202                            (mfd->total_lcd_lines - 1) - (vsync_start_y_adjust -
 203                                                          iBuf->dma_y);
 204
 205                /*
 206                 * MDP VSYNC clock must be On by now so, we don't have to
 207                 * re-enable it
 208                 */
 209                MDP_OUTP(MDP_BASE + 0x210, start_y);
 210                MDP_OUTP(MDP_BASE + 0x20c, 1);  /* enable prim vsync */
 211        } else {
 212                MDP_OUTP(MDP_BASE + 0x20c, 0);  /* disable prim vsync */
 213        }
 214#else
 215#ifdef CONFIG_FB_MSM_MDP22
 216        MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0180, dma2_cfg_reg);
 217#else
 218        MDP_OUTP(MDP_BASE + 0x90000, dma2_cfg_reg);
 219#endif
 220#endif /* MDP_HW_VSYNC */
 221
 222        /* MDP cmd block disable */
 223        mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
 224}
 225
 226static ktime_t vt = { 0 };
 227int mdp_usec_diff_threshold = 100;
 228int mdp_expected_usec_wait;
 229
 230enum hrtimer_restart mdp_dma2_vsync_hrtimer_handler(struct hrtimer *ht)
 231{
 232        struct msm_fb_data_type *mfd = NULL;
 233
 234        mfd = container_of(ht, struct msm_fb_data_type, dma_hrtimer);
 235
 236        mdp_pipe_kickoff(MDP_DMA2_TERM, mfd);
 237
 238        if (msm_fb_debug_enabled) {
 239                ktime_t t;
 240                int usec_diff;
 241                int actual_wait;
 242
 243                t = ktime_get_real();
 244
 245                actual_wait =
 246                    (t.tv.sec - vt.tv.sec) * 1000000 + (t.tv.nsec -
 247                                                        vt.tv.nsec) / 1000;
 248                usec_diff = actual_wait - mdp_expected_usec_wait;
 249
 250                if ((mdp_usec_diff_threshold < usec_diff) || (usec_diff < 0))
 251                        MSM_FB_DEBUG
 252                            ("HRT Diff = %d usec Exp=%d usec  Act=%d usec\n",
 253                             usec_diff, mdp_expected_usec_wait, actual_wait);
 254        }
 255
 256        return HRTIMER_NORESTART;
 257}
 258
 259static void mdp_dma_schedule(struct msm_fb_data_type *mfd, uint32 term)
 260{
 261        /*
 262         * dma2 configure VSYNC block
 263         * vsync supported on Primary LCD only for now
 264         */
 265        int32 mdp_lcd_rd_cnt;
 266        uint32 usec_wait_time;
 267        uint32 start_y;
 268
 269        /*
 270         * ToDo: if we can move HRT timer callback to workqueue, we can
 271         * move DMA2 power on under mdp_pipe_kickoff().
 272         * This will save a power for hrt time wait.
 273         * However if the latency for context switch (hrt irq -> workqueue)
 274         * is too big, we will miss the vsync timing.
 275         */
 276        if (term == MDP_DMA2_TERM)
 277                mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, FALSE);
 278
 279        mdp_dma2_update_time_in_usec =
 280            MDP_KTIME2USEC(mdp_dma2_last_update_time);
 281
 282        if ((!mfd->ibuf.vsync_enable) || (!mfd->panel_info.lcd.vsync_enable)
 283            || (mfd->use_mdp_vsync)) {
 284                mdp_pipe_kickoff(term, mfd);
 285                return;
 286        }
 287        /* SW vsync logic starts here */
 288
 289        /* get current rd counter */
 290        mdp_lcd_rd_cnt = mdp_get_lcd_line_counter(mfd);
 291        if (mdp_dma2_update_time_in_usec != 0) {
 292                uint32 num, den;
 293
 294                /*
 295                 * roi width boundary calculation to know the size of pixel
 296                 * width that MDP can send faster or slower than LCD read
 297                 * pointer
 298                 */
 299
 300                num = mdp_last_dma2_update_width * mdp_last_dma2_update_height;
 301                den =
 302                    (((mfd->panel_info.lcd.refx100 * mfd->total_lcd_lines) /
 303                      1000) * (mdp_dma2_update_time_in_usec / 100)) / 1000;
 304
 305                if (den == 0)
 306                        mfd->vsync_width_boundary[mdp_last_dma2_update_width] =
 307                            mfd->panel_info.xres + 1;
 308                else
 309                        mfd->vsync_width_boundary[mdp_last_dma2_update_width] =
 310                            (int)(num / den);
 311        }
 312
 313        if (mfd->vsync_width_boundary[mdp_last_dma2_update_width] >
 314            mdp_curr_dma2_update_width) {
 315                /* MDP wrp is faster than LCD rdp */
 316                mdp_lcd_rd_cnt += mdp_lcd_rd_cnt_offset_fast;
 317        } else {
 318                /* MDP wrp is slower than LCD rdp */
 319                mdp_lcd_rd_cnt -= mdp_lcd_rd_cnt_offset_slow;
 320        }
 321
 322        if (mdp_lcd_rd_cnt < 0)
 323                mdp_lcd_rd_cnt = mfd->total_lcd_lines + mdp_lcd_rd_cnt;
 324        else if (mdp_lcd_rd_cnt > mfd->total_lcd_lines)
 325                mdp_lcd_rd_cnt = mdp_lcd_rd_cnt - mfd->total_lcd_lines - 1;
 326
 327        /* get wrt pointer position */
 328        start_y = mfd->ibuf.dma_y;
 329
 330        /* measure line difference between start_y and rd counter */
 331        if (start_y > mdp_lcd_rd_cnt) {
 332                /*
 333                 * *100 for lcd_ref_hzx100 was already multiplied by 100
 334                 * *1000000 is for usec conversion
 335                 */
 336
 337                if ((start_y - mdp_lcd_rd_cnt) <=
 338                    mdp_vsync_usec_wait_line_too_short)
 339                        usec_wait_time = 0;
 340                else
 341                        usec_wait_time =
 342                            ((start_y -
 343                              mdp_lcd_rd_cnt) * 1000000) /
 344                            ((mfd->total_lcd_lines *
 345                              mfd->panel_info.lcd.refx100) / 100);
 346        } else {
 347                if ((start_y + (mfd->total_lcd_lines - mdp_lcd_rd_cnt)) <=
 348                    mdp_vsync_usec_wait_line_too_short)
 349                        usec_wait_time = 0;
 350                else
 351                        usec_wait_time =
 352                            ((start_y +
 353                              (mfd->total_lcd_lines -
 354                               mdp_lcd_rd_cnt)) * 1000000) /
 355                            ((mfd->total_lcd_lines *
 356                              mfd->panel_info.lcd.refx100) / 100);
 357        }
 358
 359        mdp_last_dma2_update_width = mdp_curr_dma2_update_width;
 360        mdp_last_dma2_update_height = mdp_curr_dma2_update_height;
 361
 362        if (usec_wait_time == 0) {
 363                mdp_pipe_kickoff(term, mfd);
 364        } else {
 365                ktime_t wait_time;
 366
 367                wait_time.tv.sec = 0;
 368                wait_time.tv.nsec = usec_wait_time * 1000;
 369
 370                if (msm_fb_debug_enabled) {
 371                        vt = ktime_get_real();
 372                        mdp_expected_usec_wait = usec_wait_time;
 373                }
 374                hrtimer_start(&mfd->dma_hrtimer, wait_time, HRTIMER_MODE_REL);
 375        }
 376}
 377
 378#ifdef MDDI_HOST_WINDOW_WORKAROUND
 379void mdp_dma2_update(struct msm_fb_data_type *mfd)
 380{
 381        MDPIBUF *iBuf;
 382        uint32 upper_height;
 383
 384        if (mfd->panel.type == EXT_MDDI_PANEL) {
 385                mdp_dma2_update_sub(mfd);
 386                return;
 387        }
 388
 389        iBuf = &mfd->ibuf;
 390
 391        upper_height =
 392            (uint32) mddi_assign_pkt_height((uint16) iBuf->dma_w,
 393                                            (uint16) iBuf->dma_h, 18);
 394
 395        if (upper_height >= iBuf->dma_h) {
 396                mdp_dma2_update_sub(mfd);
 397        } else {
 398                MDPIBUF lower_height;
 399
 400                /* sending the upper region first */
 401                lower_height = iBuf->dma_h - upper_height;
 402                iBuf->dma_h = upper_height;
 403                mdp_dma2_update_sub(mfd);
 404
 405                /* sending the lower region second */
 406                iBuf->dma_h = lower_height;
 407                iBuf->dma_y += lower_height;
 408                iBuf->vsync_enable = FALSE;
 409                mdp_dma2_update_sub(mfd);
 410        }
 411}
 412
 413void mdp_dma2_update_sub(struct msm_fb_data_type *mfd)
 414#else
 415void mdp_dma2_update(struct msm_fb_data_type *mfd)
 416#endif
 417{
 418        down(&mfd->dma->mutex);
 419        if ((mfd) && (!mfd->dma->busy) && (mfd->panel_power_on)) {
 420                down(&mfd->sem);
 421                mfd->ibuf_flushed = TRUE;
 422                mdp_dma2_update_lcd(mfd);
 423
 424                mdp_enable_irq(MDP_DMA2_TERM);
 425                mfd->dma->busy = TRUE;
 426                INIT_COMPLETION(mfd->dma->comp);
 427
 428                /* schedule DMA to start */
 429                mdp_dma_schedule(mfd, MDP_DMA2_TERM);
 430                up(&mfd->sem);
 431
 432                /* wait until DMA finishes the current job */
 433                wait_for_completion_killable(&mfd->dma->comp);
 434                mdp_disable_irq(MDP_DMA2_TERM);
 435
 436        /* signal if pan function is waiting for the update completion */
 437                if (mfd->pan_waiting) {
 438                        mfd->pan_waiting = FALSE;
 439                        complete(&mfd->pan_comp);
 440                }
 441        }
 442        up(&mfd->dma->mutex);
 443}
 444
 445void mdp_lcd_update_workqueue_handler(struct work_struct *work)
 446{
 447        struct msm_fb_data_type *mfd = NULL;
 448
 449        mfd = container_of(work, struct msm_fb_data_type, dma_update_worker);
 450        if (mfd)
 451                mfd->dma_fnc(mfd);
 452}
 453
 454void mdp_set_dma_pan_info(struct fb_info *info, struct mdp_dirty_region *dirty,
 455                          boolean sync)
 456{
 457        struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
 458        MDPIBUF *iBuf;
 459        int bpp = info->var.bits_per_pixel / 8;
 460
 461        down(&mfd->sem);
 462        iBuf = &mfd->ibuf;
 463        iBuf->buf = (uint8 *) info->fix.smem_start;
 464        iBuf->buf += info->var.xoffset * bpp +
 465                        info->var.yoffset * info->fix.line_length;
 466
 467        iBuf->ibuf_width = info->var.xres_virtual;
 468        iBuf->bpp = bpp;
 469
 470        iBuf->vsync_enable = sync;
 471
 472        if (dirty) {
 473                /*
 474                 * ToDo: dirty region check inside var.xoffset+xres
 475                 * <-> var.yoffset+yres
 476                 */
 477                iBuf->dma_x = dirty->xoffset % info->var.xres;
 478                iBuf->dma_y = dirty->yoffset % info->var.yres;
 479                iBuf->dma_w = dirty->width;
 480                iBuf->dma_h = dirty->height;
 481        } else {
 482                iBuf->dma_x = 0;
 483                iBuf->dma_y = 0;
 484                iBuf->dma_w = info->var.xres;
 485                iBuf->dma_h = info->var.yres;
 486        }
 487        mfd->ibuf_flushed = FALSE;
 488        up(&mfd->sem);
 489}
 490
 491void mdp_set_offset_info(struct fb_info *info, uint32 addr, uint32 sync)
 492{
 493        struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
 494        MDPIBUF *iBuf;
 495
 496        int bpp = info->var.bits_per_pixel / 8;
 497
 498        down(&mfd->sem);
 499        iBuf = &mfd->ibuf;
 500        iBuf->ibuf_width = info->var.xres_virtual;
 501        iBuf->bpp = bpp;
 502        iBuf->vsync_enable = sync;
 503        iBuf->dma_x = 0;
 504        iBuf->dma_y = 0;
 505        iBuf->dma_w = info->var.xres;
 506        iBuf->dma_h = info->var.yres;
 507        iBuf->buf = (uint8 *) addr;
 508
 509        mfd->ibuf_flushed = FALSE;
 510        up(&mfd->sem);
 511}
 512
 513void mdp_dma_pan_update(struct fb_info *info)
 514{
 515        struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par;
 516        MDPIBUF *iBuf;
 517
 518        iBuf = &mfd->ibuf;
 519
 520        if (mfd->sw_currently_refreshing) {
 521                /* we need to wait for the pending update */
 522                mfd->pan_waiting = TRUE;
 523                if (!mfd->ibuf_flushed) {
 524                        wait_for_completion_killable(&mfd->pan_comp);
 525                }
 526                /* waiting for this update to complete */
 527                mfd->pan_waiting = TRUE;
 528                wait_for_completion_killable(&mfd->pan_comp);
 529        } else
 530                mfd->dma_fnc(mfd);
 531}
 532
 533void mdp_refresh_screen(unsigned long data)
 534{
 535        struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data;
 536
 537        if ((mfd->sw_currently_refreshing) && (mfd->sw_refreshing_enable)) {
 538                init_timer(&mfd->refresh_timer);
 539                mfd->refresh_timer.function = mdp_refresh_screen;
 540                mfd->refresh_timer.data = data;
 541
 542                if (mfd->dma->busy)
 543                        /* come back in 1 msec */
 544                        mfd->refresh_timer.expires = jiffies + (HZ / 1000);
 545                else
 546                        mfd->refresh_timer.expires =
 547                            jiffies + mfd->refresh_timer_duration;
 548
 549                add_timer(&mfd->refresh_timer);
 550
 551                if (!mfd->dma->busy) {
 552                        if (!queue_work(mdp_dma_wq, &mfd->dma_update_worker)) {
 553                                MSM_FB_DEBUG("mdp_dma: can't queue_work! -> \
 554                        MDP/MDDI/LCD clock speed needs to be increased\n");
 555                        }
 556                }
 557        } else {
 558                if (!mfd->hw_refresh)
 559                        complete(&mfd->refresher_comp);
 560        }
 561}
 562