1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Driver for Atmel Flexcom 4 * 5 * Copyright (C) 2015 Atmel Corporation 6 * 7 * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com> 8 */ 9 10 #include <linux/module.h> 11 #include <linux/types.h> 12 #include <linux/kernel.h> 13 #include <linux/platform_device.h> 14 #include <linux/of.h> 15 #include <linux/of_platform.h> 16 #include <linux/err.h> 17 #include <linux/io.h> 18 #include <linux/clk.h> 19 #include <dt-bindings/mfd/atmel-flexcom.h> 20 21 /* I/O register offsets */ 22 #define FLEX_MR 0x0 /* Mode Register */ 23 #define FLEX_VERSION 0xfc /* Version Register */ 24 25 /* Mode Register bit fields */ 26 #define FLEX_MR_OPMODE_OFFSET (0) /* Operating Mode */ 27 #define FLEX_MR_OPMODE_MASK (0x3 << FLEX_MR_OPMODE_OFFSET) 28 #define FLEX_MR_OPMODE(opmode) (((opmode) << FLEX_MR_OPMODE_OFFSET) & \ 29 FLEX_MR_OPMODE_MASK) 30 31 struct atmel_flexcom { 32 void __iomem *base; 33 u32 opmode; 34 struct clk *clk; 35 }; 36 37 static int atmel_flexcom_probe(struct platform_device *pdev) 38 { 39 struct device_node *np = pdev->dev.of_node; 40 struct resource *res; 41 struct atmel_flexcom *ddata; 42 int err; 43 44 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 45 if (!ddata) 46 return -ENOMEM; 47 48 platform_set_drvdata(pdev, ddata); 49 50 err = of_property_read_u32(np, "atmel,flexcom-mode", &ddata->opmode); 51 if (err) 52 return err; 53 54 if (ddata->opmode < ATMEL_FLEXCOM_MODE_USART || 55 ddata->opmode > ATMEL_FLEXCOM_MODE_TWI) 56 return -EINVAL; 57 58 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 59 ddata->base = devm_ioremap_resource(&pdev->dev, res); 60 if (IS_ERR(ddata->base)) 61 return PTR_ERR(ddata->base); 62 63 ddata->clk = devm_clk_get(&pdev->dev, NULL); 64 if (IS_ERR(ddata->clk)) 65 return PTR_ERR(ddata->clk); 66 67 err = clk_prepare_enable(ddata->clk); 68 if (err) 69 return err; 70 71 /* 72 * Set the Operating Mode in the Mode Register: only the selected device 73 * is clocked. Hence, registers of the other serial devices remain 74 * inaccessible and are read as zero. Also the external I/O lines of the 75 * Flexcom are muxed to reach the selected device. 76 */ 77 writel(FLEX_MR_OPMODE(ddata->opmode), ddata->base + FLEX_MR); 78 79 clk_disable_unprepare(ddata->clk); 80 81 return devm_of_platform_populate(&pdev->dev); 82 } 83 84 static const struct of_device_id atmel_flexcom_of_match[] = { 85 { .compatible = "atmel,sama5d2-flexcom" }, 86 { /* sentinel */ } 87 }; 88 MODULE_DEVICE_TABLE(of, atmel_flexcom_of_match); 89 90 static int __maybe_unused atmel_flexcom_resume_noirq(struct device *dev) 91 { 92 struct atmel_flexcom *ddata = dev_get_drvdata(dev); 93 int err; 94 u32 val; 95 96 err = clk_prepare_enable(ddata->clk); 97 if (err) 98 return err; 99 100 val = FLEX_MR_OPMODE(ddata->opmode), 101 writel(val, ddata->base + FLEX_MR); 102 103 clk_disable_unprepare(ddata->clk); 104 105 return 0; 106 } 107 108 static const struct dev_pm_ops atmel_flexcom_pm_ops = { 109 .resume_noirq = atmel_flexcom_resume_noirq, 110 }; 111 112 static struct platform_driver atmel_flexcom_driver = { 113 .probe = atmel_flexcom_probe, 114 .driver = { 115 .name = "atmel_flexcom", 116 .pm = pm_ptr(&atmel_flexcom_pm_ops), 117 .of_match_table = atmel_flexcom_of_match, 118 }, 119 }; 120 121 module_platform_driver(atmel_flexcom_driver); 122 123 MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>"); 124 MODULE_DESCRIPTION("Atmel Flexcom MFD driver"); 125 MODULE_LICENSE("GPL v2"); 126