162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Orisetech OTA5601A TFT LCD panel driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2021, Christophe Branchereau <cbranchereau@gmail.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/bits.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1262306a36Sopenharmony_ci#include <linux/media-bus-format.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1762306a36Sopenharmony_ci#include <linux/spi/spi.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <drm/drm_modes.h> 2062306a36Sopenharmony_ci#include <drm/drm_panel.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define OTA5601A_CTL 0x01 2362306a36Sopenharmony_ci#define OTA5601A_CTL_OFF 0x00 2462306a36Sopenharmony_ci#define OTA5601A_CTL_ON BIT(0) 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistruct ota5601a_panel_info { 2762306a36Sopenharmony_ci const struct drm_display_mode *display_modes; 2862306a36Sopenharmony_ci unsigned int num_modes; 2962306a36Sopenharmony_ci u16 width_mm, height_mm; 3062306a36Sopenharmony_ci u32 bus_format, bus_flags; 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct ota5601a { 3462306a36Sopenharmony_ci struct drm_panel drm_panel; 3562306a36Sopenharmony_ci struct regmap *map; 3662306a36Sopenharmony_ci struct regulator *supply; 3762306a36Sopenharmony_ci const struct ota5601a_panel_info *panel_info; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic inline struct ota5601a *to_ota5601a(struct drm_panel *panel) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci return container_of(panel, struct ota5601a, drm_panel); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic const struct reg_sequence ota5601a_panel_regs[] = { 4862306a36Sopenharmony_ci { 0xfd, 0x00 }, /* Page Shift */ 4962306a36Sopenharmony_ci { 0x02, 0x00 }, /* Reset */ 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci { 0x18, 0x00 }, /* Interface Sel: RGB 24 Bits */ 5262306a36Sopenharmony_ci { 0x34, 0x20 }, /* Undocumented */ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci { 0x0c, 0x01 }, /* Contrast set by CMD1 == within page 0x00 */ 5562306a36Sopenharmony_ci { 0x0d, 0x48 }, /* R Brightness */ 5662306a36Sopenharmony_ci { 0x0e, 0x48 }, /* G Brightness */ 5762306a36Sopenharmony_ci { 0x0f, 0x48 }, /* B Brightness */ 5862306a36Sopenharmony_ci { 0x07, 0x40 }, /* R Contrast */ 5962306a36Sopenharmony_ci { 0x08, 0x33 }, /* G Contrast */ 6062306a36Sopenharmony_ci { 0x09, 0x3a }, /* B Contrast */ 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci { 0x16, 0x01 }, /* NTSC Sel */ 6362306a36Sopenharmony_ci { 0x19, 0x8d }, /* VBLK */ 6462306a36Sopenharmony_ci { 0x1a, 0x28 }, /* HBLK */ 6562306a36Sopenharmony_ci { 0x1c, 0x00 }, /* Scan Shift Dir. */ 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci { 0xfd, 0xc5 }, /* Page Shift */ 6862306a36Sopenharmony_ci { 0x82, 0x0c }, /* PWR_CTRL Pump */ 6962306a36Sopenharmony_ci { 0xa2, 0xb4 }, /* PWR_CTRL VGH/VGL */ 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci { 0xfd, 0xc4 }, /* Page Shift - What follows is listed as "RGB 24bit Timing Set" */ 7262306a36Sopenharmony_ci { 0x82, 0x45 }, 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci { 0xfd, 0xc1 }, 7562306a36Sopenharmony_ci { 0x91, 0x02 }, 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci { 0xfd, 0xc0 }, 7862306a36Sopenharmony_ci { 0xa1, 0x01 }, 7962306a36Sopenharmony_ci { 0xa2, 0x1f }, 8062306a36Sopenharmony_ci { 0xa3, 0x0b }, 8162306a36Sopenharmony_ci { 0xa4, 0x38 }, 8262306a36Sopenharmony_ci { 0xa5, 0x00 }, 8362306a36Sopenharmony_ci { 0xa6, 0x0a }, 8462306a36Sopenharmony_ci { 0xa7, 0x38 }, 8562306a36Sopenharmony_ci { 0xa8, 0x00 }, 8662306a36Sopenharmony_ci { 0xa9, 0x0a }, 8762306a36Sopenharmony_ci { 0xaa, 0x37 }, 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci { 0xfd, 0xce }, 9062306a36Sopenharmony_ci { 0x81, 0x18 }, 9162306a36Sopenharmony_ci { 0x82, 0x43 }, 9262306a36Sopenharmony_ci { 0x83, 0x43 }, 9362306a36Sopenharmony_ci { 0x91, 0x06 }, 9462306a36Sopenharmony_ci { 0x93, 0x38 }, 9562306a36Sopenharmony_ci { 0x94, 0x02 }, 9662306a36Sopenharmony_ci { 0x95, 0x06 }, 9762306a36Sopenharmony_ci { 0x97, 0x38 }, 9862306a36Sopenharmony_ci { 0x98, 0x02 }, 9962306a36Sopenharmony_ci { 0x99, 0x06 }, 10062306a36Sopenharmony_ci { 0x9b, 0x38 }, 10162306a36Sopenharmony_ci { 0x9c, 0x02 }, 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci { 0xfd, 0x00 }, /* Page Shift */ 10462306a36Sopenharmony_ci}; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic const struct regmap_config ota5601a_regmap_config = { 10762306a36Sopenharmony_ci .reg_bits = 8, 10862306a36Sopenharmony_ci .val_bits = 8, 10962306a36Sopenharmony_ci}; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int ota5601a_prepare(struct drm_panel *drm_panel) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci struct ota5601a *panel = to_ota5601a(drm_panel); 11462306a36Sopenharmony_ci int err; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci err = regulator_enable(panel->supply); 11762306a36Sopenharmony_ci if (err) { 11862306a36Sopenharmony_ci dev_err(drm_panel->dev, "Failed to enable power supply: %d\n", err); 11962306a36Sopenharmony_ci return err; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* Reset to be held low for 10us min according to the doc, 10ms before sending commands */ 12362306a36Sopenharmony_ci gpiod_set_value_cansleep(panel->reset_gpio, 1); 12462306a36Sopenharmony_ci usleep_range(10, 30); 12562306a36Sopenharmony_ci gpiod_set_value_cansleep(panel->reset_gpio, 0); 12662306a36Sopenharmony_ci usleep_range(10000, 20000); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* Init all registers. */ 12962306a36Sopenharmony_ci err = regmap_multi_reg_write(panel->map, ota5601a_panel_regs, 13062306a36Sopenharmony_ci ARRAY_SIZE(ota5601a_panel_regs)); 13162306a36Sopenharmony_ci if (err) { 13262306a36Sopenharmony_ci dev_err(drm_panel->dev, "Failed to init registers: %d\n", err); 13362306a36Sopenharmony_ci goto err_disable_regulator; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci msleep(120); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cierr_disable_regulator: 14162306a36Sopenharmony_ci regulator_disable(panel->supply); 14262306a36Sopenharmony_ci return err; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int ota5601a_unprepare(struct drm_panel *drm_panel) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct ota5601a *panel = to_ota5601a(drm_panel); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci gpiod_set_value_cansleep(panel->reset_gpio, 1); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci regulator_disable(panel->supply); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return 0; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int ota5601a_enable(struct drm_panel *drm_panel) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct ota5601a *panel = to_ota5601a(drm_panel); 15962306a36Sopenharmony_ci int err; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci err = regmap_write(panel->map, OTA5601A_CTL, OTA5601A_CTL_ON); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (err) { 16462306a36Sopenharmony_ci dev_err(drm_panel->dev, "Unable to enable panel: %d\n", err); 16562306a36Sopenharmony_ci return err; 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (drm_panel->backlight) { 16962306a36Sopenharmony_ci /* Wait for the picture to be ready before enabling backlight */ 17062306a36Sopenharmony_ci msleep(120); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return 0; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int ota5601a_disable(struct drm_panel *drm_panel) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct ota5601a *panel = to_ota5601a(drm_panel); 17962306a36Sopenharmony_ci int err; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci err = regmap_write(panel->map, OTA5601A_CTL, OTA5601A_CTL_OFF); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if (err) { 18462306a36Sopenharmony_ci dev_err(drm_panel->dev, "Unable to disable panel: %d\n", err); 18562306a36Sopenharmony_ci return err; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci return 0; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic int ota5601a_get_modes(struct drm_panel *drm_panel, 19262306a36Sopenharmony_ci struct drm_connector *connector) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci struct ota5601a *panel = to_ota5601a(drm_panel); 19562306a36Sopenharmony_ci const struct ota5601a_panel_info *panel_info = panel->panel_info; 19662306a36Sopenharmony_ci struct drm_display_mode *mode; 19762306a36Sopenharmony_ci unsigned int i; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci for (i = 0; i < panel_info->num_modes; i++) { 20062306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, 20162306a36Sopenharmony_ci &panel_info->display_modes[i]); 20262306a36Sopenharmony_ci if (!mode) 20362306a36Sopenharmony_ci return -ENOMEM; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci drm_mode_set_name(mode); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER; 20862306a36Sopenharmony_ci if (panel_info->num_modes == 1) 20962306a36Sopenharmony_ci mode->type |= DRM_MODE_TYPE_PREFERRED; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci connector->display_info.bpc = 8; 21562306a36Sopenharmony_ci connector->display_info.width_mm = panel_info->width_mm; 21662306a36Sopenharmony_ci connector->display_info.height_mm = panel_info->height_mm; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci drm_display_info_set_bus_formats(&connector->display_info, 21962306a36Sopenharmony_ci &panel_info->bus_format, 1); 22062306a36Sopenharmony_ci connector->display_info.bus_flags = panel_info->bus_flags; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return panel_info->num_modes; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic const struct drm_panel_funcs ota5601a_funcs = { 22662306a36Sopenharmony_ci .prepare = ota5601a_prepare, 22762306a36Sopenharmony_ci .unprepare = ota5601a_unprepare, 22862306a36Sopenharmony_ci .enable = ota5601a_enable, 22962306a36Sopenharmony_ci .disable = ota5601a_disable, 23062306a36Sopenharmony_ci .get_modes = ota5601a_get_modes, 23162306a36Sopenharmony_ci}; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic int ota5601a_probe(struct spi_device *spi) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci const struct spi_device_id *id = spi_get_device_id(spi); 23662306a36Sopenharmony_ci struct device *dev = &spi->dev; 23762306a36Sopenharmony_ci struct ota5601a *panel; 23862306a36Sopenharmony_ci int err; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); 24162306a36Sopenharmony_ci if (!panel) 24262306a36Sopenharmony_ci return -ENOMEM; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci spi_set_drvdata(spi, panel); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci panel->panel_info = (const struct ota5601a_panel_info *)id->driver_data; 24762306a36Sopenharmony_ci if (!panel->panel_info) 24862306a36Sopenharmony_ci return -EINVAL; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci panel->supply = devm_regulator_get(dev, "power"); 25162306a36Sopenharmony_ci if (IS_ERR(panel->supply)) { 25262306a36Sopenharmony_ci dev_err(dev, "Failed to get power supply\n"); 25362306a36Sopenharmony_ci return PTR_ERR(panel->supply); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci panel->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 25762306a36Sopenharmony_ci if (IS_ERR(panel->reset_gpio)) { 25862306a36Sopenharmony_ci dev_err(dev, "Failed to get reset GPIO\n"); 25962306a36Sopenharmony_ci return PTR_ERR(panel->reset_gpio); 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci spi->bits_per_word = 8; 26362306a36Sopenharmony_ci spi->mode = SPI_MODE_3 | SPI_3WIRE; 26462306a36Sopenharmony_ci err = spi_setup(spi); 26562306a36Sopenharmony_ci if (err) { 26662306a36Sopenharmony_ci dev_err(dev, "Failed to setup SPI\n"); 26762306a36Sopenharmony_ci return err; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci panel->map = devm_regmap_init_spi(spi, &ota5601a_regmap_config); 27162306a36Sopenharmony_ci if (IS_ERR(panel->map)) { 27262306a36Sopenharmony_ci dev_err(dev, "Failed to init regmap\n"); 27362306a36Sopenharmony_ci return PTR_ERR(panel->map); 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci drm_panel_init(&panel->drm_panel, dev, &ota5601a_funcs, 27762306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DPI); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci err = drm_panel_of_backlight(&panel->drm_panel); 28062306a36Sopenharmony_ci if (err) { 28162306a36Sopenharmony_ci if (err != -EPROBE_DEFER) 28262306a36Sopenharmony_ci dev_err(dev, "Failed to get backlight handle\n"); 28362306a36Sopenharmony_ci return err; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci drm_panel_add(&panel->drm_panel); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci return 0; 28962306a36Sopenharmony_ci} 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cistatic void ota5601a_remove(struct spi_device *spi) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct ota5601a *panel = spi_get_drvdata(spi); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci drm_panel_remove(&panel->drm_panel); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci ota5601a_disable(&panel->drm_panel); 29862306a36Sopenharmony_ci ota5601a_unprepare(&panel->drm_panel); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic const struct drm_display_mode gpt3_display_modes[] = { 30262306a36Sopenharmony_ci { /* 60 Hz */ 30362306a36Sopenharmony_ci .clock = 27000, 30462306a36Sopenharmony_ci .hdisplay = 640, 30562306a36Sopenharmony_ci .hsync_start = 640 + 220, 30662306a36Sopenharmony_ci .hsync_end = 640 + 220 + 20, 30762306a36Sopenharmony_ci .htotal = 640 + 220 + 20 + 20, 30862306a36Sopenharmony_ci .vdisplay = 480, 30962306a36Sopenharmony_ci .vsync_start = 480 + 7, 31062306a36Sopenharmony_ci .vsync_end = 480 + 7 + 6, 31162306a36Sopenharmony_ci .vtotal = 480 + 7 + 6 + 7, 31262306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 31362306a36Sopenharmony_ci }, 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci { /* 50 Hz */ 31662306a36Sopenharmony_ci .clock = 24000, 31762306a36Sopenharmony_ci .hdisplay = 640, 31862306a36Sopenharmony_ci .hsync_start = 640 + 280, 31962306a36Sopenharmony_ci .hsync_end = 640 + 280 + 20, 32062306a36Sopenharmony_ci .htotal = 640 + 280 + 20 + 20, 32162306a36Sopenharmony_ci .vdisplay = 480, 32262306a36Sopenharmony_ci .vsync_start = 480 + 7, 32362306a36Sopenharmony_ci .vsync_end = 480 + 7 + 6, 32462306a36Sopenharmony_ci .vtotal = 480 + 7 + 6 + 7, 32562306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 32662306a36Sopenharmony_ci }, 32762306a36Sopenharmony_ci}; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_cistatic const struct ota5601a_panel_info gpt3_info = { 33062306a36Sopenharmony_ci .display_modes = gpt3_display_modes, 33162306a36Sopenharmony_ci .num_modes = ARRAY_SIZE(gpt3_display_modes), 33262306a36Sopenharmony_ci .width_mm = 71, 33362306a36Sopenharmony_ci .height_mm = 51, 33462306a36Sopenharmony_ci .bus_format = MEDIA_BUS_FMT_RGB888_1X24, 33562306a36Sopenharmony_ci .bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE, 33662306a36Sopenharmony_ci}; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cistatic const struct spi_device_id gpt3_id[] = { 33962306a36Sopenharmony_ci { "gpt3", (kernel_ulong_t)&gpt3_info }, 34062306a36Sopenharmony_ci { /* sentinel */ } 34162306a36Sopenharmony_ci}; 34262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(spi, gpt3_id); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic const struct of_device_id ota5601a_of_match[] = { 34562306a36Sopenharmony_ci { .compatible = "focaltech,gpt3" }, 34662306a36Sopenharmony_ci { /* sentinel */ } 34762306a36Sopenharmony_ci}; 34862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ota5601a_of_match); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic struct spi_driver ota5601a_driver = { 35162306a36Sopenharmony_ci .driver = { 35262306a36Sopenharmony_ci .name = "ota5601a", 35362306a36Sopenharmony_ci .of_match_table = ota5601a_of_match, 35462306a36Sopenharmony_ci }, 35562306a36Sopenharmony_ci .id_table = gpt3_id, 35662306a36Sopenharmony_ci .probe = ota5601a_probe, 35762306a36Sopenharmony_ci .remove = ota5601a_remove, 35862306a36Sopenharmony_ci}; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_cimodule_spi_driver(ota5601a_driver); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ciMODULE_AUTHOR("Christophe Branchereau <cbranchereau@gmail.com>"); 36362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 364