Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2001-2004 Aurelien Jarno <aurelien@aurel32.net>
0004  * Ported to Linux 2.6 by Aurelien Jarno <aurelien@aurel32.net> with
0005  * the help of Jean Delvare <jdelvare@suse.de>
0006  */
0007 
0008 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0009 
0010 #include <linux/module.h>
0011 #include <linux/init.h>
0012 #include <linux/slab.h>
0013 #include <linux/i2c.h>
0014 #include <linux/mutex.h>
0015 #include <linux/err.h>
0016 #include <linux/hwmon.h>
0017 
0018 /* Insmod parameters */
0019 
0020 static int input_mode;
0021 module_param(input_mode, int, 0);
0022 MODULE_PARM_DESC(input_mode,
0023     "Analog input mode:\n"
0024     " 0 = four single ended inputs\n"
0025     " 1 = three differential inputs\n"
0026     " 2 = single ended and differential mixed\n"
0027     " 3 = two differential inputs\n");
0028 
0029 /*
0030  * The PCF8591 control byte
0031  *      7    6    5    4    3    2    1    0
0032  *   |  0 |AOEF|   AIP   |  0 |AINC|  AICH   |
0033  */
0034 
0035 /* Analog Output Enable Flag (analog output active if 1) */
0036 #define PCF8591_CONTROL_AOEF        0x40
0037 
0038 /*
0039  * Analog Input Programming
0040  * 0x00 = four single ended inputs
0041  * 0x10 = three differential inputs
0042  * 0x20 = single ended and differential mixed
0043  * 0x30 = two differential inputs
0044  */
0045 #define PCF8591_CONTROL_AIP_MASK    0x30
0046 
0047 /* Autoincrement Flag (switch on if 1) */
0048 #define PCF8591_CONTROL_AINC        0x04
0049 
0050 /*
0051  * Channel selection
0052  * 0x00 = channel 0
0053  * 0x01 = channel 1
0054  * 0x02 = channel 2
0055  * 0x03 = channel 3
0056  */
0057 #define PCF8591_CONTROL_AICH_MASK   0x03
0058 
0059 /* Initial values */
0060 #define PCF8591_INIT_CONTROL    ((input_mode << 4) | PCF8591_CONTROL_AOEF)
0061 #define PCF8591_INIT_AOUT   0   /* DAC out = 0 */
0062 
0063 /* Conversions */
0064 #define REG_TO_SIGNED(reg)  (((reg) & 0x80) ? ((reg) - 256) : (reg))
0065 
0066 struct pcf8591_data {
0067     struct device *hwmon_dev;
0068     struct mutex update_lock;
0069 
0070     u8 control;
0071     u8 aout;
0072 };
0073 
0074 static void pcf8591_init_client(struct i2c_client *client);
0075 static int pcf8591_read_channel(struct device *dev, int channel);
0076 
0077 /* following are the sysfs callback functions */
0078 #define show_in_channel(channel)                    \
0079 static ssize_t show_in##channel##_input(struct device *dev,     \
0080                     struct device_attribute *attr,  \
0081                     char *buf)          \
0082 {                                   \
0083     return sprintf(buf, "%d\n", pcf8591_read_channel(dev, channel));\
0084 }                                   \
0085 static DEVICE_ATTR(in##channel##_input, S_IRUGO,            \
0086            show_in##channel##_input, NULL);
0087 
0088 show_in_channel(0);
0089 show_in_channel(1);
0090 show_in_channel(2);
0091 show_in_channel(3);
0092 
0093 static ssize_t out0_output_show(struct device *dev,
0094                 struct device_attribute *attr, char *buf)
0095 {
0096     struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev));
0097     return sprintf(buf, "%d\n", data->aout * 10);
0098 }
0099 
0100 static ssize_t out0_output_store(struct device *dev,
0101                  struct device_attribute *attr,
0102                  const char *buf, size_t count)
0103 {
0104     unsigned long val;
0105     struct i2c_client *client = to_i2c_client(dev);
0106     struct pcf8591_data *data = i2c_get_clientdata(client);
0107     int err;
0108 
0109     err = kstrtoul(buf, 10, &val);
0110     if (err)
0111         return err;
0112 
0113     val /= 10;
0114     if (val > 255)
0115         return -EINVAL;
0116 
0117     data->aout = val;
0118     i2c_smbus_write_byte_data(client, data->control, data->aout);
0119     return count;
0120 }
0121 
0122 static DEVICE_ATTR_RW(out0_output);
0123 
0124 static ssize_t out0_enable_show(struct device *dev,
0125                 struct device_attribute *attr, char *buf)
0126 {
0127     struct pcf8591_data *data = i2c_get_clientdata(to_i2c_client(dev));
0128     return sprintf(buf, "%u\n", !(!(data->control & PCF8591_CONTROL_AOEF)));
0129 }
0130 
0131 static ssize_t out0_enable_store(struct device *dev,
0132                  struct device_attribute *attr,
0133                  const char *buf, size_t count)
0134 {
0135     struct i2c_client *client = to_i2c_client(dev);
0136     struct pcf8591_data *data = i2c_get_clientdata(client);
0137     unsigned long val;
0138     int err;
0139 
0140     err = kstrtoul(buf, 10, &val);
0141     if (err)
0142         return err;
0143 
0144     mutex_lock(&data->update_lock);
0145     if (val)
0146         data->control |= PCF8591_CONTROL_AOEF;
0147     else
0148         data->control &= ~PCF8591_CONTROL_AOEF;
0149     i2c_smbus_write_byte(client, data->control);
0150     mutex_unlock(&data->update_lock);
0151     return count;
0152 }
0153 
0154 static DEVICE_ATTR_RW(out0_enable);
0155 
0156 static struct attribute *pcf8591_attributes[] = {
0157     &dev_attr_out0_enable.attr,
0158     &dev_attr_out0_output.attr,
0159     &dev_attr_in0_input.attr,
0160     &dev_attr_in1_input.attr,
0161     NULL
0162 };
0163 
0164 static const struct attribute_group pcf8591_attr_group = {
0165     .attrs = pcf8591_attributes,
0166 };
0167 
0168 static struct attribute *pcf8591_attributes_opt[] = {
0169     &dev_attr_in2_input.attr,
0170     &dev_attr_in3_input.attr,
0171     NULL
0172 };
0173 
0174 static const struct attribute_group pcf8591_attr_group_opt = {
0175     .attrs = pcf8591_attributes_opt,
0176 };
0177 
0178 /*
0179  * Real code
0180  */
0181 
0182 static int pcf8591_probe(struct i2c_client *client)
0183 {
0184     struct pcf8591_data *data;
0185     int err;
0186 
0187     data = devm_kzalloc(&client->dev, sizeof(struct pcf8591_data),
0188                 GFP_KERNEL);
0189     if (!data)
0190         return -ENOMEM;
0191 
0192     i2c_set_clientdata(client, data);
0193     mutex_init(&data->update_lock);
0194 
0195     /* Initialize the PCF8591 chip */
0196     pcf8591_init_client(client);
0197 
0198     /* Register sysfs hooks */
0199     err = sysfs_create_group(&client->dev.kobj, &pcf8591_attr_group);
0200     if (err)
0201         return err;
0202 
0203     /* Register input2 if not in "two differential inputs" mode */
0204     if (input_mode != 3) {
0205         err = device_create_file(&client->dev, &dev_attr_in2_input);
0206         if (err)
0207             goto exit_sysfs_remove;
0208     }
0209 
0210     /* Register input3 only in "four single ended inputs" mode */
0211     if (input_mode == 0) {
0212         err = device_create_file(&client->dev, &dev_attr_in3_input);
0213         if (err)
0214             goto exit_sysfs_remove;
0215     }
0216 
0217     data->hwmon_dev = hwmon_device_register(&client->dev);
0218     if (IS_ERR(data->hwmon_dev)) {
0219         err = PTR_ERR(data->hwmon_dev);
0220         goto exit_sysfs_remove;
0221     }
0222 
0223     return 0;
0224 
0225 exit_sysfs_remove:
0226     sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt);
0227     sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group);
0228     return err;
0229 }
0230 
0231 static int pcf8591_remove(struct i2c_client *client)
0232 {
0233     struct pcf8591_data *data = i2c_get_clientdata(client);
0234 
0235     hwmon_device_unregister(data->hwmon_dev);
0236     sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group_opt);
0237     sysfs_remove_group(&client->dev.kobj, &pcf8591_attr_group);
0238     return 0;
0239 }
0240 
0241 /* Called when we have found a new PCF8591. */
0242 static void pcf8591_init_client(struct i2c_client *client)
0243 {
0244     struct pcf8591_data *data = i2c_get_clientdata(client);
0245     data->control = PCF8591_INIT_CONTROL;
0246     data->aout = PCF8591_INIT_AOUT;
0247 
0248     i2c_smbus_write_byte_data(client, data->control, data->aout);
0249 
0250     /*
0251      * The first byte transmitted contains the conversion code of the
0252      * previous read cycle. FLUSH IT!
0253      */
0254     i2c_smbus_read_byte(client);
0255 }
0256 
0257 static int pcf8591_read_channel(struct device *dev, int channel)
0258 {
0259     u8 value;
0260     struct i2c_client *client = to_i2c_client(dev);
0261     struct pcf8591_data *data = i2c_get_clientdata(client);
0262 
0263     mutex_lock(&data->update_lock);
0264 
0265     if ((data->control & PCF8591_CONTROL_AICH_MASK) != channel) {
0266         data->control = (data->control & ~PCF8591_CONTROL_AICH_MASK)
0267                   | channel;
0268         i2c_smbus_write_byte(client, data->control);
0269 
0270         /*
0271          * The first byte transmitted contains the conversion code of
0272          * the previous read cycle. FLUSH IT!
0273          */
0274         i2c_smbus_read_byte(client);
0275     }
0276     value = i2c_smbus_read_byte(client);
0277 
0278     mutex_unlock(&data->update_lock);
0279 
0280     if ((channel == 2 && input_mode == 2) ||
0281         (channel != 3 && (input_mode == 1 || input_mode == 3)))
0282         return 10 * REG_TO_SIGNED(value);
0283     else
0284         return 10 * value;
0285 }
0286 
0287 static const struct i2c_device_id pcf8591_id[] = {
0288     { "pcf8591", 0 },
0289     { }
0290 };
0291 MODULE_DEVICE_TABLE(i2c, pcf8591_id);
0292 
0293 static struct i2c_driver pcf8591_driver = {
0294     .driver = {
0295         .name   = "pcf8591",
0296     },
0297     .probe_new  = pcf8591_probe,
0298     .remove     = pcf8591_remove,
0299     .id_table   = pcf8591_id,
0300 };
0301 
0302 static int __init pcf8591_init(void)
0303 {
0304     if (input_mode < 0 || input_mode > 3) {
0305         pr_warn("invalid input_mode (%d)\n", input_mode);
0306         input_mode = 0;
0307     }
0308     return i2c_add_driver(&pcf8591_driver);
0309 }
0310 
0311 static void __exit pcf8591_exit(void)
0312 {
0313     i2c_del_driver(&pcf8591_driver);
0314 }
0315 
0316 MODULE_AUTHOR("Aurelien Jarno <aurelien@aurel32.net>");
0317 MODULE_DESCRIPTION("PCF8591 driver");
0318 MODULE_LICENSE("GPL");
0319 
0320 module_init(pcf8591_init);
0321 module_exit(pcf8591_exit);