1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2014 Free Electrons 4 * Copyright (C) 2014 Atmel 5 * 6 * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> 7 */ 8 9 #include <linux/clk.h> 10 #include <linux/iopoll.h> 11 #include <linux/mfd/atmel-hlcdc.h> 12 #include <linux/mfd/core.h> 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/regmap.h> 16 17 #define ATMEL_HLCDC_REG_MAX (0x4000 - 0x4) 18 19 struct atmel_hlcdc_regmap { 20 void __iomem *regs; 21 struct device *dev; 22 }; 23 24 static const struct mfd_cell atmel_hlcdc_cells[] = { 25 { 26 .name = "atmel-hlcdc-pwm", 27 .of_compatible = "atmel,hlcdc-pwm", 28 }, 29 { 30 .name = "atmel-hlcdc-dc", 31 .of_compatible = "atmel,hlcdc-display-controller", 32 }, 33 }; 34 35 static int regmap_atmel_hlcdc_reg_write(void *context, unsigned int reg, 36 unsigned int val) 37 { 38 struct atmel_hlcdc_regmap *hregmap = context; 39 40 if (reg <= ATMEL_HLCDC_DIS) { 41 u32 status; 42 int ret; 43 44 ret = readl_poll_timeout_atomic(hregmap->regs + ATMEL_HLCDC_SR, 45 status, 46 !(status & ATMEL_HLCDC_SIP), 47 1, 100); 48 if (ret) { 49 dev_err(hregmap->dev, 50 "Timeout! Clock domain synchronization is in progress!\n"); 51 return ret; 52 } 53 } 54 55 writel(val, hregmap->regs + reg); 56 57 return 0; 58 } 59 60 static int regmap_atmel_hlcdc_reg_read(void *context, unsigned int reg, 61 unsigned int *val) 62 { 63 struct atmel_hlcdc_regmap *hregmap = context; 64 65 *val = readl(hregmap->regs + reg); 66 67 return 0; 68 } 69 70 static const struct regmap_config atmel_hlcdc_regmap_config = { 71 .reg_bits = 32, 72 .val_bits = 32, 73 .reg_stride = 4, 74 .max_register = ATMEL_HLCDC_REG_MAX, 75 .reg_write = regmap_atmel_hlcdc_reg_write, 76 .reg_read = regmap_atmel_hlcdc_reg_read, 77 .fast_io = true, 78 }; 79 80 static int atmel_hlcdc_probe(struct platform_device *pdev) 81 { 82 struct atmel_hlcdc_regmap *hregmap; 83 struct device *dev = &pdev->dev; 84 struct atmel_hlcdc *hlcdc; 85 86 hregmap = devm_kzalloc(dev, sizeof(*hregmap), GFP_KERNEL); 87 if (!hregmap) 88 return -ENOMEM; 89 90 hlcdc = devm_kzalloc(dev, sizeof(*hlcdc), GFP_KERNEL); 91 if (!hlcdc) 92 return -ENOMEM; 93 94 hregmap->regs = devm_platform_ioremap_resource(pdev, 0); 95 if (IS_ERR(hregmap->regs)) 96 return PTR_ERR(hregmap->regs); 97 98 hregmap->dev = &pdev->dev; 99 100 hlcdc->irq = platform_get_irq(pdev, 0); 101 if (hlcdc->irq < 0) 102 return hlcdc->irq; 103 104 hlcdc->periph_clk = devm_clk_get(dev, "periph_clk"); 105 if (IS_ERR(hlcdc->periph_clk)) { 106 dev_err(dev, "failed to get peripheral clock\n"); 107 return PTR_ERR(hlcdc->periph_clk); 108 } 109 110 /* 111 * Retrieve one of the primary clocks required for LCD operation: 112 * prefer sys_clk (for RGB/MIPI), and fall back to lvds_pll_clk 113 * (for LVDS) if needed. 114 */ 115 hlcdc->sys_clk = devm_clk_get(dev, "sys_clk"); 116 if (IS_ERR(hlcdc->sys_clk)) { 117 hlcdc->sys_clk = NULL; 118 hlcdc->lvds_pll_clk = devm_clk_get(dev, "lvds_pll_clk"); 119 if (IS_ERR(hlcdc->lvds_pll_clk)) { 120 dev_err(dev, "Failed to obtain both the LCDC (generic) and LVDS PLL clocks\n"); 121 return PTR_ERR(hlcdc->lvds_pll_clk); 122 } 123 } 124 125 hlcdc->slow_clk = devm_clk_get(dev, "slow_clk"); 126 if (IS_ERR(hlcdc->slow_clk)) { 127 dev_err(dev, "failed to get slow clock\n"); 128 return PTR_ERR(hlcdc->slow_clk); 129 } 130 131 hlcdc->regmap = devm_regmap_init(dev, NULL, hregmap, 132 &atmel_hlcdc_regmap_config); 133 if (IS_ERR(hlcdc->regmap)) 134 return PTR_ERR(hlcdc->regmap); 135 136 dev_set_drvdata(dev, hlcdc); 137 138 return devm_mfd_add_devices(dev, -1, atmel_hlcdc_cells, 139 ARRAY_SIZE(atmel_hlcdc_cells), 140 NULL, 0, NULL); 141 } 142 143 static const struct of_device_id atmel_hlcdc_match[] = { 144 { .compatible = "atmel,at91sam9n12-hlcdc" }, 145 { .compatible = "atmel,at91sam9x5-hlcdc" }, 146 { .compatible = "atmel,sama5d2-hlcdc" }, 147 { .compatible = "atmel,sama5d3-hlcdc" }, 148 { .compatible = "atmel,sama5d4-hlcdc" }, 149 { .compatible = "microchip,sam9x60-hlcdc" }, 150 { .compatible = "microchip,sam9x75-xlcdc" }, 151 { .compatible = "microchip,sama7d65-xlcdc" }, 152 { /* sentinel */ }, 153 }; 154 MODULE_DEVICE_TABLE(of, atmel_hlcdc_match); 155 156 static struct platform_driver atmel_hlcdc_driver = { 157 .probe = atmel_hlcdc_probe, 158 .driver = { 159 .name = "atmel-hlcdc", 160 .of_match_table = atmel_hlcdc_match, 161 }, 162 }; 163 module_platform_driver(atmel_hlcdc_driver); 164 165 MODULE_ALIAS("platform:atmel-hlcdc"); 166 MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); 167 MODULE_DESCRIPTION("Atmel HLCDC driver"); 168 MODULE_LICENSE("GPL v2"); 169