12dfef650SFabio Estevam // SPDX-License-Identifier: GPL-2.0 22dfef650SFabio Estevam // 32dfef650SFabio Estevam // Copyright 2016 Freescale Semiconductor, Inc. 443528445SJia Hongtao 551904045SAnson Huang #include <linux/clk.h> 643528445SJia Hongtao #include <linux/module.h> 743528445SJia Hongtao #include <linux/platform_device.h> 843528445SJia Hongtao #include <linux/err.h> 943528445SJia Hongtao #include <linux/io.h> 1043528445SJia Hongtao #include <linux/of.h> 1143528445SJia Hongtao #include <linux/of_address.h> 1243528445SJia Hongtao #include <linux/thermal.h> 1343528445SJia Hongtao 1443528445SJia Hongtao #include "thermal_core.h" 1543528445SJia Hongtao 1643528445SJia Hongtao #define SITES_MAX 16 179809797bSYuantian Tang #define TMR_DISABLE 0x0 189809797bSYuantian Tang #define TMR_ME 0x80000000 199809797bSYuantian Tang #define TMR_ALPF 0x0c000000 209809797bSYuantian Tang #define TMR_ALPF_V2 0x03000000 219809797bSYuantian Tang #define TMTMIR_DEFAULT 0x0000000f 229809797bSYuantian Tang #define TIER_DISABLE 0x0 239809797bSYuantian Tang #define TEUMR0_V2 0x51009c00 249809797bSYuantian Tang #define TMU_VER1 0x1 259809797bSYuantian Tang #define TMU_VER2 0x2 2643528445SJia Hongtao 2743528445SJia Hongtao /* 2843528445SJia Hongtao * QorIQ TMU Registers 2943528445SJia Hongtao */ 3043528445SJia Hongtao struct qoriq_tmu_site_regs { 3143528445SJia Hongtao u32 tritsr; /* Immediate Temperature Site Register */ 3243528445SJia Hongtao u32 tratsr; /* Average Temperature Site Register */ 3343528445SJia Hongtao u8 res0[0x8]; 3443528445SJia Hongtao }; 3543528445SJia Hongtao 369809797bSYuantian Tang struct qoriq_tmu_regs_v1 { 3743528445SJia Hongtao u32 tmr; /* Mode Register */ 3843528445SJia Hongtao u32 tsr; /* Status Register */ 3943528445SJia Hongtao u32 tmtmir; /* Temperature measurement interval Register */ 4043528445SJia Hongtao u8 res0[0x14]; 4143528445SJia Hongtao u32 tier; /* Interrupt Enable Register */ 4243528445SJia Hongtao u32 tidr; /* Interrupt Detect Register */ 4343528445SJia Hongtao u32 tiscr; /* Interrupt Site Capture Register */ 4443528445SJia Hongtao u32 ticscr; /* Interrupt Critical Site Capture Register */ 4543528445SJia Hongtao u8 res1[0x10]; 4643528445SJia Hongtao u32 tmhtcrh; /* High Temperature Capture Register */ 4743528445SJia Hongtao u32 tmhtcrl; /* Low Temperature Capture Register */ 4843528445SJia Hongtao u8 res2[0x8]; 4943528445SJia Hongtao u32 tmhtitr; /* High Temperature Immediate Threshold */ 5043528445SJia Hongtao u32 tmhtatr; /* High Temperature Average Threshold */ 5143528445SJia Hongtao u32 tmhtactr; /* High Temperature Average Crit Threshold */ 5243528445SJia Hongtao u8 res3[0x24]; 5343528445SJia Hongtao u32 ttcfgr; /* Temperature Configuration Register */ 5443528445SJia Hongtao u32 tscfgr; /* Sensor Configuration Register */ 5543528445SJia Hongtao u8 res4[0x78]; 5643528445SJia Hongtao struct qoriq_tmu_site_regs site[SITES_MAX]; 5743528445SJia Hongtao u8 res5[0x9f8]; 5843528445SJia Hongtao u32 ipbrr0; /* IP Block Revision Register 0 */ 5943528445SJia Hongtao u32 ipbrr1; /* IP Block Revision Register 1 */ 6043528445SJia Hongtao u8 res6[0x310]; 619809797bSYuantian Tang u32 ttrcr[4]; /* Temperature Range Control Register */ 629809797bSYuantian Tang }; 639809797bSYuantian Tang 649809797bSYuantian Tang struct qoriq_tmu_regs_v2 { 659809797bSYuantian Tang u32 tmr; /* Mode Register */ 669809797bSYuantian Tang u32 tsr; /* Status Register */ 679809797bSYuantian Tang u32 tmsr; /* monitor site register */ 689809797bSYuantian Tang u32 tmtmir; /* Temperature measurement interval Register */ 699809797bSYuantian Tang u8 res0[0x10]; 709809797bSYuantian Tang u32 tier; /* Interrupt Enable Register */ 719809797bSYuantian Tang u32 tidr; /* Interrupt Detect Register */ 729809797bSYuantian Tang u8 res1[0x8]; 739809797bSYuantian Tang u32 tiiscr; /* interrupt immediate site capture register */ 749809797bSYuantian Tang u32 tiascr; /* interrupt average site capture register */ 759809797bSYuantian Tang u32 ticscr; /* Interrupt Critical Site Capture Register */ 769809797bSYuantian Tang u32 res2; 779809797bSYuantian Tang u32 tmhtcr; /* monitor high temperature capture register */ 789809797bSYuantian Tang u32 tmltcr; /* monitor low temperature capture register */ 799809797bSYuantian Tang u32 tmrtrcr; /* monitor rising temperature rate capture register */ 809809797bSYuantian Tang u32 tmftrcr; /* monitor falling temperature rate capture register */ 819809797bSYuantian Tang u32 tmhtitr; /* High Temperature Immediate Threshold */ 829809797bSYuantian Tang u32 tmhtatr; /* High Temperature Average Threshold */ 839809797bSYuantian Tang u32 tmhtactr; /* High Temperature Average Crit Threshold */ 849809797bSYuantian Tang u32 res3; 859809797bSYuantian Tang u32 tmltitr; /* monitor low temperature immediate threshold */ 869809797bSYuantian Tang u32 tmltatr; /* monitor low temperature average threshold register */ 879809797bSYuantian Tang u32 tmltactr; /* monitor low temperature average critical threshold */ 889809797bSYuantian Tang u32 res4; 899809797bSYuantian Tang u32 tmrtrctr; /* monitor rising temperature rate critical threshold */ 909809797bSYuantian Tang u32 tmftrctr; /* monitor falling temperature rate critical threshold*/ 919809797bSYuantian Tang u8 res5[0x8]; 929809797bSYuantian Tang u32 ttcfgr; /* Temperature Configuration Register */ 939809797bSYuantian Tang u32 tscfgr; /* Sensor Configuration Register */ 949809797bSYuantian Tang u8 res6[0x78]; 959809797bSYuantian Tang struct qoriq_tmu_site_regs site[SITES_MAX]; 969809797bSYuantian Tang u8 res7[0x9f8]; 979809797bSYuantian Tang u32 ipbrr0; /* IP Block Revision Register 0 */ 989809797bSYuantian Tang u32 ipbrr1; /* IP Block Revision Register 1 */ 999809797bSYuantian Tang u8 res8[0x300]; 1009809797bSYuantian Tang u32 teumr0; 1019809797bSYuantian Tang u32 teumr1; 1029809797bSYuantian Tang u32 teumr2; 1039809797bSYuantian Tang u32 res9; 1049809797bSYuantian Tang u32 ttrcr[4]; /* Temperature Range Control Register */ 10543528445SJia Hongtao }; 10643528445SJia Hongtao 1077797ff42SYuantian Tang struct qoriq_tmu_data; 1087797ff42SYuantian Tang 10943528445SJia Hongtao /* 11043528445SJia Hongtao * Thermal zone data 11143528445SJia Hongtao */ 1127797ff42SYuantian Tang struct qoriq_sensor { 1137797ff42SYuantian Tang struct qoriq_tmu_data *qdata; 1147797ff42SYuantian Tang int id; 1157797ff42SYuantian Tang }; 1167797ff42SYuantian Tang 11743528445SJia Hongtao struct qoriq_tmu_data { 1189809797bSYuantian Tang int ver; 1199809797bSYuantian Tang struct qoriq_tmu_regs_v1 __iomem *regs; 1209809797bSYuantian Tang struct qoriq_tmu_regs_v2 __iomem *regs_v2; 12151904045SAnson Huang struct clk *clk; 12243528445SJia Hongtao bool little_endian; 1237797ff42SYuantian Tang struct qoriq_sensor *sensor[SITES_MAX]; 12443528445SJia Hongtao }; 12543528445SJia Hongtao 12643528445SJia Hongtao static void tmu_write(struct qoriq_tmu_data *p, u32 val, void __iomem *addr) 12743528445SJia Hongtao { 12843528445SJia Hongtao if (p->little_endian) 12943528445SJia Hongtao iowrite32(val, addr); 13043528445SJia Hongtao else 13143528445SJia Hongtao iowrite32be(val, addr); 13243528445SJia Hongtao } 13343528445SJia Hongtao 13443528445SJia Hongtao static u32 tmu_read(struct qoriq_tmu_data *p, void __iomem *addr) 13543528445SJia Hongtao { 13643528445SJia Hongtao if (p->little_endian) 13743528445SJia Hongtao return ioread32(addr); 13843528445SJia Hongtao else 13943528445SJia Hongtao return ioread32be(addr); 14043528445SJia Hongtao } 14143528445SJia Hongtao 14243528445SJia Hongtao static int tmu_get_temp(void *p, int *temp) 14343528445SJia Hongtao { 1447797ff42SYuantian Tang struct qoriq_sensor *qsensor = p; 1457797ff42SYuantian Tang struct qoriq_tmu_data *qdata = qsensor->qdata; 14643528445SJia Hongtao u32 val; 14743528445SJia Hongtao 1487797ff42SYuantian Tang val = tmu_read(qdata, &qdata->regs->site[qsensor->id].tritsr); 14943528445SJia Hongtao *temp = (val & 0xff) * 1000; 15043528445SJia Hongtao 15143528445SJia Hongtao return 0; 15243528445SJia Hongtao } 15343528445SJia Hongtao 1547797ff42SYuantian Tang static const struct thermal_zone_of_device_ops tmu_tz_ops = { 1557797ff42SYuantian Tang .get_temp = tmu_get_temp, 1567797ff42SYuantian Tang }; 1577797ff42SYuantian Tang 1587797ff42SYuantian Tang static int qoriq_tmu_register_tmu_zone(struct platform_device *pdev) 15943528445SJia Hongtao { 1607797ff42SYuantian Tang struct qoriq_tmu_data *qdata = platform_get_drvdata(pdev); 1617797ff42SYuantian Tang int id, sites = 0; 16243528445SJia Hongtao 1637797ff42SYuantian Tang for (id = 0; id < SITES_MAX; id++) { 16411ef00f7SAndrey Smirnov struct thermal_zone_device *tzd; 16511ef00f7SAndrey Smirnov int ret; 16611ef00f7SAndrey Smirnov 1677797ff42SYuantian Tang qdata->sensor[id] = devm_kzalloc(&pdev->dev, 1687797ff42SYuantian Tang sizeof(struct qoriq_sensor), GFP_KERNEL); 1697797ff42SYuantian Tang if (!qdata->sensor[id]) 1707797ff42SYuantian Tang return -ENOMEM; 17143528445SJia Hongtao 1727797ff42SYuantian Tang qdata->sensor[id]->id = id; 1737797ff42SYuantian Tang qdata->sensor[id]->qdata = qdata; 17411ef00f7SAndrey Smirnov 17511ef00f7SAndrey Smirnov tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id, 17611ef00f7SAndrey Smirnov qdata->sensor[id], 17711ef00f7SAndrey Smirnov &tmu_tz_ops); 17811ef00f7SAndrey Smirnov ret = PTR_ERR_OR_ZERO(tzd); 17911ef00f7SAndrey Smirnov if (ret) { 18011ef00f7SAndrey Smirnov if (ret == -ENODEV) 1817797ff42SYuantian Tang continue; 1827797ff42SYuantian Tang else 18311ef00f7SAndrey Smirnov return ret; 18443528445SJia Hongtao } 18543528445SJia Hongtao 1869809797bSYuantian Tang if (qdata->ver == TMU_VER1) 1877797ff42SYuantian Tang sites |= 0x1 << (15 - id); 1889809797bSYuantian Tang else 1899809797bSYuantian Tang sites |= 0x1 << id; 19043528445SJia Hongtao } 19143528445SJia Hongtao 1927797ff42SYuantian Tang /* Enable monitoring */ 1939809797bSYuantian Tang if (sites != 0) { 1949809797bSYuantian Tang if (qdata->ver == TMU_VER1) { 1959809797bSYuantian Tang tmu_write(qdata, sites | TMR_ME | TMR_ALPF, 1969809797bSYuantian Tang &qdata->regs->tmr); 1979809797bSYuantian Tang } else { 1989809797bSYuantian Tang tmu_write(qdata, sites, &qdata->regs_v2->tmsr); 1999809797bSYuantian Tang tmu_write(qdata, TMR_ME | TMR_ALPF_V2, 2009809797bSYuantian Tang &qdata->regs_v2->tmr); 2019809797bSYuantian Tang } 2029809797bSYuantian Tang } 20343528445SJia Hongtao 2047797ff42SYuantian Tang return 0; 20543528445SJia Hongtao } 20643528445SJia Hongtao 20743528445SJia Hongtao static int qoriq_tmu_calibration(struct platform_device *pdev) 20843528445SJia Hongtao { 20943528445SJia Hongtao int i, val, len; 21043528445SJia Hongtao u32 range[4]; 21143528445SJia Hongtao const u32 *calibration; 21243528445SJia Hongtao struct device_node *np = pdev->dev.of_node; 21343528445SJia Hongtao struct qoriq_tmu_data *data = platform_get_drvdata(pdev); 21443528445SJia Hongtao 2159809797bSYuantian Tang len = of_property_count_u32_elems(np, "fsl,tmu-range"); 2169809797bSYuantian Tang if (len < 0 || len > 4) { 2179809797bSYuantian Tang dev_err(&pdev->dev, "invalid range data.\n"); 2189809797bSYuantian Tang return len; 2199809797bSYuantian Tang } 2209809797bSYuantian Tang 2219809797bSYuantian Tang val = of_property_read_u32_array(np, "fsl,tmu-range", range, len); 2229809797bSYuantian Tang if (val != 0) { 2239809797bSYuantian Tang dev_err(&pdev->dev, "failed to read range data.\n"); 2249809797bSYuantian Tang return val; 22543528445SJia Hongtao } 22643528445SJia Hongtao 22743528445SJia Hongtao /* Init temperature range registers */ 2289809797bSYuantian Tang for (i = 0; i < len; i++) 2299809797bSYuantian Tang tmu_write(data, range[i], &data->regs->ttrcr[i]); 23043528445SJia Hongtao 23143528445SJia Hongtao calibration = of_get_property(np, "fsl,tmu-calibration", &len); 23243528445SJia Hongtao if (calibration == NULL || len % 8) { 23343528445SJia Hongtao dev_err(&pdev->dev, "invalid calibration data.\n"); 23443528445SJia Hongtao return -ENODEV; 23543528445SJia Hongtao } 23643528445SJia Hongtao 23743528445SJia Hongtao for (i = 0; i < len; i += 8, calibration += 2) { 23843528445SJia Hongtao val = of_read_number(calibration, 1); 23943528445SJia Hongtao tmu_write(data, val, &data->regs->ttcfgr); 24043528445SJia Hongtao val = of_read_number(calibration + 1, 1); 24143528445SJia Hongtao tmu_write(data, val, &data->regs->tscfgr); 24243528445SJia Hongtao } 24343528445SJia Hongtao 24443528445SJia Hongtao return 0; 24543528445SJia Hongtao } 24643528445SJia Hongtao 24743528445SJia Hongtao static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) 24843528445SJia Hongtao { 24943528445SJia Hongtao /* Disable interrupt, using polling instead */ 25043528445SJia Hongtao tmu_write(data, TIER_DISABLE, &data->regs->tier); 25143528445SJia Hongtao 25243528445SJia Hongtao /* Set update_interval */ 2539809797bSYuantian Tang if (data->ver == TMU_VER1) { 25443528445SJia Hongtao tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir); 2559809797bSYuantian Tang } else { 2569809797bSYuantian Tang tmu_write(data, TMTMIR_DEFAULT, &data->regs_v2->tmtmir); 2579809797bSYuantian Tang tmu_write(data, TEUMR0_V2, &data->regs_v2->teumr0); 2589809797bSYuantian Tang } 25943528445SJia Hongtao 26043528445SJia Hongtao /* Disable monitoring */ 26143528445SJia Hongtao tmu_write(data, TMR_DISABLE, &data->regs->tmr); 26243528445SJia Hongtao } 26343528445SJia Hongtao 26443528445SJia Hongtao static int qoriq_tmu_probe(struct platform_device *pdev) 26543528445SJia Hongtao { 26643528445SJia Hongtao int ret; 2679809797bSYuantian Tang u32 ver; 26843528445SJia Hongtao struct qoriq_tmu_data *data; 26943528445SJia Hongtao struct device_node *np = pdev->dev.of_node; 270e167dc43SAndrey Smirnov struct device *dev = &pdev->dev; 27143528445SJia Hongtao 272e167dc43SAndrey Smirnov data = devm_kzalloc(dev, sizeof(struct qoriq_tmu_data), 27343528445SJia Hongtao GFP_KERNEL); 27443528445SJia Hongtao if (!data) 27543528445SJia Hongtao return -ENOMEM; 27643528445SJia Hongtao 27743528445SJia Hongtao platform_set_drvdata(pdev, data); 27843528445SJia Hongtao 27943528445SJia Hongtao data->little_endian = of_property_read_bool(np, "little-endian"); 28043528445SJia Hongtao 2814d82000aSAnson Huang data->regs = devm_platform_ioremap_resource(pdev, 0); 2824d82000aSAnson Huang if (IS_ERR(data->regs)) { 283e167dc43SAndrey Smirnov dev_err(dev, "Failed to get memory region\n"); 2844d82000aSAnson Huang return PTR_ERR(data->regs); 28543528445SJia Hongtao } 28643528445SJia Hongtao 287e167dc43SAndrey Smirnov data->clk = devm_clk_get_optional(dev, NULL); 28851904045SAnson Huang if (IS_ERR(data->clk)) 28951904045SAnson Huang return PTR_ERR(data->clk); 29051904045SAnson Huang 29151904045SAnson Huang ret = clk_prepare_enable(data->clk); 29251904045SAnson Huang if (ret) { 293e167dc43SAndrey Smirnov dev_err(dev, "Failed to enable clock\n"); 29451904045SAnson Huang return ret; 29551904045SAnson Huang } 29651904045SAnson Huang 2979809797bSYuantian Tang /* version register offset at: 0xbf8 on both v1 and v2 */ 2989809797bSYuantian Tang ver = tmu_read(data, &data->regs->ipbrr0); 2999809797bSYuantian Tang data->ver = (ver >> 8) & 0xff; 3009809797bSYuantian Tang if (data->ver == TMU_VER2) 3019809797bSYuantian Tang data->regs_v2 = (void __iomem *)data->regs; 3029809797bSYuantian Tang 30343528445SJia Hongtao qoriq_tmu_init_device(data); /* TMU initialization */ 30443528445SJia Hongtao 30543528445SJia Hongtao ret = qoriq_tmu_calibration(pdev); /* TMU calibration */ 30643528445SJia Hongtao if (ret < 0) 3074d82000aSAnson Huang goto err; 30843528445SJia Hongtao 3097797ff42SYuantian Tang ret = qoriq_tmu_register_tmu_zone(pdev); 3107797ff42SYuantian Tang if (ret < 0) { 311e167dc43SAndrey Smirnov dev_err(dev, "Failed to register sensors\n"); 3127797ff42SYuantian Tang ret = -ENODEV; 3134d82000aSAnson Huang goto err; 31443528445SJia Hongtao } 31543528445SJia Hongtao 31643528445SJia Hongtao return 0; 31743528445SJia Hongtao 3184d82000aSAnson Huang err: 31951904045SAnson Huang clk_disable_unprepare(data->clk); 32043528445SJia Hongtao platform_set_drvdata(pdev, NULL); 32143528445SJia Hongtao 32243528445SJia Hongtao return ret; 32343528445SJia Hongtao } 32443528445SJia Hongtao 32543528445SJia Hongtao static int qoriq_tmu_remove(struct platform_device *pdev) 32643528445SJia Hongtao { 32743528445SJia Hongtao struct qoriq_tmu_data *data = platform_get_drvdata(pdev); 32843528445SJia Hongtao 32943528445SJia Hongtao /* Disable monitoring */ 33043528445SJia Hongtao tmu_write(data, TMR_DISABLE, &data->regs->tmr); 33143528445SJia Hongtao 33251904045SAnson Huang clk_disable_unprepare(data->clk); 33351904045SAnson Huang 33443528445SJia Hongtao platform_set_drvdata(pdev, NULL); 33543528445SJia Hongtao 33643528445SJia Hongtao return 0; 33743528445SJia Hongtao } 33843528445SJia Hongtao 339aea59197SAnson Huang static int __maybe_unused qoriq_tmu_suspend(struct device *dev) 34043528445SJia Hongtao { 34143528445SJia Hongtao u32 tmr; 34243528445SJia Hongtao struct qoriq_tmu_data *data = dev_get_drvdata(dev); 34343528445SJia Hongtao 34443528445SJia Hongtao /* Disable monitoring */ 34543528445SJia Hongtao tmr = tmu_read(data, &data->regs->tmr); 34643528445SJia Hongtao tmr &= ~TMR_ME; 34743528445SJia Hongtao tmu_write(data, tmr, &data->regs->tmr); 34843528445SJia Hongtao 34951904045SAnson Huang clk_disable_unprepare(data->clk); 35051904045SAnson Huang 35143528445SJia Hongtao return 0; 35243528445SJia Hongtao } 35343528445SJia Hongtao 354aea59197SAnson Huang static int __maybe_unused qoriq_tmu_resume(struct device *dev) 35543528445SJia Hongtao { 35643528445SJia Hongtao u32 tmr; 35751904045SAnson Huang int ret; 35843528445SJia Hongtao struct qoriq_tmu_data *data = dev_get_drvdata(dev); 35943528445SJia Hongtao 36051904045SAnson Huang ret = clk_prepare_enable(data->clk); 36151904045SAnson Huang if (ret) 36251904045SAnson Huang return ret; 36351904045SAnson Huang 36443528445SJia Hongtao /* Enable monitoring */ 36543528445SJia Hongtao tmr = tmu_read(data, &data->regs->tmr); 36643528445SJia Hongtao tmr |= TMR_ME; 36743528445SJia Hongtao tmu_write(data, tmr, &data->regs->tmr); 36843528445SJia Hongtao 36943528445SJia Hongtao return 0; 37043528445SJia Hongtao } 37143528445SJia Hongtao 37243528445SJia Hongtao static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, 37343528445SJia Hongtao qoriq_tmu_suspend, qoriq_tmu_resume); 37443528445SJia Hongtao 37543528445SJia Hongtao static const struct of_device_id qoriq_tmu_match[] = { 37643528445SJia Hongtao { .compatible = "fsl,qoriq-tmu", }, 3776017e2a9SAnson Huang { .compatible = "fsl,imx8mq-tmu", }, 37843528445SJia Hongtao {}, 37943528445SJia Hongtao }; 38043528445SJia Hongtao MODULE_DEVICE_TABLE(of, qoriq_tmu_match); 38143528445SJia Hongtao 38243528445SJia Hongtao static struct platform_driver qoriq_tmu = { 38343528445SJia Hongtao .driver = { 38443528445SJia Hongtao .name = "qoriq_thermal", 38543528445SJia Hongtao .pm = &qoriq_tmu_pm_ops, 38643528445SJia Hongtao .of_match_table = qoriq_tmu_match, 38743528445SJia Hongtao }, 38843528445SJia Hongtao .probe = qoriq_tmu_probe, 38943528445SJia Hongtao .remove = qoriq_tmu_remove, 39043528445SJia Hongtao }; 39143528445SJia Hongtao module_platform_driver(qoriq_tmu); 39243528445SJia Hongtao 39343528445SJia Hongtao MODULE_AUTHOR("Jia Hongtao <hongtao.jia@nxp.com>"); 39443528445SJia Hongtao MODULE_DESCRIPTION("QorIQ Thermal Monitoring Unit driver"); 39543528445SJia Hongtao MODULE_LICENSE("GPL v2"); 396