18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/export.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/regmap.h> 98c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 108c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 118c2ecf20Sopenharmony_ci#include <linux/reset-controller.h> 128c2ecf20Sopenharmony_ci#include <linux/of.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "common.h" 158c2ecf20Sopenharmony_ci#include "clk-rcg.h" 168c2ecf20Sopenharmony_ci#include "clk-regmap.h" 178c2ecf20Sopenharmony_ci#include "reset.h" 188c2ecf20Sopenharmony_ci#include "gdsc.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct qcom_cc { 218c2ecf20Sopenharmony_ci struct qcom_reset_controller reset; 228c2ecf20Sopenharmony_ci struct clk_regmap **rclks; 238c2ecf20Sopenharmony_ci size_t num_rclks; 248c2ecf20Sopenharmony_ci}; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciconst 278c2ecf20Sopenharmony_cistruct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci if (!f) 308c2ecf20Sopenharmony_ci return NULL; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci if (!f->freq) 338c2ecf20Sopenharmony_ci return f; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci for (; f->freq; f++) 368c2ecf20Sopenharmony_ci if (rate <= f->freq) 378c2ecf20Sopenharmony_ci return f; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* Default to our fastest rate */ 408c2ecf20Sopenharmony_ci return f - 1; 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_freq); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ciconst struct freq_tbl *qcom_find_freq_floor(const struct freq_tbl *f, 458c2ecf20Sopenharmony_ci unsigned long rate) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci const struct freq_tbl *best = NULL; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci for ( ; f->freq; f++) { 508c2ecf20Sopenharmony_ci if (rate >= f->freq) 518c2ecf20Sopenharmony_ci best = f; 528c2ecf20Sopenharmony_ci else 538c2ecf20Sopenharmony_ci break; 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return best; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_freq_floor); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ciint qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map, u8 src) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci int i, num_parents = clk_hw_get_num_parents(hw); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci for (i = 0; i < num_parents; i++) 658c2ecf20Sopenharmony_ci if (src == map[i].src) 668c2ecf20Sopenharmony_ci return i; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return -ENOENT; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_src_index); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ciint qcom_find_cfg_index(struct clk_hw *hw, const struct parent_map *map, u8 cfg) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci int i, num_parents = clk_hw_get_num_parents(hw); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci for (i = 0; i < num_parents; i++) 778c2ecf20Sopenharmony_ci if (cfg == map[i].cfg) 788c2ecf20Sopenharmony_ci return i; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return -ENOENT; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_cfg_index); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistruct regmap * 858c2ecf20Sopenharmony_ciqcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci void __iomem *base; 888c2ecf20Sopenharmony_ci struct resource *res; 898c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 928c2ecf20Sopenharmony_ci base = devm_ioremap_resource(dev, res); 938c2ecf20Sopenharmony_ci if (IS_ERR(base)) 948c2ecf20Sopenharmony_ci return ERR_CAST(base); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return devm_regmap_init_mmio(dev, base, desc->config); 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_map); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_civoid 1018c2ecf20Sopenharmony_ciqcom_pll_set_fsm_mode(struct regmap *map, u32 reg, u8 bias_count, u8 lock_count) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci u32 val; 1048c2ecf20Sopenharmony_ci u32 mask; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* De-assert reset to FSM */ 1078c2ecf20Sopenharmony_ci regmap_update_bits(map, reg, PLL_VOTE_FSM_RESET, 0); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci /* Program bias count and lock count */ 1108c2ecf20Sopenharmony_ci val = bias_count << PLL_BIAS_COUNT_SHIFT | 1118c2ecf20Sopenharmony_ci lock_count << PLL_LOCK_COUNT_SHIFT; 1128c2ecf20Sopenharmony_ci mask = PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT; 1138c2ecf20Sopenharmony_ci mask |= PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT; 1148c2ecf20Sopenharmony_ci regmap_update_bits(map, reg, mask, val); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci /* Enable PLL FSM voting */ 1178c2ecf20Sopenharmony_ci regmap_update_bits(map, reg, PLL_VOTE_FSM_ENA, PLL_VOTE_FSM_ENA); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_pll_set_fsm_mode); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic void qcom_cc_gdsc_unregister(void *data) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci gdsc_unregister(data); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * Backwards compatibility with old DTs. Register a pass-through factor 1/1 1288c2ecf20Sopenharmony_ci * clock to translate 'path' clk into 'name' clk and register the 'path' 1298c2ecf20Sopenharmony_ci * clk as a fixed rate clock if it isn't present. 1308c2ecf20Sopenharmony_ci */ 1318c2ecf20Sopenharmony_cistatic int _qcom_cc_register_board_clk(struct device *dev, const char *path, 1328c2ecf20Sopenharmony_ci const char *name, unsigned long rate, 1338c2ecf20Sopenharmony_ci bool add_factor) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct device_node *node = NULL; 1368c2ecf20Sopenharmony_ci struct device_node *clocks_node; 1378c2ecf20Sopenharmony_ci struct clk_fixed_factor *factor; 1388c2ecf20Sopenharmony_ci struct clk_fixed_rate *fixed; 1398c2ecf20Sopenharmony_ci struct clk_init_data init_data = { }; 1408c2ecf20Sopenharmony_ci int ret; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci clocks_node = of_find_node_by_path("/clocks"); 1438c2ecf20Sopenharmony_ci if (clocks_node) { 1448c2ecf20Sopenharmony_ci node = of_get_child_by_name(clocks_node, path); 1458c2ecf20Sopenharmony_ci of_node_put(clocks_node); 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (!node) { 1498c2ecf20Sopenharmony_ci fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL); 1508c2ecf20Sopenharmony_ci if (!fixed) 1518c2ecf20Sopenharmony_ci return -EINVAL; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci fixed->fixed_rate = rate; 1548c2ecf20Sopenharmony_ci fixed->hw.init = &init_data; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci init_data.name = path; 1578c2ecf20Sopenharmony_ci init_data.ops = &clk_fixed_rate_ops; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci ret = devm_clk_hw_register(dev, &fixed->hw); 1608c2ecf20Sopenharmony_ci if (ret) 1618c2ecf20Sopenharmony_ci return ret; 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci of_node_put(node); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (add_factor) { 1668c2ecf20Sopenharmony_ci factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL); 1678c2ecf20Sopenharmony_ci if (!factor) 1688c2ecf20Sopenharmony_ci return -EINVAL; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci factor->mult = factor->div = 1; 1718c2ecf20Sopenharmony_ci factor->hw.init = &init_data; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci init_data.name = name; 1748c2ecf20Sopenharmony_ci init_data.parent_names = &path; 1758c2ecf20Sopenharmony_ci init_data.num_parents = 1; 1768c2ecf20Sopenharmony_ci init_data.flags = 0; 1778c2ecf20Sopenharmony_ci init_data.ops = &clk_fixed_factor_ops; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = devm_clk_hw_register(dev, &factor->hw); 1808c2ecf20Sopenharmony_ci if (ret) 1818c2ecf20Sopenharmony_ci return ret; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ciint qcom_cc_register_board_clk(struct device *dev, const char *path, 1888c2ecf20Sopenharmony_ci const char *name, unsigned long rate) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci bool add_factor = true; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* 1938c2ecf20Sopenharmony_ci * TODO: The RPM clock driver currently does not support the xo clock. 1948c2ecf20Sopenharmony_ci * When xo is added to the RPM clock driver, we should change this 1958c2ecf20Sopenharmony_ci * function to skip registration of xo factor clocks. 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor); 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_register_board_clk); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ciint qcom_cc_register_sleep_clk(struct device *dev) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src", 2058c2ecf20Sopenharmony_ci 32768, true); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci/* Drop 'protected-clocks' from the list of clocks to register */ 2108c2ecf20Sopenharmony_cistatic void qcom_cc_drop_protected(struct device *dev, struct qcom_cc *cc) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci struct device_node *np = dev->of_node; 2138c2ecf20Sopenharmony_ci struct property *prop; 2148c2ecf20Sopenharmony_ci const __be32 *p; 2158c2ecf20Sopenharmony_ci u32 i; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci of_property_for_each_u32(np, "protected-clocks", prop, p, i) { 2188c2ecf20Sopenharmony_ci if (i >= cc->num_rclks) 2198c2ecf20Sopenharmony_ci continue; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci cc->rclks[i] = NULL; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci} 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cistatic struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec, 2268c2ecf20Sopenharmony_ci void *data) 2278c2ecf20Sopenharmony_ci{ 2288c2ecf20Sopenharmony_ci struct qcom_cc *cc = data; 2298c2ecf20Sopenharmony_ci unsigned int idx = clkspec->args[0]; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (idx >= cc->num_rclks) { 2328c2ecf20Sopenharmony_ci pr_err("%s: invalid index %u\n", __func__, idx); 2338c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ciint qcom_cc_really_probe(struct platform_device *pdev, 2408c2ecf20Sopenharmony_ci const struct qcom_cc_desc *desc, struct regmap *regmap) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci int i, ret; 2438c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2448c2ecf20Sopenharmony_ci struct qcom_reset_controller *reset; 2458c2ecf20Sopenharmony_ci struct qcom_cc *cc; 2468c2ecf20Sopenharmony_ci struct gdsc_desc *scd; 2478c2ecf20Sopenharmony_ci size_t num_clks = desc->num_clks; 2488c2ecf20Sopenharmony_ci struct clk_regmap **rclks = desc->clks; 2498c2ecf20Sopenharmony_ci size_t num_clk_hws = desc->num_clk_hws; 2508c2ecf20Sopenharmony_ci struct clk_hw **clk_hws = desc->clk_hws; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); 2538c2ecf20Sopenharmony_ci if (!cc) 2548c2ecf20Sopenharmony_ci return -ENOMEM; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci reset = &cc->reset; 2578c2ecf20Sopenharmony_ci reset->rcdev.of_node = dev->of_node; 2588c2ecf20Sopenharmony_ci reset->rcdev.ops = &qcom_reset_ops; 2598c2ecf20Sopenharmony_ci reset->rcdev.owner = dev->driver->owner; 2608c2ecf20Sopenharmony_ci reset->rcdev.nr_resets = desc->num_resets; 2618c2ecf20Sopenharmony_ci reset->regmap = regmap; 2628c2ecf20Sopenharmony_ci reset->reset_map = desc->resets; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci ret = devm_reset_controller_register(dev, &reset->rcdev); 2658c2ecf20Sopenharmony_ci if (ret) 2668c2ecf20Sopenharmony_ci return ret; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (desc->gdscs && desc->num_gdscs) { 2698c2ecf20Sopenharmony_ci scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL); 2708c2ecf20Sopenharmony_ci if (!scd) 2718c2ecf20Sopenharmony_ci return -ENOMEM; 2728c2ecf20Sopenharmony_ci scd->dev = dev; 2738c2ecf20Sopenharmony_ci scd->scs = desc->gdscs; 2748c2ecf20Sopenharmony_ci scd->num = desc->num_gdscs; 2758c2ecf20Sopenharmony_ci ret = gdsc_register(scd, &reset->rcdev, regmap); 2768c2ecf20Sopenharmony_ci if (ret) 2778c2ecf20Sopenharmony_ci return ret; 2788c2ecf20Sopenharmony_ci ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister, 2798c2ecf20Sopenharmony_ci scd); 2808c2ecf20Sopenharmony_ci if (ret) 2818c2ecf20Sopenharmony_ci return ret; 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci cc->rclks = rclks; 2858c2ecf20Sopenharmony_ci cc->num_rclks = num_clks; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci qcom_cc_drop_protected(dev, cc); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci for (i = 0; i < num_clk_hws; i++) { 2908c2ecf20Sopenharmony_ci ret = devm_clk_hw_register(dev, clk_hws[i]); 2918c2ecf20Sopenharmony_ci if (ret) 2928c2ecf20Sopenharmony_ci return ret; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci for (i = 0; i < num_clks; i++) { 2968c2ecf20Sopenharmony_ci if (!rclks[i]) 2978c2ecf20Sopenharmony_ci continue; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci ret = devm_clk_register_regmap(dev, rclks[i]); 3008c2ecf20Sopenharmony_ci if (ret) 3018c2ecf20Sopenharmony_ci return ret; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc); 3058c2ecf20Sopenharmony_ci if (ret) 3068c2ecf20Sopenharmony_ci return ret; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci return 0; 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_really_probe); 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ciint qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci struct regmap *regmap; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci regmap = qcom_cc_map(pdev, desc); 3178c2ecf20Sopenharmony_ci if (IS_ERR(regmap)) 3188c2ecf20Sopenharmony_ci return PTR_ERR(regmap); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci return qcom_cc_really_probe(pdev, desc, regmap); 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_probe); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ciint qcom_cc_probe_by_index(struct platform_device *pdev, int index, 3258c2ecf20Sopenharmony_ci const struct qcom_cc_desc *desc) 3268c2ecf20Sopenharmony_ci{ 3278c2ecf20Sopenharmony_ci struct regmap *regmap; 3288c2ecf20Sopenharmony_ci struct resource *res; 3298c2ecf20Sopenharmony_ci void __iomem *base; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, index); 3328c2ecf20Sopenharmony_ci base = devm_ioremap_resource(&pdev->dev, res); 3338c2ecf20Sopenharmony_ci if (IS_ERR(base)) 3348c2ecf20Sopenharmony_ci return -ENOMEM; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config); 3378c2ecf20Sopenharmony_ci if (IS_ERR(regmap)) 3388c2ecf20Sopenharmony_ci return PTR_ERR(regmap); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci return qcom_cc_really_probe(pdev, desc, regmap); 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_probe_by_index); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 345