Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | // SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2009-2010 Pengutronix * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de> * * loosely based on an earlier driver that has * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de> */ #include <linux/slab.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/mfd/core.h> #include <linux/mfd/mc13xxx.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/err.h> #include <linux/spi/spi.h> #include "mc13xxx.h" static const struct spi_device_id mc13xxx_device_id[] = { { .name = "mc13783", .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc13783, }, { .name = "mc13892", .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc13892, }, { .name = "mc34708", .driver_data = (kernel_ulong_t)&mc13xxx_variant_mc34708, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, mc13xxx_device_id); static const struct of_device_id mc13xxx_dt_ids[] = { { .compatible = "fsl,mc13783", .data = &mc13xxx_variant_mc13783, }, { .compatible = "fsl,mc13892", .data = &mc13xxx_variant_mc13892, }, { .compatible = "fsl,mc34708", .data = &mc13xxx_variant_mc34708, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mc13xxx_dt_ids); static const struct regmap_config mc13xxx_regmap_spi_config = { .reg_bits = 7, .pad_bits = 1, .val_bits = 24, .write_flag_mask = 0x80, .max_register = MC13XXX_NUMREGS, .cache_type = REGCACHE_NONE, .use_single_read = true, .use_single_write = true, }; static int mc13xxx_spi_read(void *context, const void *reg, size_t reg_size, void *val, size_t val_size) { unsigned char w[4] = { *((unsigned char *) reg), 0, 0, 0}; unsigned char r[4]; unsigned char *p = val; struct device *dev = context; struct spi_device *spi = to_spi_device(dev); struct spi_transfer t = { .tx_buf = w, .rx_buf = r, .len = 4, }; struct spi_message m; int ret; if (val_size != 3 || reg_size != 1) return -ENOTSUPP; spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spi_sync(spi, &m); memcpy(p, &r[1], 3); return ret; } static int mc13xxx_spi_write(void *context, const void *data, size_t count) { struct device *dev = context; struct spi_device *spi = to_spi_device(dev); const char *reg = data; if (count != 4) return -ENOTSUPP; /* include errata fix for spi audio problems */ if (*reg == MC13783_AUDIO_CODEC || *reg == MC13783_AUDIO_DAC) spi_write(spi, data, count); return spi_write(spi, data, count); } /* * We cannot use regmap-spi generic bus implementation here. * The MC13783 chip will get corrupted if CS signal is deasserted * and on i.Mx31 SoC (the target SoC for MC13783 PMIC) the SPI controller * has the following errata (DSPhl22960): * "The CSPI negates SS when the FIFO becomes empty with * SSCTL= 0. Software cannot guarantee that the FIFO will not * drain because of higher priority interrupts and the * non-realtime characteristics of the operating system. As a * result, the SS will negate before all of the data has been * transferred to/from the peripheral." * We workaround this by accessing the SPI controller with a * single transfer. */ static struct regmap_bus regmap_mc13xxx_bus = { .write = mc13xxx_spi_write, .read = mc13xxx_spi_read, }; static int mc13xxx_spi_probe(struct spi_device *spi) { struct mc13xxx *mc13xxx; int ret; mc13xxx = devm_kzalloc(&spi->dev, sizeof(*mc13xxx), GFP_KERNEL); if (!mc13xxx) return -ENOMEM; dev_set_drvdata(&spi->dev, mc13xxx); spi->mode = SPI_MODE_0 | SPI_CS_HIGH; mc13xxx->irq = spi->irq; spi->max_speed_hz = spi->max_speed_hz ? : 26000000; ret = spi_setup(spi); if (ret) return ret; mc13xxx->regmap = devm_regmap_init(&spi->dev, ®map_mc13xxx_bus, &spi->dev, &mc13xxx_regmap_spi_config); if (IS_ERR(mc13xxx->regmap)) { ret = PTR_ERR(mc13xxx->regmap); dev_err(&spi->dev, "Failed to initialize regmap: %d\n", ret); return ret; } if (spi->dev.of_node) { const struct of_device_id *of_id = of_match_device(mc13xxx_dt_ids, &spi->dev); mc13xxx->variant = of_id->data; } else { const struct spi_device_id *id_entry = spi_get_device_id(spi); mc13xxx->variant = (void *)id_entry->driver_data; } return mc13xxx_common_init(&spi->dev); } static void mc13xxx_spi_remove(struct spi_device *spi) { mc13xxx_common_exit(&spi->dev); } static struct spi_driver mc13xxx_spi_driver = { .id_table = mc13xxx_device_id, .driver = { .name = "mc13xxx", .of_match_table = mc13xxx_dt_ids, }, .probe = mc13xxx_spi_probe, .remove = mc13xxx_spi_remove, }; static int __init mc13xxx_init(void) { return spi_register_driver(&mc13xxx_spi_driver); } subsys_initcall(mc13xxx_init); static void __exit mc13xxx_exit(void) { spi_unregister_driver(&mc13xxx_spi_driver); } module_exit(mc13xxx_exit); MODULE_DESCRIPTION("Core driver for Freescale MC13XXX PMIC"); MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); MODULE_LICENSE("GPL v2"); |